============================================================================== --------------------[ BFi11-dev - file 05 - 05/02/2002 ]---------------------- ============================================================================== -[ DiSCLAiMER ]--------------------------------------------------------------- Tutto il materiale contenuto in BFi ha fini eslusivamente informativi ed educativi. Gli autori di BFi non si riterranno in alcun modo responsabili per danni perpetrati a cose o persone causati dall'uso di codice, programmi, informazioni, tecniche contenuti all'interno della rivista. BFi e' libero e autonomo mezzo di espressione; come noi autori siamo liberi di scrivere BFi, tu sei libero di continuare a leggere oppure di fermarti qui. Pertanto, se ti ritieni offeso dai temi trattati e/o dal modo in cui lo sono, * interrompi immediatamente la lettura e cancella questi file dal tuo computer * . Proseguendo tu, lettore, ti assumi ogni genere di responsabilita` per l'uso che farai delle informazioni contenute in BFi. Si vieta il posting di BFi in newsgroup e la diffusione di *parti* della rivista: distribuite BFi nella sua forma integrale ed originale. ------------------------------------------------------------------------------ -[ HACKiNG ]------------------------------------------------------------------ ---[ ANGEL: THE P0WER T0 PR0TECT - PART I -----[ The Sponge - http://www.sikurezza.org/angel AngeL - The Power to protect "Ouverture in B minor" part 1 The Sponge 0x0 Intro 0x1 Cos'e` AngeL? 0x2 Struttura generale del modulo 0x3 Protezione contro attacchi host based 0x3.1 Fork bombing 0x3.2 Malloc bombing 0x3.3 Sniffing 0x3.4 Buffer overflow & Format bug 0x4 Sviluppi futuri 0x5 Riferimenti 0x0 Intro Era una notte buia e tempestosa e due ardimentosi studendi del Dipartimento di Scienze dell'Informazione dell'Universita` di Milano stavano risolvendo il complicatissimo algoritmo "Scegli la tua tesi di laurea". I nostri due eroi, spinti dalla volonta` di occuparsi di sicurezza, di sviluppare del codice e di farlo sotto Linux si rivolsero speranzosi al docente selezionato per l'incarico di... "relatore". Questi propose ai nostri prodi quella che potremmo definire una mission impossible: inibire le potenzialita` ostili di un qualsiasi host di una rete in maniera tale da renderlo inoffensivo nei confronti dei suoi "vicini di cavo". Se aggiungiamo a tale richiesta il fatto che anche alcuni comportamenti dell'host nel suo contesto locale dovessero essere monitorati per impedire l'epidemia dei cosiddetti "exploit locali", otteniamo un oggetto conosciuto col nome di... [ rullo di tamburi ] AngeL - The power to protect. 0x1 Cos'e` AngeL? AngeL e` un modulo per il kernel di linux, scritto per la famiglia 2.4.x (al momento in cui scrivo l'ultima release stabile e` la 2.4.17 ed il modulo si comporta perfettamente) ed adattato per funzionare coi kernel della classe 2.2.y (y > 15, non abbiamo provato coi kernel precedenti). Il compito di AngeL (il cui soddisfacimento ha fatto in modo che i nostri eroi conseguissero l'oggetto denominato "laurea") e` appunto quello di trasformare un host in un elemento inoffensivo nei confronti di altri host connessi in rete ed allo stesso momento di fornire al kernel strumenti necessari a fare in modo che un utente non privilegiato non possa acquistare il controllo del sistema. In sostanza, mentre di solito pensiamo a difenderci dagli altri host attraverso gingilli come firewall, post scanner detector..., abbiamo pensato di spostare l'attenzione verso la difesa della rete, vista come risorsa, da possibili comportamenti ostili del nostro host. Per modificare cosi` radicalmente il comportamento del kernel e` stato necessario sostituire alcune system call con delle funzioni "wrapper" aventi il compito di decidere, attraverso l'esecuzione di opportuni sanity check, se l'esecuzione di una certa chiamata di sistema poteva essere pericolosa e portare ad una situazione di ostilita` del nostro host. Parallelamente, grazie al sofisticato sistema di firewall interno al kernel, i pacchetti in uscita dalla nostra macchina vengono analizzati per capire se sono l'origine di un attacco verso un altro host oppure se si tratta di traffico reale... nel primo caso i pacchetti vengono ovviamente "droppati" :) Voglio ricordare, se ce ne fosse bisogno, che un processo richiede servizi al kernel attraverso l'interfaccia offerta dalle system call che sono quindi l'unico punto debole che un processo utente non privilegiato puo` utilizzare per un attacco locale o remoto (sia esso un Denial of Service che un exploit). Per chi non avesse ancora capito cosa fa AngeL pensate che il nostro host e` come il lupo della favola di cappuccietto rosso. AngeL e` la sega con cui gli spacchiamo i denti e gli limiamo le unghie trasformando il lupo in un dolce gattone :P Nella fredda giornata in cui sto scrivendo (12.01.2002) AngeL ha una versione stabile [0.8.5] ed una versione di sviluppo [0.9.0pre8]. Io descrivero` la versione in sviluppo riportando occasionalmente pezzi di codice della versione stabile che devo ancora aggiungere al nuovo lavoro. La divisione tra stabile ed instabile segue la notazione del kernel, minor number dispari = codice in fase di sviluppo altrimenti solid rock code :) 0x2 Struttura generale del modulo Premessa: d'ora in poi eseguite questo nella shell del vostro cervello user@brain $ export ANGEL_PATH=$HOME/AngeL-0.9.0pre8/src/module In questa maniera, quando faro` riferimento alla directory $ANGEL_PATH sapete che mi sto riferendo alla dir dove sono contenuti i sorgenti del modulo. Adesso entriamo nel vivo della questione. Il cuore del modulo e` contenuto nel file AAA/main/engine.c. In questo file sono contenute le funzioni init_module e release_module eseguite da insmod in fase di caricamento e di rimozione di angel dalla memoria... bella scoperta! :) Logicamente e` come se il modulo fosse diviso in 3 parti: * una parte principale di inizializzazione e di gestione delle entry in /proc/sys/angel * un sottosistema per ostacolare gli attacchi host based * un sottosistema per ostacolare gli attacchi network based Questi due sottosistemi hanno entrambi un file principale (AAA/host/engine.c e AAA/net/engine.c) dove il sottosistema in questione viene inizializzato, le system call vengono sostituite dai nostri wrapper, viene appeso un hook nella catena di firewalling di netfilter e cose cosi` (ovviamente ci torneremo sopra). Entrambi offrono una coppia di funzioni che permette l'inizializzazione e la rimozione dalla memoria del sottosistema: * angel_host_init(), angel_host_shutdown(); * angel_net_init(), angel_net_shutdown(). Il compito di AAA/main/engine.c e` quindi quello di: 0. Scrivere informazioni inutili, ma che fanno figo!... e anche tener traccia del jiffie in cui il modulo e` partito per poter tenere traccia dell'uptime del modulo. /* * AngeL startup * So the story begin... */ #ifdef IS_KERNEL_4 int __init angel_setup (void) { #endif #ifdef IS_KERNEL_2 int init_module() { #endif angel_startup_time = jiffies; angel_secure_level = ANGEL_DEFAULT_ZONE; printk(KERN_INFO "[angel] v%s build %s ( %s ) is starting up.\n", ANGEL_VERSION, BUILD, CODENAME); angel_log("[angel] v%s build %s ( %s ) is starting up.", ANGEL_VERSION, BUILD, CODENAME); angel_log("secure level setting to default value ( %d )", angel_secure_level); i. Inizializzare il sottosistema per gli attacchi host based host_init(); angel_host_startup_time = jiffies; angel_log("angel host subsystem initialized"); ii. Inizializzare il sottosistema per gli attacchi network based net_init(); angel_net_startup_time = jiffies; angel_log("angel net subsystem initialized"); iii. Inizializzare le entry in /proc/sys/angel e definite in $HOME/AngeL-0.9.0pre8/include/angel_sysctl.h angel_sysctl_hdr = register_sysctl_table((ctl_table *)&angel_sysctl_dir, 0); if (!angel_sysctl_hdr) { angel_log("register_sysctl_table() failed."); return -1; } angel_log("register_sysctl_table() done."); iv. Comunicare al mondo il termine del proprio lavoro printk(KERN_INFO "[angel] startup complete, host disarmed."); angel_log("[angel] startup complete, host disarmed."); return 0; angel_log() e` una funzione ispirata dal modulo Ele0n0ra di Tharkas apparso nello scorso numero di BFi. Ho praticamente tenuto tutta la funzione, salvo renderla piu` leggibile per me e modificando il formato della data e ora del logging che ora appare come quello di syslog. Soffre di un bug un po' noioso. Non sa dell'esistenza dell'ora legale, anche perche` il giorno dell'anno viene recuperato dai secondi ritornati dalla time() e dovrei vedere se in mezzo al kernel c'e` qualche flag che mi puo` aiutare (AAA/common/log.c per i dettagli). Adesso so i piu` maliziosi di voi cosa staranno pensando: "Ma se un attacker, eludendo i controlli host based di AngeL, diventa root puo` rimuovere il modulo con un rmmod e poi far partire un DDOS, un patriot, un missile nucleare e quant'altro!!!" Ed io dico: "Mica vero ciccio" :) Nella versione stabile di AngeL (e nella prossima versione di sviluppo.. argh il tempo e` cosi` tiranno) esiste un sistema di autenticazione tramite password che rende possibile la rimozione del modulo solamente all'amministratore di sistema che ha specificato la password durante il caricamento del modulo stesso come parametro di insmod. Vediamo meglio nel dettaglio il meccanismo di autenticazione di AngeL. Prima dell'inizio di init_module() dichiaro tutte le variabili che mi servono per gestire la password. Come si puo` vedere dai commenti (scritti in inglese per delirii di onnipotenza) AngeL accetta anche come parametro una password gia` cifrata con l'algoritmo MD5, questo in maniera che l'attacker (divenuto root) scorra il .bash_history alla ricerca di come e` stato invocato insmod recuperando la password in chiaro e rendendo vano il nostro lavoro. Non pensavate mica lasciassi la password in chiaro apposta per voi vero? :P Se comunque si sceglie di caricare il modulo specificando una password non cifrata, questa verra` sottoposta all'algoritmo MD5 (lo so che e` solo un algoritmo di hashing e non di cifratura, ma e` veloce ed e` univoco per una coppia di chiavi x e y) e memorizzata nella mia bella variabile "password". /* * From release 0.7.9, you can supply an already md5 signed password as command * line paramenter. You can, of course override this, loading AngeL with the * parameter startup_password = ANGEL_PASS_IS_PLAIN_TEXT */ int startup_password = ANGEL_PASS_IS_PLAIN_TEXT; /* * unlocked says if I supply the right password to "unlock" AngeL. If the * module is "unlocked", its usage count is 0 and then I can remove it using * angel_unload script */ int unlocked = NO; /* * password will contain the "unlock" password. This symbol ( as all AngeL * symbols ) isn't exported so, *nobody* can watch into it * v0.7.x Update * + Accessing to /dev/kmem and looking "deeper and deeper" you can checkout * our "plain text password". To avoid this we sign the password using MD5 * algorithm. */ char *password = NULL; #ifdef MD5_SIGN unsigned char sign[16]; #endif ... MODULE_PARM(password, "s"); MODULE_PARM(locked, "i"); MODULE_PARM(startup_password, "i"); Il lettore disattento di BFi si stara` chiedendo "bene ed ora come ci accedi a quella variabile da userspace per poter rimuovere il modulo?". Mmmh non siete molto attenti oggi :P ma attraverso un device! In pratica io creo un device ( /dev/angel ) ed associo ad esso, come funzione che implementa la scrittura nel device, una routine che controlla quello che l'utente (ovviamente solo root ha permesso di scrittura) ha inserito e, dopo averlo cifrato con l'MD5, lo controlla col valore criptato della password. Se le due stringhe combaciano allora AngeL entra in uno stato SAFE_UNLOCK e il comando rmmod in questo caso rimuove il modulo dal sistema. Associato a /dev/angel c'e` anche una funzione che implementa la lettura dal modulo e che ritorna... EOF. Per i dettagli sulle due funzioni in questione, sbirciate angel_lock.* [per ora c'e` solo nella versione stabile, ma lo mettero` al piu` presto anche nella 0.9.0 senza cambiare una virgola almeno fino a quando non trovo un sistema migliore per proteggere il modulo, probabilmente in AAA/common/lock.c o un nome simile]. /* * Check dei parametri passati ad insmod. * * locked mi dice se e` possibile o meno rimuovere AngeL senza * particolari accorgimenti ( in teoria come avveniva fino alla * versione 0.1.23. * * Ora il comportamento di default e` quello di accettare in input una * password da inserire in un dispositivo creato ad hoc ( /dev/angel ) * e che permettera` di rimuovere il modulo. Questo ci garantisce che, * se qualcuno e` riuscito a compiere un exploit e diventare root, * possa rimuovere AngeL con facilita`. * * Il parametro locked verra` utilizzato fino al termine della fase * di sviluppo poi verra` eliminato in quanto inutile. */ if (locked == YES) { if (password == NULL) { printk(KERN_INFO "[angel]: for security reason you need a password to unlock the module.\n"); printk(KERN_INFO "[angel]: try 'insmod angel.o password=.\n"); return -ANGEL_NO_PASSWORD; } #ifdef MD5_SIGN else { /* * Sign our password */ if (startup_password == ANGEL_PASS_IS_PLAIN_TEXT ) MD5Sign(password, sign); else strncpy(sign, password, 16); strcpy(password, "Nice try, eagle one"); } #endif /* * Registrazione di un device a caratteri ( /dev/angel ) per * permettere a root di inserire la password * */ #ifdef IS_KERNEL_4 lock_register = register_chrdev (lock_dev_major, LOCK_CHAR_DEV, &lock_dev_fops); #endif #ifdef IS_KERNEL_2 lock_register = module_register_chrdev (lock_dev_major, LOCK_CHAR_DEV, &lock_dev_fops); #endif if ( lock_register < 0 ) { printk(KERN_WARNING "[angel]: I can't get this major number %d.\n", lock_dev_major); return lock_register; } if ( lock_dev_major == 0 ) // Dynamic major number forced lock_dev_major = lock_register; /* * In questa maniera il modulo risultera` sempre utilizzato da * qualcuno e non sara` possibile rimuoverlo. * lock_open e close comunque faranno variare il numero degli * utilizzatori del modulo che sara` comunque > 1. * Raggiungera` 0 solo dopo l'inserimento della password di * sblocco e quindi sara` possibile rimuoverlo. */ MOD_INC_USE_COUNT; } #ifndef DEBUG if ( locked == NO ) cleanup_mode = ANGEL_SAFE_CLEANUP; #endif La chiamata a MOD_INC_USE_COUNT dovrebbe gia` fare in modo che rmmod si rifiuti di rimuovere AngeL ma, poiche` la paranoia non e` mai troppa in questi casi, supponendo che un attacker sia in grado di azzerare lo usage counter del modulo via /dev/kmem facilmente, ho aggiunto anche tutto il pappone della password. Ah gia`! Non vi ho detto cosa succede se root (che in questo caso suppongo essere un fake e chissenefrega se si e` dimenticato la passwd) cerca di fare rmmod prima di aver inserito la password corretta. In fase di Makefile recupero l'indirizzo della funzione handle_sysrq() dal kernel (non dal System.map), ipotizzando che almeno quando root compila angel in /boot esista un vmlinuz sano! Associo al puntatore a funzione angel_handle_sysrq tale indirizzo ed eseguo le seguenti operazioni: * sincronizzo i dischi * rimonto i dischi read/only (anche se nella doc. questo corrisponde a smontare i dischi, io questa discrepanza mica l'ho mai capita) * spengo la macchina. Questo pero` funziona solo se il bios (ed il kernel) supportano l'APM e lo spegnimento via software. In caso contrario, poco male, sollevo un kernel panic e blocco il sistema. Ho adottato una filosofia del tipo "meglio non avere affatto l'host piuttosto che avere una sistema di protezione bucato" :) Questo codice e` la parte finale della funzione cleanup_module(): if (cleanup_mode == ANGEL_HANGUP_CLEANUP) { printk(KERN_EMERG "[angel] Unauthorized module removing attempt.\n"); printk(KERN_EMERG "[angel] Emergency shutdown sequence begun.\n"); /* * We can't trust on reboot system call, it may be wrapped to * malicious code by the attacker. We have to shut down the system * to ourselves. */ /* * Many thanks to GG Sullivan that show us how to import * handle_sysrq address from System.map */ /* * Step 1. Syncing devices. */ angel_handle_sysrq('s', NULL, NULL, NULL); /* * Step 2. Remounting all the device READ/ONLY */ angel_handle_sysrq('u', NULL, NULL, NULL); /* * Step 3. Shut off the machine */ angel_handle_sysrq('o', NULL, NULL, NULL); /* * This is for the machine that doesn't support APM. I won't * be able, on that machines, to power off the host using * software functions. In these cases I will raise a kernel * panic, so the host results useless. */ /* * Step 3b. Raise a kernel PaNiC */ panic("AngeL unauthorized removal"); /* * There can be only one... */ } Spero tutti abbiate notato che questo sistema della password e` solo un po' di filo spinato di protezione in piu`, assolutamente non sufficiente se l'attacker e` ben preparato e determinato a far fuori AngeL. Infatti puo` sempre, attraverso /dev/mem e /dev/kmem modificare il valore della password con uno ad hoc (nella paranoia piu` totale supponiamo ovviamente che il cracker trovi facilmente l'indirizzo di memoria di AngeL e della password) oppure addirittura modificare il kernel a suo piacimento senza necessariamente utilizzare moduli. Ho introdotto nella versione 0.9.0 un angel_secure_level che rappresenta la cattiveria con cui AngeL deve reagire ad un tentativo di attacco. In un ipotetico stato di allarme si potrebbe vietare la scrittura in /dev/mem e /dev/kmem oppure filtrarla. Questo porterebbe a possibili problemi con X ma poco importa in un server, vero?!? :) Adesso che abbiamo visto l'inizializzazione di AngeL e come il modulo si cautela da tentativi di rimozione non desiderati, cominciamo a vedere come il modulo difende la macchina da attacchi host based, che mirano cioe` a dare i privilegi di root ed un utente qualsiasi o che mirano a DoS locali. 0x3 Protezione contro attacchi host based Spostiamoci nella directory AAA/host dove sono contenuti tutti i file che compongono il sottosistema di angel contro gli attacchi host based. Il file principale e` il solito engine.c che inizializza tutte le contromisure da prendere e quali test effettuare per accorgersi di un attacco locale. Per attacco locale viene identificato: * un tentativo di exploit (utente non autorizzato che guadagna una shell di root) * un DoS come fork e malloc bombing * lo sniffing Il primo compito effettuato in engine.c e` quello di salvare i puntatori delle system call originali in modo tale da ripristinarne il valore corretto durante la rimozione del modulo, cosi` da avere un sistema funzionante. Durante questa fase di inizializzazione possiamo vedere chiaramente quali chiamate di sistema vengono controllate da AngeL e quindi quali comportamenti del kernel verranno modificati. /* * Puntatori originali delle chiamate a sistema che angel_host ha * dirottato */ hijacked_system_calls.orig_sys_fork = sys_call_table[__NR_fork]; hijacked_system_calls.orig_sys_vfork = sys_call_table[__NR_vfork]; hijacked_system_calls.orig_sys_clone = sys_call_table[__NR_clone]; hijacked_system_calls.orig_sys_kill = sys_call_table[__NR_kill]; hijacked_system_calls.orig_sys_ioctl = sys_call_table[__NR_ioctl]; hijacked_system_calls.orig_sys_socketcall = sys_call_table[__NR_socketcall]; hijacked_system_calls.orig_sys_brk = sys_call_table[__NR_brk]; Hijacked_system_calls non e` altro che una struct dove i campi sono gli indirizzi delle routine originali del kernel che ho raggruppato solo per dare un po' d'ordine al codice. Segue a questo punto l'inizializzazione dei valori di soglia utilizzati per valutare le richieste di allocazione di memoria o di creazione di un nuovo processo e stabilire se esse siano lecite o se portino ad un DoS locale. In seguito, come si legge dal commento nel codice stesso, sara` possibile leggere questi valori direttamente da un file di configurazione, una cosa tipo /etc/angel.conf per intenderci. /* * Questi valori dovranno essere letti dal file di configurazione */ fork_threshold.max_forks_per_user = 100; fork_threshold.max_forks_per_second = 50; memory_threshold.max_brk_dimension=50000000; memory_threshold.max_brk_per_jiffie=5000; Anche se le variabili hanno un nome abbastanza esplicativo torneremo ancora sul loro significato quando parleremo dei DoS locali. I wrapper di AngeL vengono infine installati al posto delle system call originali e da questo momento il sottosistema per la difesa contro gli attacchi host based e` attivo... lo dice anche l'ultima printk ;P sys_call_table[__NR_fork] = angel_sys_fork; sys_call_table[__NR_vfork] = angel_sys_vfork; sys_call_table[__NR_clone] = angel_sys_clone; sys_call_table[__NR_ioctl] = angel_sys_ioctl; sys_call_table[__NR_socketcall] = angel_sys_socketcall; sys_call_table[__NR_brk] = angel_sys_brk; sys_call_table[__NR_execve] = angel_sys_execve; printk(KERN_INFO "[angel] host subsystem is starting up.\n"); Per i curiosi che stanno guardando il codice della 0.9.0pre8 c'e` ancora tanta roba commentata eredita` di quello che e` l'ultima release stabile di AngeL. Abbiate pazienza che quando se ne andra` via la parolina "pre" scompariranno anche tutte le cianfrusaglie di codice che ci sono ancora :) 0x3.1 Fork bombing Entriamo nel vivo. Chi di voi non sa cos'e` un fork bombing alzi la zampetta.. mmmhh solo uno, vabbe` introduzione veloce al problema. #include #include #include int main ( void ) { while ( 1 ) { fork(); // qui potete metterci anche un sleep() se vi piace di piu`, // ma senza l'attacco e` fulmineo } return 0; } Eseguendo questo codice, dopo averlo compilato, la vostra linux box si inchiodera` inesorabilmente appena avrete rilasciato il tasto "Invio". Quello che succede e` che il kernel deve riempire le strutture dati per i nuovi processi, strutture che ben presto crescono in numero e dimensione fino ad esaurire la memoria disponibile. Quindi il sistema dovra` cercare di swappare un po' di memoria per liberare spazio per le nuove tabelle, perdendo tempo per decidere quali pagine scaricare su disco. Inoltre i nostri nuovi processi vogliono andare in esecuzione e quindi lo scheduler ha il suo bel d'affare ad assegnare un po' di CPU ad ognuno; CPU comunque pressoche` monopolizzata dal kernel... in sostanza pressoche` istantaneamente il carico della macchina schizza verso l'alto ed il kernel decide che siamo bambini cattivi da punire e si prende un po' di pausa. Il problema quindi risiede: * nella creazione di un elevato numero di processi che consumano lo spazio per le tabelle dei processi * nell'elevata velocita` con la quale i processi vengono creati che costringe il kernel a fare gli straordinari per gestire l'esecuzione dei processi precedenti e per creare quello corrente Lo sapete vero che ogni figlio creato eseguira` a sua volta il while(1) producendo ulteriori figli lui stesso? Bene, allora potete sbizzarvi ad immaginare quanti cazzo di processi ci saranno in giro dopo un po'. :) Quello che andremo a fare, in soldoni, e` calcolare il numero di processi che questo utente ha creato e il numero di processi che sono stati creati da questo utente negli ultimi 100 jiffies (1 jiffie *dovrebbe* essere un centesimo di secondo, mi ricordo che ho visto una volta sola la definizione sono stato felice e non me ne sono piu` occupato). Se uno di questi due valori supera i valori di soglia fissati in fase di inizializzazione allora questo viene interpretato come un fork bombing ed il processo corrispondente viene ucciso. inline int Is_this_a_fork_bomb() { unsigned long current_time = 0; int forks_in_last_second = 0; int tasks_by_this_user = 0; int invoking_uid; struct task_struct *p; invoking_uid = current->uid; /* if (invoking_uid == 0) return(0); */ current_time = jiffies; /* * Now we cycle through the task_struct linked list and obtain the * number of tasks spawned by the current user (total as well as those * spawned in the last 100 jiffies) */ read_lock(&tasklist_lock); for_each_task(p) { if ((p->uid) == invoking_uid) { tasks_by_this_user++; if ( (current_time - (p->start_time)) <= 100) forks_in_last_second++; } } read_unlock(&tasklist_lock); if ( (tasks_by_this_user >= fork_threshold.max_forks_per_user) ||(forks_in_last_second >= fork_threshold.max_forks_per_second)) return(invoking_uid); else return(0); } Il wrapper per la fork(), praticamente identico a quello per la vfork() e la clone(), non fa altro che valutare il valore restituito da Is_this_a_fork_bomb() e uccidere il processo se questo valore risulta diverso da 0. Neppure root e` esente da questi controlli, appunto per evitare che un fake root decida di inchiodare, per invidia o quant'altro, la nostra macchina. int angel_sys_fork(struct pt_regs regs ) { int forker_uid; HostStats[FORKS]++; if ((forker_uid = Is_this_a_fork_bomb() )) { #ifndef ANGEL_NOLOG printk(KERN_INFO "[angel_host] Possible fork bomb by uid ( %d ). Killed.\n",forker_uid); angel_log("[angel_host] Possible fork bomb by uid ( %d ). Killed.\n",forker_uid); #endif HostStats[FORK_BOMB]++; return(hijacked_system_calls.orig_sys_kill(0,9)); } else return(hijacked_system_calls.orig_sys_fork(regs)); } Tale contromisura si e` dimostrata efficace anche nel caso di sleep tra una fork e l'altra (dopo un po' vengono generati comunque un numero abnorme di processi) anche se non sono mancati casi di *false positive*. Ad esempio sulla macchina che ho sul lavoro con in esecuzione: * l'application server (scritto in java quindi un po' di thread) * qualche gvim * fvwm con X * xmms per la musica * un po' di mozilla a volte poteva succedere (diciamo una volta al mese) che un mozilla in piu` venisse scambiato per un fork bombing. Magari pensero` se esistono metodi migliori per individuare l'attacco. 0x3.2 Malloc bombing Per quanto riguarda il malloc bombing l'attacco e` molto simile al fork bombing. Il seguente codice riesce a bloccare la nostra macchina in quanto la memoria disponibile viene pressoche` esaurita ed il demone kswapd cerca di liberare pagine swappando come un forsennato. Questo demone ben presto monopolizzera` la cpu nel soddisfacimento del suo vano lavoro perche` ben presto la memoria si esaurira` definitivamente, kswapd non sapra` pił come fare per liberare pagine in RAM e ci ritroveremo con l'host bloccato prima ancora che qualcuno abbia avuto il tempo di chiedere "Ehi, qualcuno ha visto la mia copia di BFI?" :))) #include #include int main ( void ) { while ( 1 ) { malloc(); // qui potete metterci anche un sleep() se vi piace di piu`, // ma senza l'attacco e` fulmineo } return 0; } Come nel caso precedente, l'intercettazione dell'attacco si basa su un euristica su quanta memoria ogni processo abbia allocato in quel momento e con quanta rapidita`. Poiche` la funzione Is_this_a_malloc_bomb() non ha nulla di interessante al suo interno, potete spulciarla in AAA/src/module/host/angel_malloc_bomb.c 0x3.3 Sniffing Lo sniffing di per se` non e` un vero e proprio attacco, piuttosto e` un'azione che puo` ledere la privacy di altri utenti connessi al sistema o del traffico in transito sul nostro host e magari diretto altrove. Morale: dobbiamo impedire anche lo sniffing. Essendoci due modi conosciuti (da me :P) per operare tale azione, la contromisura e` abbastanza semplice. Sostanzialmente devo impedire che un utente (anche se solo root ha questo potere) faccia entrare l'interfaccia di rete in modalita` promiscua attraverso la system call ioctl(). Nel wrapper a questa chiamata controllero` il tipo di comando che si vuole impartire alla scheda di rete. Se questo comando ha il compito di impostare la modalita` promiscua io lo impedisco. That's all :) int angel_sys_ioctl(int sd, int cmd, unsigned long arg) { struct ifreq *ifr; int ret; /* * We want to set some parameter... */ if (cmd == SIOCSIFFLAGS) { ifr = (struct ifreq *)arg; if (ifr->ifr_flags & IFF_PROMISC) { printk(KERN_INFO "[angel]: %s ( pid %d, uid %d )" " wants to put %s in " "promiscous mode using ioctl().\n" "This may be a sniffing attempt. Killed.\n", current->comm, current->pid, current->uid, ifr->ifr_name); angel_log("[angel]: %s ( pid %d, uid %d )" " wants to put %s in " "promiscous mode using ioctl().\n" "This may be a sniffing attempt. Killed.\n", current->comm, current->pid, current->uid, ifr->ifr_name); // Stats[SNIFFING]++; return -EACCES; } } ret = hijacked_system_calls.orig_sys_ioctl(sd, cmd, arg); return ret; } Come si puo` notare, se il comando e` lecito per AngeL, lascio che sia la ioctl() originale a sbrigare il lavoro vero e proprio. La seconda maniera per operare lo sniffing e` di aprire una socket di tipo SOCK_PACKET (e famiglia PF_INET) oppure, visto che questa e` una pratica che si usava anni fa, aprire una socket della famiglia PF_PACKET. La socket aperta in questo modo ci da accesso al data link layer dello stack tcp/ip di Linux e possiamo fare quello che vogliamo coi nostri bei datagrammi in transito. Ovviamente una socket di tipo packet la puo` aprire solo root, ma noi non facciamo sconti a nessuno, veor?!? :) Il lato negativo della faccenda e` che programmini carini come tcpdump, ethereal, nmap (perfettamente leciti per compiti di amministrazione) non si possono usare. Il mio cervello sta lavorando ad un workaround per questo... vedro` cosa posso fare. Per chi non lo sapesse, la nostra amata socket() non e` una system call ma, come tutte le chiamate per la gestione delle socket (dovrebbero inventare un sinonimo carino per questo termine) anche essa fa a capo alla system call socket_call. Questa funge da multiplexer per tutte le funzioni socket-related e provvedera` a chiamare la routine opportuna a seconda di un parametro passatole che identifica il servizio richiesto dall'utente. int angel_sys_socketcall(int call, unsigned long *args) { ... /* * I'm interested just in socket() system call... */ if (call == SYS_SOCKET) { ... if ((a0 == PF_INET) && (a1 == SOCK_PACKET)) { printk(KERN_INFO "[angel]: %s wants to use an obsolete" " (PF_INET, SOCK_PACKET) socket.\n", current->comm); printk(KERN_INFO "[angel]: This should be a sniffing " "attempt. Killed.\n"); return -EACCES; } if (a0 == PF_PACKET) { printk(KERN_INFO "[angel]: %s wants to open a PF_PACKET" " socket.\nThis is a sniffing attempt." " Killed.\n", current->comm); return -EACCES; } } return hijacked_system_calls.orig_sys_socketcall(call, args); } Niente di magico o particolarmente complicato. In questa maniera *nessuno* da userspace puo` recuperare i pacchetti che stanno transitando sulla mia scheda di rete. 0x3.4 Buffer overflow & Format bug Molti nel campo della computer security (che nome buffo, me li immagino i computer che fanno i buttafuori :P) sono concordi nell'affermare che il buffer overflow e` uno degli attacchi piu` temuti e verso il quale molte soluzioni si dimostrano ben poco efficaci. Il format bug, salito agli onori della cronoca negli ultimi anni, si e` affiancato al buffer overflow come attacco locale dall'elevata pericolosita`. In questo articolo non entreremo nel dettaglio su come realizzare un buffer overflow o un format bug, accenneremo al problema quel tanto che basta per capire la routine di AngeL che sostituisce la system call execve(). In rete ci sono centinaia di documenti che entrano nel dettaglio, molto meglio di quanto potrei fare io, su queste due tipologie d'attacco: il primo, almeno per quanto riguarda il buffer overflow, e` il classico "Smashing the stack for fun & profit" di Aleph One (phrack n.49 www.phrack.org). Buffer overflow - Prendiamo il nostro programma 'foo', di proprieta` dell'utente root, ed eseguibile da tutti in quanto utility di sistema. Il programma foo ha bisogno di accedere a /dev/ttyS0 durante la sua esecuzione. Purtroppo pero` un utente non privilegiato potrebbe non avere i permessi per aprire in lettura/scrittura questo device. L'amministratore imposta quindi il bit 'suid' in maniera tale che *qualsiasi* utente lanci il programma foo in esecuzione, il processo generato acquisti i privilegi del proprietario del file 'foo', cioe` root nel nostro scenario. Adesso l'utente sponge lancia foo passandogli come input 10000 caratteri. Il programma foo, per un errore di implementazione (sarebbe meglio parlare di leggerezza del programmatore), non controlla la dimensione dell'input prima di copiarlo nel suo bel buffer di 256 caratteri. Risultato: segmentation fault (core dumped). L'utente sponge, che e` smaliziato e non vuole usare foo ma vuole acquisire i privilegi di root, capisce che foo potrebbe essere attaccato con un buffer overflow e si ingegna per riempire l'input in maniera opportuna (iniettando in un punto specifico dello stesso uno shellcode) e che faccia in modo che foo, nel momento di leggere l'input, sovrascriva zone importanti del processo in esecuzione. Quello che sponge vuole ottenere e` la sovrascrittura del return address di una funzione di foo con l'indirizzo dello shellcode in maniera tale che sia foo stesso ad aprire una shell all'utente sponge. Ma foo gira coi privilegi di root... e quindi ta-da, anche la shell ottenuta avra` i privilegi di root. Attacco riuscito. L'approccio utilizzato per contrastare questo attacco e` stato quello di una ricerca euristica sui parametri che un processo vuole passare ad un secondo programma, avente il bit suid asserito, al momento di una richiesta di esecuzione. Tale ricerca andra` estesa anche alle variabili d'ambiente per evitare che lo shellcode venga messo in una variabile d'ambiente nota (ad esempio $TERM) ed usata dal programma vittima. Molti storceranno il naso alla parola euristica, in effetti la bonta` di questo metodo si appoggia molto sul numero di shellcode conosciuti. Nella versione 0.8.5, l'ultima release stabile, sono riconosciuti 23 shellcode differenti che rendono vani buona parte degli attacchi esistenti (diciamo che il 90% degli exploit che abbiamo utilizzato per testare il codice compilati cosi` com'erano fallivano e necessitavano di modifiche non banali per portare a termine l'attacco). Sto cercando di spostare la contromisura da un approccio di tipo pattern matching ad uno in cui viene individuata una caratteristica comune ad ogni shellcode e che mi permetta di discriminare i tentativi d'attacco da richieste lecite. Questa e` la funzione che confronta ogni parametro ed ogni variabile d'ambiente con i valori degli shellcode in archivio. Il vettore execcodes contiene gli stessi pattern di shellcodes salvo che per gli ultimi caratteri /bin/sh. Questo per intercettare tentativi di esecuzione di altri programmi. int angel_sully_bof2(const char *param) { int i=0; // for (i=0;shellcodes[i]!=NULL;i++) { for (i=0;i<=5;i++) { /* * Il numero di shellcode sara` sempre = a quello degli exec * code, quindi posso permettermi di fare i controlli in un * unico ciclo. */ #ifdef PARANOIC_SCAN if (strlen(param)>=strlen(shellcodes[i])) { if (strstr(param, shellcodes[i])!= NULL) { return ANGEL_BUFF_OVERFLOW; } } #endif if (strlen(param)>=strlen(execcodes[i])) { //if (strstr(param, execcodes[i])!= NULL) { if (strcmp(param, execcodes[i])!= 0) { return ANGEL_BUFF_OVERFLOW; } } } return ANGEL_SAFE_EXEC; } All'interno di angel_execve() viene testata la presenza di shellcode nel caso che il programma da eseguire avesse il bit suid asserito. if (original_file.st_mode & S_ISUID) { argv = (char **) regs.ecx; get_user (ar, ++argv); while (*argv && ar != NULL) { if (IS_ERR (tmp = getname (*argv))) break; get_user (ar, ++argv); putname (tmp); strcpy(ExecveMemory.user_env_copy, tmp); if (angel_sully_bof2(ExecveMemory.user_env_copy) == ANGEL_BUFF_OVERFLOW) { printk(KERN_CRIT "[angel]: Possible buffer overflow. Killed.\n" "[angel]: Exploint against %s by %s ( pid = %d ). Uid is %d.\n", filename, current->comm, current->pid, current->uid); HostStats[BUFFEROVERFLOW]++; ... } Per prevenire che un programma vittima sia gia' in esecuzione ed esegua solo in un secondo momento una shell (ad esempio perche' uno shellcode non e` stato intercettato), limito i privilegi dei processi lanciati in esecuzione (nel caso di shell) rimuovendo il bit suid "al volo". Quello che in sintesi viene fatto e` l'impostazione dell'effective uid al valore del real uid nel caso che un processo suid voglia, ad un certo punto eseguire una shell che quindi ereditera' i privilegi dell'utente che ha lanciato il programma e non del proprietario del file. if ((current->uid!=current->euid) && (current->euid == 0)) { if (strstr(filename, "/bin/sh") != NULL ) { current->euid = current->uid; modified_bit = MODIFIED_SUID; HostStats[SUID_DOWN]++; } } if ((current->gid!=current->egid) && (current->egid == 0)) { if (strstr(filename, "/bin/sh") != NULL ) { current->egid = current->gid; if ( modified_bit == 0) modified_bit = MODIFIED_SGID; else modified_bit = MODIFIED_BOTH; } } Format bug - Il problema del format bug e` ancora pił insidioso ed e` legato al fatto che, in programmi scritti male, un input del tipo "%d %x %p" possa essere interpretato dalle funzioni della famiglia printf() non come dato bensi` come primitiva di formattazione. Supponiamo che volessi stampare una strina, ma al posto di printf("%s\n", input) io chiamassi la stampa cosi` per velocizzare il mio compito o solo perche` sono pigro: printf(input). Succede che, se input contiene delle primitive di formattazione, queste vengono interpretate come tali dalla printf che recupera dallo stack del programma (in particolare dagli indirizzi di memoria che seguono la printf) i dati da stampare. Un attacker puo` quindi risalire all'indirizzo del return address di una funzione mediante il recupero di informazioni sullo stack gentilmente offerte dal programma stesso. Se aggiungiamo anche che esiste un'interessante primitiva non molto conosciuta (%n) ma che consente di scrivere in memoria ad un indirizzo dato, possiamo comprendere come un attacker possa iniettare lo shellcode direttamente nello stack con meno problemi riguardanti alla sovrascittura del RET rispetto all'attacco basato sul buffer overflow. E` infatti lo stesso programma che ci dice dov'e` il RET!!! Quello che faremo in angel_execve e` semplicemente la verifica che tra i parametri del nostro programma suid e tra le sue variabili d'ambiente, non si annidino dei valori contenenti direttive di formattazione note per la printf(). Tali direttive non rappresentanto input validi per nessuna applicazione e quindi posso ipotizzare, in loro presenza, che si tratti di un tentativo di attacco. Quello che segue e` il codice per decidere se una stringa e` una direttiva di formattazione possibilmente pericolosa... nulla di particolare. int isformat (char *tmp, unsigned char is_env) { int ret = 0; if (tmp[0]=='%') { switch (tmp[1]) { case 'd': case 'u': case 'o': case 'x': case 'f': case 'e': case 'c': ret = 1; break; case 's': if (is_env == NO) ret= 1; break; case 'l': switch (tmp[2]) { case 'd': case 'u': case 'o': case 'x': case 'f': case 'e': case 'c': ret = 1; break; } break; case '.': if (isdigit(tmp[2])) { switch (tmp[3]) { case 'd': case 'u': case 'o': case 'x': case 'f': case 'e': case 'c': ret = 1; break; case 's': if (is_env == NO) ret= 1; break; case 'l': switch (tmp[2]) { case 'd': case 'u': case 'o': case 'x': case 'f': case 'e': case 'c': ret = 1; break; } break; default: ret = 0; break; } } break; } } return ret; } Il test sul parametro vengono effettuati assieme a quelli per il buffer overflow e non rappresentano nulla di incredibilmente interessante. 0x4 Sviluppi futuri Here we are. Abbiamo chiacchierato un po' su AngeL e spero di non avervi annoiato troppo. Spero inoltre che vi sia venuta un po' di curiosita` e che scarichiate AngeL per testarlo, sputarci sopra, codare un po' e cose del genere. Possibili sviluppi futuri, almeno per la parte host based, sono senza dubbio un migliore approccio contro il problema del buffer overflow sostituendo la ricerca euristica con qualcosa di meno dipendente dagli shellcode conosciuti o gia` incontrati. Vanno gestiti tentativi di attacco diretto contro AngeL via entry in /proc/sys o via /dev/mem. Per evitare falsi positivi nel caso di DoS locali, e` allo studio un tool semplice che misurera` la velocita` massima di fork e di brk per l'host in questione ed impostera` le entry opportune in maniera tale che le operazioni compiute dal un processore molto veloce vengano interpretate come attacco. Vorrei migliorare il numero di tool di supporto ed ampliare la quantita` di attacchi intercettati, nonche' la documentazione ed il numero di persone coinvolte nel progetto AngeL. Ricordando a tutti che la security e` uno "state of mind" e che per quanto buoni siano i vostri strumenti la sicurezza al 100% non esiste (a patto di non tenere spento il PC o staccato dalla rete, senza dischetti ne' tastiere), vi do appuntamento alla seconda parte del tour su AngeL che vertera` sugli attacchi network based. Stay tuned for more happy days. 0x5 Riferimenti * AngeL web site: www.sikurezza.org/angel * Sottosistema contro gli attacchi host based: Paolo Perego aka TheSponge * Sottosistema contro gli attacchi network based: Aldo Scaccabarozzi aka axsca -[ WEB ]---------------------------------------------------------------------- http://www.bfi.cx http://www.s0ftpj.org/bfi/ http://bfi.itapac.net -[ E-MAiL ]------------------------------------------------------------------- bfi@s0ftpj.org -[ PGP ]---------------------------------------------------------------------- -----BEGIN PGP PUBLIC KEY BLOCK----- Version: 2.6.3i mQENAzZsSu8AAAEIAM5FrActPz32W1AbxJ/LDG7bB371rhB1aG7/AzDEkXH67nni DrMRyP+0u4tCTGizOGof0s/YDm2hH4jh+aGO9djJBzIEU8p1dvY677uw6oVCM374 nkjbyDjvBeuJVooKo+J6yGZuUq7jVgBKsR0uklfe5/0TUXsVva9b1pBfxqynK5OO lQGJuq7g79jTSTqsa0mbFFxAlFq5GZmL+fnZdjWGI0c2pZrz+Tdj2+Ic3dl9dWax iuy9Bp4Bq+H0mpCmnvwTMVdS2c+99s9unfnbzGvO6KqiwZzIWU9pQeK+v7W6vPa3 TbGHwwH4iaAWQH0mm7v+KdpMzqUPucgvfugfx+kABRO0FUJmSTk4IDxiZmk5OEB1 c2EubmV0PokBFQMFEDZsSu+5yC9+6B/H6QEBb6EIAMRP40T7m4Y1arNkj5enWC/b a6M4oog42xr9UHOd8X2cOBBNB8qTe+dhBIhPX0fDJnnCr0WuEQ+eiw0YHJKyk5ql GB/UkRH/hR4IpA0alUUjEYjTqL5HZmW9phMA9xiTAqoNhmXaIh7MVaYmcxhXwoOo WYOaYoklxxA5qZxOwIXRxlmaN48SKsQuPrSrHwTdKxd+qB7QDU83h8nQ7dB4MAse gDvMUdspekxAX8XBikXLvVuT0ai4xd8o8owWNR5fQAsNkbrdjOUWrOs0dbFx2K9J l3XqeKl3XEgLvVG8JyhloKl65h9rUyw6Ek5hvb5ROuyS/lAGGWvxv2YJrN8ABLo= =o7CG -----END PGP PUBLIC KEY BLOCK----- ============================================================================== -----------------------------------[ EOF ]------------------------------------ ==============================================================================