Copy Link
Add to Bookmark
Report

BFi numero 11 anno 6 file 05 di 15

eZine's profile picture
Published in 
Butchered From Inside
 · 5 years ago

  

==============================================================================
-------------[ BFi numero 11, anno 6 - 23/12/2003 - file 5 di 15 ]------------
==============================================================================


-[ HACKiNG ]------------------------------------------------------------------
---[ ANGEL: THE P0WER T0 PR0TECT - PART I
---[ dev-05, 05/02/2002
-----[ The Sponge <sponge@tiscali.it> - http://www.sikurezza.org/angel


AngeL - The Power to protect
"Ouverture in B minor" part 1
The Sponge <sponge@tiscali.it>

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.

<CODE>
/*
* 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);
</CODE>

i. Inizializzare il sottosistema per gli attacchi host based

<CODE>
host_init();
angel_host_startup_time = jiffies;

angel_log("angel host subsystem initialized");
</CODE>

ii. Inizializzare il sottosistema per gli attacchi network based

<CODE>
net_init();
angel_net_startup_time = jiffies;
angel_log("angel net subsystem initialized");
<CODE>

iii. Inizializzare le entry in /proc/sys/angel e definite in
$HOME/AngeL-0.9.0pre8/include/angel_sysctl.h

<CODE>
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.");
</CODE>

iv. Comunicare al mondo il termine del proprio lavoro

<CODE>
printk(KERN_INFO "[angel] startup complete, host disarmed.");
angel_log("[angel] startup complete, host disarmed.");
return 0;
</CODE>

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".

<CODE>
/*
* 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");
</CODE>

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].

<CODE>
/*
* 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=<your passord here>.\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
</CODE>

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():

<CODE>
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...
*/

}
</CODE>

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.

<CODE>
/*
* 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];
</CODE>

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.

<CODE>
/*
* 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;
</CODE>

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

<CODE>
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");
</CODE>

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.
<ATTACK>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

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;
}
</ATTACK>

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.

<CODE>
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);
}
</CODE>

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.

<CODE>
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));
}
</CODE>

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?"
:)))

<ATTACK>
#include <stdio.h>
#include <stdlib.h>

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;
}
</ATTACK>

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 :)

<CODE>
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;
}
</CODE>
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.

<CODE>
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);
}
</CODE>

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.

<CODE>
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;
}
</CODE>

All'interno di angel_execve() viene testata la presenza di shellcode nel caso
che il programma da eseguire avesse il bit suid asserito.

<CODE>
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]++;
...
}
</CODE>

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.

<CODE>
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;

}
}
</CODE>

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.

<CODE>
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;
}
</CODE>

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 <sponge@tiscali.it>
* Sottosistema contro gli attacchi network based: Aldo Scaccabarozzi
aka axsca <axsca@tiscali.it>


==============================================================================
---------------------------------[ EOF 5/15 ]---------------------------------
==============================================================================

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT