advances in attacking linux kernel 17 Ottobre 2006 Net & System Security Pisa, Italia Pierre Falda [email protected] // introduzione _In questo intervento vedremo come sia possibile manomettere un sistema linux in modalità completamente nascosta sfruttando l’hardware sottostante come complice. _I concetti di base sono validi per qualsiasi sistema operativo _Come prerequisiti sono richieste unicamentemte un minimo di dimestichezza col linguaggio C ed Assembly oltre ad alcune nozioni base di sistemi operativi Pierre Falda [email protected] // sommario 01// Panoramica 02// Terminologia 03// Uso ed abuso di sys call table e kmem 04// Cenni sul rilevamento 05// Caratteristiche abusabili dell’hardware 06// Abuso dei meccanismi di linux 07// conclusioni Pierre Falda [email protected] 01// Panoramica Pierre Falda [email protected] // 01 panoramica Partiremo dall’analisi generale di alcune tecniche di manomissione già note fino ad arrivare a vedere, piu o meno approfonditamente, come sia possibile fonderle per ottenere un potente nuovo motore di occultamento delle informazioni. L’esposizione avrà lo scopo di fornire principalmente gli strumenti concettuali per comprendere la struttura, il funzionamento nonché la pericolosità di software di questo tipo. Pierre Falda [email protected] 02// Terminologia Pierre Falda [email protected] // 02 terminologia Syscalls: funzioni che ci offre il kernel per interfacciarci con le sue funzionalità Sys call table: è una tabella residente a kspace contenente gli indirizzi delle syscall Kmem: /dev/kmem è un character device che fornisce un'immagine della memoria virtuale del kernel. Interrupt: è un evento sincrono od asincrono che altera la sequenza di istruzioni eseguite dal processore Exception: segnale lanciato dalla CPU per segnalare un evento eccezionale Interrupt Handler: funzione che si fa carico della gestione di un determinato interrupt Pierre Falda [email protected] // 02 terminologia Interrupt Descriptor Table: tabella che associa ciascun interrupt col suo interrupt handler e le relative informazioni di supporto Breakpoint: è una qualsiasi locazione all’interno del programma scelta dallo sviluppatore dove l’esecuzione del programma stesso viene fermata Virtual File System: Il virtual file system e' un layer del kernel che si occupa di gestire tutte le syscall legate ad un filesystem IA-32: Architettura dei processori intel a 32 bit Pierre Falda [email protected] 03// Uso ed abuso di sys call table e kmem Pierre Falda [email protected] // 03 come viene utilizzata la sct? Pierre Falda [email protected] // 03 come viene utilizzata la sct? Pierre Falda [email protected] // 03 abuso della sys call table La prima generazione di LKM maligni modificava la s.c.t. ... Sys_exit 0xc0123456 Sys_fork 0xc0789101 Sys_read 0xc0112131 Sys_write Hacked Write 0xc0415161 0xbadc0ded ... 0xc0123456: int sys_exit(int) … .. 0xc0789101: int sys_fork(void) … .. 0xc0112131: int sys_read(int,void*,int) … .. 0xc0415161: int sys_write(int,void*,int) … .. 0xbadc0ded: int hacked_write(int,void*,void) Pierre Falda [email protected] // 03 abuso della sys call table PRO: _Estremamente semplice da realizzare _Permette un’altissima portabilità CONTRO: _Semplice da rilevare _Semplice rilevare quanto nascosto tramite questa tecnica in quanto non si opera sulle informazioni vere e proprie ma solo sulla loro visualizzazione. PROGRAMMA: _Adore Pierre Falda [email protected] // 03 abuso DI KMEM _Una generazione piu recente di kernel malware ha riadottato l’utilizzo parziale di questa tecnica per questioni implementative e di portabilità _/dev/kmem , come precedentemente detto, fornisce un'immagine della memoria virtuale del kernel. Normalmente utilizzato da X (ovvero il server grafico) puo essere sfruttato anche per inserire dati a piacere, tramite opportune tecniche _L’attacco non viene piu portato a termine tramite l’ausilio di lkm ma iniettando direttamente del codice a kernel space Pierre Falda [email protected] // 03 abuso DI KMEM PRO: _L’attacco non viene piu portato a termine tramite l’ausilio di lkm ma iniettando direttamente del codice a kernel space CONTRO: _Lo sviluppo del relativo software è piu complesso e laborioso PROGRAMMA: _Suckit Pierre Falda [email protected] // 03 SUCKIT _Crea una propria sys call table contenente puntatori alle proprie syscall maligne _Non modifica la sys call table del sistema ma sostituisce il suo indirizzo nello handler dell’interrupt 0x80 _Si inietta in memoria tramite kmem Pierre Falda [email protected] // 03 SUCKIT ed 0d dc ba Pierre Falda [email protected] badc0ded 04// Cenni sul rilevamento Pierre Falda [email protected] // 04 cenni sul rilevamento _Alcuni tool permettevano un’analisi del sistema per cercare modifiche note a zone sensibili del kernel, ma una modifica ‘anomala’ o ben congegnata poteva facilmente ingannarli _Anche una modifica simile però puo essere facilmente rilevata tramite un hash del segmento testo del kernel effettuato da software operante a kspace _Dobbiamo perciò riuscire a modificare il comportamento del kernel senza modificare lo stesso PROGRAMMA: _Kstat Pierre Falda [email protected] // 04 kstat bypass int check_sct() { int kd; char sch_code[100], *buf; kd=open(KMEM, O_RDONLY); printf(”\nLegal sys_call_table should be at 0x%x ...", SYS_CALL_TABLE); kread(kd, sc_addr, sch_code, 100); buf = (char *) memmem(sch_code, 100, ”\xff\x14\x85", 3); sct = *(unsigned *)(buf+3); if(sct == SYS_CALL_TABLE) { printf(" OK!\n"); close(kd); return 0; } else { printf(" WARNING! sys_call_table hijacked!\n\n"); printf("Checking sys_call_table array now at 0x%lx …\n\n\n", sct); close(kd); return 1; } return 0; } Pierre Falda [email protected] // 04 kstat bypass int kread(int des, unsigned long addr, void *buf, int len) { int rlen; if(lseek(des, (off_t)addr, SEEK_SET) == -1) return -1; if((rlen = read(des, buf, len)) != len) return -1; return rlen; } Pierre Falda [email protected] // 04 kstat bypass static unsigned char buffer (100)={0}; long long my_lseek(struct file *target, long long offset,unsigned int origin) { if((unsigned long)offset==FORBIDD) offset=(long long)&buffer; return o_lseek(target,offset,origin); } Pierre Falda [email protected] 05// Caratteristiche abusabili Dell'hardware Pierre Falda [email protected] // 05 abuso dell’hardware _IA-32 fornisce meccanismi per il debug del codice che sono un valido aiuto per il debugging di: - applicativi - software di sistema - sistemi operativi multiprogrammati _Si accede al supporto per il debugging tramite l’utilizzo di 8 Debug Registers (dbr0-dbr7) , dei registri specifici modello (MSRs) e dell’istruzione di breakpoint (int 3 #BP ) DBRs 0-3: contengono l’indirizzo lineare di un breakpoint. Una debug exception (#DB )viene generata quando avviene in tentativo di accesso all’indirizzo del breakpoint DBR 6: riporta le condizioni che erano presenti quando la debug o breakpoint exception è stata generata DBR 7: Specifica le forme di accesso che causeranno la debug exception al raggiungimento del breakpoint Pierre Falda [email protected] // 05 debug registers Pierre Falda [email protected] // 05 debug registers _La modalità di accesso puo indicare: - Break alla sola esecuzione - Break alla sola scrittura - Break alla lettura/scrittura di I/O - Break alla lettura o scrittura di dati _In base a quanto detto fin’ora questo ci permette di far generare una #DB quando la cpu tenta di eseguire del codice posto in una qualsiasi locazione di memoria a nostra scelta, anche a kernel space Pierre Falda [email protected] // 05 gestione delle #DB _La #DB verrà gestita anch’essa tramite il meccanismo dell’idt _In linux la funzione principale atta alla gestione delle #DB, come possiamo vedere dalla trap_init in poi all’interno dei file traps.c ed entry.S, è la fastcall void do_debug(struct *pt_regs, int errorcode) _Allo stato attuale delle cose noi possiamo quindi dirottare qualsiasi flusso di esecuzione del kernel verso la do_debug, senza modificare un singolo bit del segmento testo! Pierre Falda [email protected] // 05 do_debug fun! Per capire bene con cosa abbiamo a che fare diamo uno sguardo al suo argomento principale, la struct pt_regs: struct pt_regs { long ebx; long ecx; long edx; long esi; long edi; long ebp; long eax; int xds; int xes; long orig_eax; long eip; int xcs; long eflags; long esp; int xss; }; Pierre Falda [email protected] // 05 do_debug fun! _Dalla do_debug possiamo quindi avere accesso al valore di eip rappresentante l’indirizzo di ritorno relativo a chi ha fatto scattare il breakpoint, ovvero il kernel nel nostro caso, così da poterne modificare a piacimento il flusso di esecuzione una volta terminata la procedura! _Modificando eip possiamo mandare in esecuzione una nostra routine che una volta fatto il ‘lavoro sporco’ si occupi di ripristinare il flusso di esecuzione originario Pierre Falda [email protected] // 05 do_debug fun! eip overwriting do_debug evil routine jump to regular flow debug exception breakpoint execution flow Pierre Falda [email protected] execution flow // 05 do_debug fun! _Tutto questo necessita però del nostro controllo sulla do_debug, il che ci porta a due possibili soluzioni: - dirottamento della stessa - abuso della flessibilità di linux _Dirottarla implicherebbe modificare parte del segmento testo, vediamo perciò come sia possibile ottenere lo stesso risultato senza modificare nulla di rilevabile dalle consuete tecniche Pierre Falda [email protected] 06// Abuso dei meccanismi di linux Pierre Falda [email protected] // 06 abuso dei meccanismi di Linux Diamo un’occhiata alla do_debug: fastcall void __kprobes do_debug(struct pt_regs * regs, long error_code) { unsigned int condition; struct task_struct *tsk = current; get_debugreg(condition, 6); if (notify_die(DIE_DEBUG, "debug", regs, condition, error_code,, SIGTRAP) == NOTIFY_STOP) return; Pierre Falda [email protected] // 06 abuso dei meccanismi di linux static inline int notify_die(enum die_val val, const char *str, struct pt_regs *regs, long err, int trap, int sig) { struct die_args args = { .regs = regs, .str = str, .err = err, .trapnr = trap, .signr = sig }; return notifier_call_chain(&i386die_chain, val, &args); } Pierre Falda [email protected] // 06 abuso dei meccanismi di linux int __kprobes notifier_call_chain(struct notifier_block **n, unsigned long val, void *v) { int ret=NOTIFY_DONE; struct notifier_block *nb = *n; while(nb) { ret=nb->notifier_call(nb,val,v); if(ret&NOTIFY_STOP_MASK) { return ret; } nb=nb->next; } return ret; } Pierre Falda [email protected] // 06 abuso dei meccanismi di linux struct notifier_block { Int (*notifier_call)(struct notifier_block *self, unsigned long, void *); struct notifier_block *next; int priority; }; int register_die_notifier(struct notifier_block *nb) { int err = 0; unsigned long flags; spin_lock_irqsave(&die_notifier_lock, flags); err = notifier_chain_register(&i386die_chain, nb); spin_unlock_irqrestore(&die_notifier_lock, flags); return err; } EXPORT_SYMBOL(register_die_notifier); Pierre Falda [email protected] // 06 abuso dei meccanismi di linux _Iniettare il nostro codice in kmem _Creare una nostra sys call table maligna _Creare un handler maligno per la gestione delle #DB _Registrare il nostro handler tramite la register_die_notifier _Inserire l’indirizzo della syscall_call come breakpoint Pierre Falda [email protected] 07// conclusioni Pierre Falda [email protected] // 07 conclusioni _Possiamo modificare il comportamento di porzioni di codice senza modificarlo _Possiamo iniettare codice nella memoria del kernel senza utilizzare moduli e talvolta senza utilizzare punti di accesso standard come kmem _Questa tecnica si presta molto alla creazione sia di malware sofisticati sia di anti-malware altrettanto potenti PROGRAMMA: _Mood-NT Pierre Falda [email protected] // riferimenti _http://www.antifork.org _http://www.s0ftpj.org _http://www.phrack.org _http://www.invisiblethings.org _http://darkangel.antifork.org/codes.htm Pierre Falda [email protected] …grazie per l’attenzione… domande sui concetti esposti? advances in attacking linux kernel Pierre Falda [email protected]