Tracciamento di eventi a livello di sistema operativo e
Transcript
Tracciamento di eventi a livello di sistema operativo e
Università degli studi di Genova Facoltà di Scienze Matematiche, Fisiche e Naturali Corso di Laurea in Informatica Anno accademico 2008/2009 Prova finale Tracciamento di eventi a livello di sistema operativo e applicazioni alla sicurezza Candidato: Emanuele Rocca Relatore: Prof. Giuseppe Ciaccio 9 ottobre 2009 Indice 1 Introduzione 3 1.1 Tracing dinamico a livello di sistema operativo . . . . . . . . 3 1.2 Applicazioni alla sicurezza . . . . . . . . . . . . . . . . . . . . 4 2 SystemTap 5 2.1 Architettura . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 2.2 Il linguaggio . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.2.1 Tipi di script . . . . . . . . . . . . . . . . . . . . . . . 7 2.2.2 Definizione di nuove sonde . . . . . . . . . . . . . . . . 7 2.2.3 Definizione di funzioni . . . . . . . . . . . . . . . . . . 8 2.2.4 Statistiche . . . . . . . . . . . . . . . . . . . . . . . . . 8 Affidabilità e sicurezza . . . . . . . . . . . . . . . . . . . . . . 9 2.3.1 Memoria, puntatori NULL e divisione per zero . . . . 9 2.3.2 Cicli infiniti e ricorsione . . . . . . . . . . . . . . . . . 9 2.3.3 Carico eccessivo 9 2.3 . . . . . . . . . . . . . . . . . . . . . 3 Tapset per i portscan 10 3.1 Scansioni di tipo NULL, FIN e XMAS . . . . . . . . . . . . . 10 3.2 Scansioni TCP Connect . . . . . . . . . . . . . . . . . . . . . 11 3.2.1 Funzioni ausiliarie . . . . . . . . . . . . . . . . . . . . 11 3.2.2 Tenere traccia dei socket . . . . . . . . . . . . . . . . . 12 3.2.3 Individuazione dei socket senza traffico . . . . . . . . . 12 4 Anomaly Based Intrusion Detection Systems 14 4.1 Analisi dei payload . . . . . . . . . . . . . . . . . . . . . . . . 14 4.2 Analisi delle sequenze di chiamate di sistema . . . . . . . . . 14 1 5 Implementazione di un Anomaly Based IDS 5.1 5.2 5.3 15 Generazione sequenze di chiamate di sistema . . . . . . . . . 15 5.1.1 Scelta della lunghezza . . . . . . . . . . . . . . . . . . 15 5.1.2 Generare le sequenze con SystemTap . . . . . . . . . . 16 Il database dei comportamenti normali . . . . . . . . . . . . . 18 5.2.1 Generazione sequenze uniche . . . . . . . . . . . . . . 18 5.2.2 Serializzazione . . . . . . . . . . . . . . . . . . . . . . 20 Individuazione di comportamenti anomali . . . . . . . . . . . 21 5.3.1 22 Confronto algoritmi per il calcolo della distanza . . . . 6 Valutazione del sistema proposto 23 6.1 Impatto sulle performance del sistema operativo . . . . . . . 23 6.2 Riconoscimento di comportamenti anomali . . . . . . . . . . . 24 6.2.1 Individuazione di rootkit . . . . . . . . . . . . . . . . 25 6.2.2 Scansioni con Nessus . . . . . . . . . . . . . . . . . . . 25 6.2.3 Falsi positivi . . . . . . . . . . . . . . . . . . . . . . . 26 7 Conclusioni 27 2 1 Introduzione I sistemi di tracciamento dinamico del funzionamento dei sistemi operativi consentono di analizzare in maniera approfondita il comportamento di un sistema in esecuzione. Tali sistemi di analisi vengono usati principalmente da sviluppatori software e sistemisti, ma si rivelano utili anche per facilitare il debugging agli sviluppatori dei sistemi operativi stessi. Sono inoltre molto interessanti dal punto di vista didattico, permettendo di studiare e osservare “dal vivo” il funzionamento e l’interazione dei vari sottosistemi di un kernel. In questa prova finale si cercherà di mostrare come la flessibilità di strumenti di questo tipo consenta in maniera relativamente semplice di identificare possibili tentativi di intrusione, sia tramite sonde sviluppate al fine di individurare particolari tipi di portscan, sia mediante analisi delle anomalie. La storia dei sistemi di tracing dinamico è assolutamente recente e di conseguenza molto breve: tra i primi software di rilievo, infatti, ricordiamo DTrace di Sun Microsystems, presentato per la prima volta alla USENIX 2004 Annual Technical Conference [1] e usato largamente da sistemisti e sviluppatori Unix su Sun Solaris e Mac OS X. Da subito DTrace ha dimostrato di essere un software molto interessante, al punto di venire spesso citato tra le caratteristiche principali di Sun Solaris. SystemTap, un software analogo per il kernel Linux, è stato presentato nel 2005 all’Ottawa Linux Symposium [2] da Red Hat, IBM, e Intel. La principale ragione per cui non è stato possibile effettuare il porting di DTrace a Linux, ma si è scelto piuttosto di sviluppare un nuovo sistema, è di carattere legale. Nonostante DTrace sia a tutti gli effetti software libero, è stato rilasciato secondo i termini della licenza CDDL1 , incompatibile con la licenza GNU GPL2 del kernel Linux. 1.1 Tracing dinamico a livello di sistema operativo I tradizionali strumenti di analisi delle performance sono orientati allo sviluppatore, e dunque agli ambienti di sviluppo e testing, piuttosto che ai sistemi in produzione. Spesso però le differenze di comportamento tra un sistema in fase di test ed uno in produzione, vuoi per via di configurazioni 1 2 Common Development and Distribution License General Public License 3 non perfettamente replicabili o per tipologie di utilizzo impreviste rendono desiderabile l’uso di un sistema di analisi preciso e senza “effetti collaterali”, che siano di tipo prestazionale o di affidabilità. Per consentire l’analisi del comportamento di un sistema in produzione sono necessari strumenti con le seguenti prerogative: • Nessuna influenza sul sistema quando disabilitati • Semplicità nell’estrazione ed elaborazione dei dati raccolti • Massima sicurezza durante l’esecuzione L’utilizzo di sonde inserite staticamente nel kernel, approccio usato per esempio dal Linux Trace Toolkit, influenza il comportamento del sistema operativo anche quando il sistema di analisi non è in uso; di contro, la possibilità di avvalersi di sonde caricabili dinamicamente consente di non influire sul lavoro del sistema nel momento in cui non fosse necessario usare lo strumento di analisi. Per permettere di estrarre ed elaborare con semplicità i dati di interesse, il sistema di tracing dinamico deve fornire un apposito linguaggio di scripting ad alto livello e mascherare in qualche modo i dettagli implementativi del sistema operativo agli utenti, che non necessariamente sono esperti del kernel. Il sistema di tracing deve inoltre garantire un elevato grado di sicurezza: l’utilizzo del sistema da parte di un utente non deve portare a malfunzionamenti, per esempio per via di cicli infiniti o divisioni per zero. 1.2 Applicazioni alla sicurezza I sistemi atti a rilevare tentativi di intrusione, noti come Intrusion Detection System, o IDS, possono essere di rete oppure host-based. La possibilità di analizzare nel dettaglio il comportamento di un kernel ci consente di sviluppare un prototipo di IDS host-based “ibrido”. Useremo infatti un approccio basato sulla conoscenza del comportamento di alcuni eventi malevoli, nella fattispecie dei portscan, ma proveremo anche a seguire l’approccio basato sull’analisi delle anomalie, osservando le sequenze di chiamate di sistema eseguite dai vari programmi, come dettagliato nelle sezioni 3 e 4.2. 4 Figura 1: Architettura ad alto livello di SystemTap 2 SystemTap L’uso di SystemTap è relativamente semplice, richiedendo all’utente solo di imparare un linguaggio di scripting ad alto livello, trattato nel dettaglio nella sezione 2.2. Per poter lavorare con il sistema operativo, però, gli script debbono essere in qualche modo trasformati in moduli del kernel, che andranno caricati e che dovranno interagire con lo spazio utente. L’architettura di SystemTap, spiegata nella sezione che segue, è fortemente influenzata da queste considerazioni. 2.1 Architettura SystemTap è formato da due componenti principali: un front-end chiamato stap che si occupa di produrre un modulo del kernel a partire da uno script SystemTap, ed un back-end chiamato staprun che carica il modulo, estrae l’output dallo spazio kernel e infine rimuove il modulo dalla memoria. 5 Questo tipo di separazione tra la fase di compilazione e quella di esecuzione consente di compilare gli script su una macchina di sviluppo con installate le informazioni di debug e le librerie necessarie. Il modulo cosı̀ prodotto può essere eseguito su una sistema di produzione, senza doverne cambiare la configurazione o installare software addizionale. L’organizzazione architetturale di SystemTap può essere vista come una pipeline [5] organizzata nelle seguenti fasi: • parse • elaborate • translate • build • load/run • extract output/unload Tra le componenti principali del front-end vi è il cosiddetto translator che si occupa dei primi tre stadi: durante la fase di parse viene analizzato lo script in input, elaborato durante la fase elaborate, e infine tradotto in un programma C durante la terza fase, detta translate [5]. Il programma C cosı̀ prodotto viene compilato durante lo stadio di build, producendo un modulo del kernel e concludendo l’attività per quanto riguarda il front-end. Il back-end si occupa a questo punto di caricare il modulo del kernel nella fase di load/run; nella fase detta extract output il back-end copia dallo spazio kernel i dati da mandare in output su file temporanei, sino a quando l’utente non decide di terminare l’esecuzione o non si verificano errori a run-time quali per esempio il superamento del numero massimo di iterazioni consentite o una divisione per zero3 . Sia in caso di errore che in caso di conclusione con successo, l’ultima operazione compiuta dal back-end è quella di rimozione (fase di unload ) del modulo dalla memoria. 3 Per maggiori dettagli sulle limitazioni imposte da SystemTap per evitare di incorrere in problemi di affidabilità o di sicurezza si veda la sezione 2.3 6 2.2 Il linguaggio Il linguaggio di scripting di SystemTap è fortemente tipato, procedurale, non richiede di dichiarare le variabili ed è ispirato ad awk e dtrace [8]. I due costrutti principali sono sonde e funzioni. Le sonde consentono di identificare il verificarsi di particolari eventi del kernel, quali per esempio la chiamata di una determinata syscall o l’ingresso in una particolare funzione del kernel. Mediante le funzioni è invece possibile scrivere codice modulare e riusabile. 2.2.1 Tipi di script Gli script SystemTap possono essere di due tipi: tapset e probe. I tapset sono librerie di funzioni e alias di sonde che consentono di scrivere con più facilità gli script probe, che sono script SystemTap prodotti dall’utente per raggiungere un determinato scopo. Per esempio, come si vedrà nei capitoli successivi, nello svolgere questa prova finale è stato prodotto un tapset chiamato portscan.stp contenente varie funzioni e alias di sonde per individuare alcune categorie di portscan e un probe script che si avvale di questo e molti altri tapset forniti con SystemTap4 per raccogliere dati circa possibili intrusioni e comportamenti anomali. 2.2.2 Definizione di nuove sonde Per definire una nuova sonda è necessario dichiararla come alias di una o più sonde esistenti. In questo modo, al trigger della (o delle) sonde cui si fa riferimento verrà invocata anche quella in via di definizione, che potrà leggere dati, modificarli e renderli disponibili all’utente come meglio crede. A titolo d’esempio, il seguente probe alias definisce la sonda udp.sendmsg, che viene invocata al momento della chiamata di udp_sendmsg, funzione del kernel responsabile dell’invio di un datagramma UDP. /∗∗ ∗ probe udp . sendmsg − Sending a UDP message ∗ @name : Name o f t h i s probe ∗ @sock : Network s o c k e t 4 Tra gli altri: io.stp per l’input/output, networking.stp per gli eventi relativi alla rete, tcp.stp per l’analisi dei pacchetti TCP 7 ∗ @ s i z e : Number o f b y t e s t o send ∗ ∗ Context : ∗ The p r o c e s s which s e n d s a udp message ∗/ probe udp . sendmsg = k e r n e l . f u n c t i o n ( ” udp sendmsg ” ) { name = ”udp . sendmsg ” sock = $sk size = $len } Tra i parametri con cui viene chiamata la funzione udp_sendmsg troviamo il socket sk e il numero intero size, corrispondente al numero di byte da inviare. La semplice sonda udp.sendmsg esporta queste variabili rispettivamente col nome di sock e size. 2.2.3 Definizione di funzioni Le funzioni sono definibili mediante la parola chiave function; non è necessario specificare il tipo del valore di ritorno o dei parametri, come si può notare dall’esempio seguente: function fibonacci ( i ) { i f ( i < 1) return 0 e l s e i f ( i < 2) return 1 e l s e r e t u r n f i b o n a c c i ( i −1) + f i b o n a c c i ( i −2) } 2.2.4 Statistiche Per uno strumento di analisi è molto importante rendere semplice la raccolta di statistiche in una maniera che eviti il lock esclusivo delle variabili globali atte a contenere i valori collezionati. Per questa ragione SystemTap fornisce un operatore speciale per la raccolta dei valori e varie funzioni per elaborare i dati di interesse. L’operatore di aggregazione è <<< ed un esempio del suo utilizzo è il seguente: probe k e r n e l . f u n c t i o n ( ” k m a l l o c ” ) { k m a l l o c [ execname ( ) ] <<< $ s i z e } 8 2.3 Affidabilità e sicurezza Come già accennato nella sezione 1.1, l’affidabilità e la sicurezza sono tra i requisiti fondamentali di un sistema di tracciamento dinamico a livello kernel. SystemTap prevede vari controlli e limitazioni per evitare disastrosi bug in kernel space. 2.3.1 Memoria, puntatori NULL e divisione per zero Non è possibile esaurire la memoria a disposizione perché SystemTap non consente allocazione dinamica. Gli array vengono allocati durante l’inizializzazione della sonda e ogni tentativo di allocazione troppo dispendioso non consente alla sonda stessa di partire. Inoltre, il numero massimo di elementi in un array è 2048, modificabile mediante il parametro MAXMAPENTRIES. Per quanto concerne l’accesso a puntatori NULL e l’esecuzione di operazioni non valide, in fase di compilazione le operazioni potenzialmente pericolose vengono tradotte in codice C che controlla gli argomenti passati; in fase di esecuzione le eventuali operazioni non valide provocano l’interruzione della sonda e l’emissione di un messaggio di errore. 2.3.2 Cicli infiniti e ricorsione SystemTap rende inoffensive le sequenze di chiamate ricorsive che non “interrompono” la ricorsione, o più in generale i cicli infiniti, mediante due limiti configurabili: il numero massimo di chiamate ricorsive permesse è 10, modificabile tramite il parametro MAXNESTING. Analogamente, MAXACTION limita a 1000 il numero massimo di statement eseguibili per ogni “probe hit”. 2.3.3 Carico eccessivo Per evitare che le sonde possano girare troppo a lungo e dunque impegnare eccessivamente il sistema è stato introdotto un limite al numero di cicli spendibili su tutte le sonde per cpu5 durante un determinato numero di cicli6 . 5 6 STP OVERLOAD THRESHOLD, valore predefinito 500000000 STP OVERLOAD INTERVAL, default a 1000000000 9 Nel caso di sonde che effettivamente necessitino di lunghe computazioni è anche possibile disabilitare del tutto questo tipo di controlli, definiti di overload processing, impostando il parametro STP_NO_OVERLOAD. 3 Tapset per i portscan La comunità ha prodotto un buon numero di tapset orientati ognuno ad un determinanto ambito d’interesse, in modo da rendere più semplice l’utilizzo stesso di SystemTap. Non si può pretendere infatti che gli utenti, per quanto tecnicamente preparati, conoscano cosı̀ nel dettaglio il funzionamento del kernel Linux da sapere quale funzione si occupa di un dato compito. I vari tapset forniti con SystemTap, risparmiando all’utente i dettagli implementativi del kernel, permettono di associare ad un “concetto”, quale ad esempio l’invio di un frame Ethernet, una serie di eventi del kernel. In questo modo, in caso di riorganizzazione del codice del kernel, l’utente non dovrà aggiornare i propri script, visto che la comunità che lavora su SystemTap tiene costantemente aggiornati i tapset a corredo. Basandoci sul tapset che si occupa del protocollo TCP abbiamo prodotto un tapset che fornisce due sonde atte a individuare scansioni NULL, FIX, XMAS e TCP Connect. Queste tipologie di scansione sono un sottoinsieme delle tecniche di portscan adottate dal famoso scanner di rete Nmap. 3.1 Scansioni di tipo NULL, FIN e XMAS Per quanto concerne i primi tre tipi di portscan, l’individuazione tramite SystemTap è banale: alla ricezione di un pacchetto TCP, mediante la sonda tcp.receive, si controllano i flag impostati. Se nessuno dei flag TCP è impostato si tratta di una scansione NULL, se è settato il solo flag FIN si tratta di una FIN scan, se sia FIN che PSH che URG sono impostati si tratta di una scansione di tipo XMAS. La variabile name conterrà il nome del tipo di scansione NULL/FIN/XMAS eventualmente identificata, stringa vuota altrimenti. /∗ NULL, FIN and XMAS s c a n s a r e e a s y t o c a t c h ∗/ probe p o r t s c a n . n u l l f i n x m a s = t c p . r e c e i v e { name = ”” i f ( f i n && psh && urg ) name = ”XMAS” ; 10 if ( ! psh && ! urg && ! syn && ! r s t && ! ack ) name = f i n ? ”FIN” : ”NULL” ; i f ( name == ” ” ) next ; } 3.2 Scansioni TCP Connect Riconoscere le scansioni di tipo TCP Connect non è immediato come nel caso affrontato in precedenza: è infatti necessario individurare i socket TCP che vengono chiusi senza che attraverso di essi siano effettivamente transitati dei dati. 3.2.1 Funzioni ausiliarie Come prima cosa ci servono alcune funzioni atte a leggere i dati presenti nella struttura skb, resa disponibile dalla sonda tcp.receive, contenuta nel tapset tcp. La prima delle funzioni che definiamo calcola la lunghezza del payload di un dato pacchetto, la seconda costruisce una stringa di identificazione del socket cui il pacchetto afferisce. function t c p p a y l o a d l e n : l o n g ( skb ) { tcphdr = g e t s k b t c p h d r ( skb ) r e t u r n @cast ( skb , ” s k b u f f ”)−> l e n − @cast ( tcphdr , ” t c p h d r”)−> d o f f ∗ 4 } t c p s o c k s t r : s t r i n g ( skb ) { function tcphdr = g e t s k b t c p h d r ( skb ) dport = t c p s k b d p o r t ( tcphdr ) sport = t c p s k b s p o r t ( tcphdr ) iphdr = g e t s k b i p h d r ( skb ) saddr = ip ntop ( i p s k b s a d d r ( iphdr ) ) daddr = i p n t o p ( i p s k b d a d d r ( i p h d r ) ) r e t u r n s p r i n t f (”% s :%d %s :%d ” , saddr , s p o r t , daddr , d p o r t ) ; } 11 3.2.2 Tenere traccia dei socket Abbiamo bisogno di una struttura dati nella quale immagazzinare il numero di byte di payload effettivamente transitati attraverso ogni socket. Decidiamo di utilizzare un array associativo e come chiave scegliamo la stringa di identificazione costruita come illustrato nella sezione 3.2.1. Aggiorniamo la nostra struttura alla ricezione di pacchetti TCP col flag SYN impostato, prima fase del three-way handshake. global sockets probe k e r n e l . f u n c t i o n ( ” t c p v 4 s y n r e c v s o c k ” ) { t c p p a y l o a d l e n ( $skb ) s o c k e t s [ t c p s o c k s t r ( $skb ) ] = } 3.2.3 Individuazione dei socket senza traffico Alla ricezione di un pacchetto TCP verifichiamo l’esistenza della corrispondente stringa di identificazione del socket. Nel caso in cui la dimensione del payload del pacchetto sia maggiore di 0 cancelliamo il socket corrispondente dalla nostra struttura dati in modo da ignorare questo e i successivi eventi relativi al socket che stiamo controllando. Se il pacchetto sotto esame ha i flag ACK e RST impostati ci troviamo di fronte ad una scansione di tipo TCP connect: il fatto che i due flag siano impostati implica una richiesta di chiusura del socket e tutti gli eventuali pacchetti ricevuti in precedenza avevano dimensione di payload a 0, altrimenti il socket sarebbe stato cancellato dalla struttura dati e gli eventi successivi ignorati. probe p o r t s c a n . t c p c o n n e c t = t c p . r e c e i v e { sockstr = t c p s o c k s t r ( $skb ) i f ( ! ( [ sockstr ] in sockets )) next ; s o c k e t s [ s o c k s t r ] += t c p p a y l o a d l e n ( $skb ) i f ( s o c k e t s [ s o c k s t r ] > 0) { /∗ some data has a c t u a l l y been s e n t on t h i s s o c k e t ∗/ delete sockets [ sockstr ] next ; } i f ( ack && r s t ) { name = ”TCP Connect ” 12 delete sockets [ sockstr ] } else next ; } 13 4 Anomaly Based Intrusion Detection Systems L’approccio fin qui adottato per l’identificazione di possibili scansioni in corso è simile a quello seguito dai cosiddetti Signature Based Intrusion Detection Systems. Tale categoria di IDS confronta il comportamento a runtime del sistema con un database di comportamenti anomali noti: eventuali corrispondenze vengono considerate come possibili attacchi. Tra i punti deboli di questa categoria di IDS annoveriamo l’impossibilità di individuare tutti gli attacchi la cui signature non è presente nel database, la conseguente necessità di mantenere aggiornate le “definizioni” degli attacchi noti e il rischio di non individuare il comportamento di tutti gli exploit volutamente modificati per differire dalle definizioni. Di contro, gli Anomaly Based Intrusion Detection Systems considerano come anomalo ogni comportamento che si discosta in qualche modo da quello normale del sistema; tale categoria di IDS non necessita dunque di un database di pattern anomali, ma piuttosto di una definzione di “comportamento normale”. Per quanto concerne gli IDS di rete il concetto di normalità può essere definito basandosi sull’analisi dei payload dei pacchetti ricevuti, mentre per i cosiddetti Host-based Intrusion Detection System un metodo per discernere tra comportamenti anomali e accettabili è l’analisi delle sequenze di chiamate di sistema. 4.1 Analisi dei payload La strada dell’analisi dei payload dei pacchetti ricevuti è stata seguita da Wang e Stolfo [13]: in una prima fase di addestramento del sistema viene analizzata la dimensione del payload dei pacchetti ricevuti sulle varie porte e ne viene calcolata la deviazione standard. Questi dati vengono comparati con le dimensioni dei payload dei pacchetti ricevuti a runtime e vengono generati degli allarmi al superamento delle soglie di tolleranza impostate. 4.2 Analisi delle sequenze di chiamate di sistema Il prototipo di Intrusion Detection System sviluppato nel corso di questa prova finale si basa sull’analisi di brevi sequenze di chiamate di sistema, approccio teorizzato da Forrest et al [10]. 14 L’idea di fondo è di produrre una sorta di “sistema immunitario” per calcolatori e per farlo è necessario riuscire a distinguere i comportamenti normali da quelli anomali. Il lavoro svolto da Hofmeyr et al [11] dimostra empiricamente la validità dell’uso di sequenze di syscall come discriminante tra comportamenti legittimi e illegittimi di un programma in esecuzione. L’approccio proposto consiste nel memorizzare in un database le sequenze uniche di chiamate di sistema per ogni programma in esecuzione sulla macchina durante un periodo di utilizzo normale del calcolatore. Una volta costruito questo database sarà possibile confrontare le sequenze di syscall eseguite dai vari processi con quelle ritenute normali, alla ricerca di eventuali pattern anomali. 5 Implementazione di un Anomaly Based IDS A livello macroscopico, i due problemi principali da affrontare nell’implementazione di un IDS basato sull’analisi delle sequenze di syscall sono la creazione di un database di comportamenti normali e l’analisi dei comportamenti in fase di esecuzione per riscontrare eventuali anomalie. Come prerequisito per affrontare entrambi i problemi abbiamo individuato lo sviluppo di uno strumento che produca le sequenze di syscall di una certa lunghezza eseguite dai vari programmi; nella nostra implementazione questo strumento è stato realizzato mediante una sonda SystemTap, descritta nel dettaglio nella sezione 5.1.2. L’output di questa sonda viene analizzato da alcuni script Python che si occupano di costruire e rendere persistente il database di sequenze uniche, come spiegato nella sezione 5.2, nonché di scovare eventuali pattern anomali a runtime (5.3). 5.1 5.1.1 Generazione sequenze di chiamate di sistema Scelta della lunghezza La scelta della lunghezza delle sequenze da analizzare è particolarmente delicata: come è ovvio siamo interessati ad avere sequenze il più corte possibile, in modo da diminuire la dimensione del database e la computazione necessaria in fase di esecuzione per identificare i comportamenti anomali. D’altro 15 canto, evidentemente, vogliamo sequenze sufficientemente lunghe da permetterci di caratterizzare correttamente il comportamento dei programmi in esame. Hofmeyr et al [11] concludono che sequenze di dieci syscall sono un buon compromesso tra questi requisiti contrastanti, mentre Warrender et al [12] notano che è possibile ottenere risultati soddisfacenti con sequenze di lunghezza sei, migliorando sensibilmente le performance e diminuendo la dimensione del database rispetto all’analisi di sequenze di lunghezza dieci. Nella nostra implementazione la scelta è lasciata all’utente tramite un parametro di configurazione, il cui valore predefinito è 6: garantendo questa scelta all’utente si rende necessaria l’implementazione di controlli volti ad evitare l’utilizzo di un database di sequenze con lunghezza diversa dal valore scelto per il parametro. Cambiandolo, infatti, si rende necessaria la ricostruzione del database. 5.1.2 Generare le sequenze con SystemTap Eccoci finalmente allo script SystemTap che si occupa di costruire tutte le sequenze di syscall di una data lunghezza divise per eseguibile. Lo presentiamo nella sua interezza, descrivendo poi passo passo le operazioni svolte. global values global occurs probe s y s c a l l . ∗ { e x e c = execname ( ) i f ( e x e c == ” s t a p ” | | e x e c == ” s t a p i o ” ) next ; v a l u e s [ exec , o c c u r s [ e x e c ] ] = name o c c u r s [ e x e c ]++ i f ( o c c u r s [ e x e c ] == $1 ) { p r i n t f (”% s %d ” , exec , u i d ( ) ) f o r ( i =0; i < $1 ; i ++) { p r i n t f ( ” %s ” , v a l u e s [ exec , i ] ) v a l u e s [ exec , i ] = v a l u e s [ exec , i +1] } p r i n t f (”\n ” ) ; o c c u r s [ e x e c ]−− } 16 } Lo script in questione è piuttosto breve ma ci permette di illustrare varie caratteristiche di SystemTap. Gli eventi che vogliamo individuare sono, banalmente, le invocazioni di chiamate di sistema. Il tapset syscalls.stp ci viene in aiuto, esponendo varie sonde quali per esempio syscall.brk, syscall.chmod e cosı̀ via. Attendiamo dunque l’esecuzione di una qualunque chiamata di sistema, assegnando alla variabile exec il nome dell’eseguibile e ignorando le syscall relative agli eseguibili stap e stapio. probe s y s c a l l . ∗ { e x e c = execname ( ) i f ( e x e c == ” s t a p ” | | e x e c == ” s t a p i o ” ) next ; A questo punto memorizziamo nell’array associativo values il nome della chiamata corrente; gli indici di values sono nell’ordine: il nome dell’eseguibile che ha effettuato la determinata syscall e la posizione nella sequenza della stessa. La posizione corrente viene mantenuta, per eseguibile, nell’array occurs. v a l u e s [ exec , o c c u r s [ e x e c ] ] = name o c c u r s [ e x e c ]++ Come si diceva, la lunghezza delle sequenze è un parametro configurabile, che passiamo allo script nella variabile $1: raggiunta la lunghezza desiderata scriviamo su standard output il nome dell’eseguibile, lo user id e la sequenza identificata. Durante l’output della sequenza ci occupiamo inoltre di fare scorrere verso sinistra la finestra. i f ( o c c u r s [ e x e c ] == $1 ) { p r i n t f (”% s %d ” , exec , u i d ( ) ) f o r ( i =0; i < $1 ; i ++) { p r i n t f ( ” %s ” , v a l u e s [ exec , i ] ) v a l u e s [ exec , i ] = v a l u e s [ exec , i +1] } p r i n t f (”\n ” ) ; o c c u r s [ e x e c ]−− } L’output ottenuto è di questo tipo: 17 gnome−t e r m i n a l 1000 g e t t i m e o f d a y p o l l g e t t i m e o f d a y g e t t i m e o f d a y w r i t e p o l l gnome−t e r m i n a l 1000 p o l l g e t t i m e o f d a y g e t t i m e o f d a y w r i t e p o l l p o l l Nell’esempio, la lunghezza della sequenze è stata impostata a 6 e l’eseguibile gnome-terminal ha invocato nell’ordine queste syscall: • gettimeofday • poll • gettimeofday • gettimeofday • write • poll • poll 5.2 Il database dei comportamenti normali Per produrre il database dei comportamenti normali partiamo dunque dal copioso output generato dalla sonda SystemTap di cui al punto 5.1.2, lo analizziamo individuando le sequenze uniche e serializziamo i dati raccolti mediante lo script Python che andiamo a descrivere. 5.2.1 Generazione sequenze uniche Lo script builddb.py si avvale di due moduli per assolvere il suo compito: il primo di questi, reader.py, genera una struttura dati contenente le sequenze uniche divise per eseguibile. La parte fondamentale di questa struttura è il dizionario executables, che contiene i dati veri e propri divisi per eseguibile; per esempio, si può accedere alle sequenze generate da firefox con: executables[’firefox’]. Considerata la mole di dati da analizzare è importante assicurarsi di utilizzare un metodo efficiente per generare l’insieme di sequenze uniche: per questa ragione abbiamo pensato di implementare una classe generica che effettua la lettura dell’input, lasciando a classi più specifiche l’implementazione del 18 metodo che si occupa di verificare se una data sequenza è già presente nel database, cosı̀ come l’inserimento di una sequenza nuova. Il metodo go() analizza ogni linea in input, chiamando il metodo addseq(eseguibile, sequenza) che garantisce l’inserimento nel dizionario executables descritto poc’anzi solo in caso di sequenza sconosciuta. knownseq(eseguibile, sequenza), risulterà utile in fase di esecuzione, come descritto nella sezione 5.3. class SyscallDataReader ( object ) : [ ... ] def go ( s e l f ) : while True : # Not u s i n g r e a d l i n e s ( ) t o a l l o w u n b u f f e r e d i n p u t i n p u t l i n e = s e l f . input . r e a d l i n e () i f not i n p u t l i n e : s e l f . e n d i n g = time . time ( ) break execname , uid , c a l l s = l i n e 2 d a t a ( i n p u t l i n e ) s e l f . addseq ( execname , c a l l s ) def addseq ( s e l f , execname , c a l l s ) : r a i s e NotImplementedError def knownseq ( s e l f , execname , c a l l s ) : r a i s e NotImplementedError Le varie specializzazioni si differenziano a seconda del tipo di struttura dati usata per rappresentare i valori del dizionario executables. L’implementazione mostratasi più efficiente si avvale della struttura dati set, inclusa tra le strutture dati di base di Python. class SetSyscallDataReader ( SyscallDataReader ) : def addseq ( s e l f , execname , c a l l s ) : i f execname not in s e l f . e x e c u t a b l e s : s e l f . e x e c u t a b l e s [ execname ] = s e t ( [ ] ) s e l f . e x e c u t a b l e s [ execname ] . add ( c a l l s ) def knownseq ( s e l f , execname , c a l l s ) : return c a l l s in s e l f . e x e c u t a b l e s [ execname ] 19 Come si può notare, l’implementazione di un “reader” è questione di poche righe, consentendo di sperimentare facilmente vari algoritmi e strutture dati. A titolo d’esempio, la seguente implementazione usa la struttura dati list: class ListSyscallDataReader ( SyscallDataReader ) : def addseq ( s e l f , execname , c a l l s ) : i f execname not in s e l f . e x e c u t a b l e s : s e l f . e x e c u t a b l e s [ execname ] = [ ] i f not s e l f . knownseq ( execname , c a l l s ) : s e l f . e x e c u t a b l e s [ execname ] . append ( c a l l s ) def knownseq ( s e l f , execname , c a l l s ) : return c a l l s in s e l f . e x e c u t a b l e s [ execname ] 5.2.2 Serializzazione dbaccess.py è il secondo modulo utilizzato dallo script per la costruzione del database, e fornisce le funzioni utili a salvare su file e successivamente caricare la struttura dati contenente le sequenze uniche raccolte. import o s import s h u t i l import c P i c k l e import c o n f i g def d b e x i s t s ( ) : return o s . path . i s f i l e ( c o n f i g .FILENAME) def g e t d a t a ( ) : dbf = open ( c o n f i g . FILENAME, ’ r ’ ) r e a d e r = c P i c k l e . l o a d s ( dbf . r e a d ( ) ) dbf . c l o s e ( ) return r e a d e r def putdata ( data ) : if dbexists ( ) : backup = c o n f i g .FILENAME + ” . o l d ” print ” C r e a t i n g backup f i l e ” , backup , ” b e f o r e s a v i n g d a t a b a s e . . . ” , s h u t i l . copy ( c o n f i g . FILENAME, backup ) print ” done . ” # can ’ t p i c k l e f i l e o b j e c t s data . i n p u t = None 20 dbf = open ( c o n f i g . FILENAME, c P i c k l e . dump( data , dbf ) dbf . c l o s e ( ) 5.3 ’wb ’ ) Individuazione di comportamenti anomali Lo script Python responsabile dell’individuazione dei comportamenti anomali è l’ultima componente del nostro sistema. Il suo funzionamento è concettualmente molto semplice: per ogni sequenza in input ne calcola la distanza di Hamming minima rispetto a tutte le sequenze presenti nel database. Se la distanza minima supera il valore configurabile ALLOWED_MISMATCHES, di default impostato a 2, la sequenza viene considerata anomala. Di seguito riportiamo la funzione atta a calcolare la distanza minima di una sequenza rispetto a quelle note: per ogni sequenza nota ne calcoliamo la distanza rispetto a quella in esame, fermandoci se il valore trovato è inferiore o uguale al numero di differenze concesse. In tal caso, infatti, ci troviamo di fronte a una sequenza legittima. def m i n d i s t a n c e ( s e q u e n c e , known seqs , d i s t a n c e=d i s t a n c e x r a n g e ) : minimum = c o n f i g .SEQUENCE LENGTHS f o r known in known seqs : v a l = d i s t a n c e ( known , s e q u e n c e ) i f v a l <= c o n f i g .ALLOWED MISMATCHES: return v a l i f v a l < minimum : minimum = v a l return minimum La funzione distance(), usata per calcolare la distanza di Hamming tra due sequenze, viene passata come parametro in modo da poterne sperimentare facilmente varie implementazioni e valutarne l’efficienza come descritto nella sezione che segue. 21 5.3.1 Confronto algoritmi per il calcolo della distanza Evidentemente, l’operazione eseguita più di frequente a runtime è il calcolo della distanza tra due sequenze; ne segue che la scelta di un buon algoritmo è particolarmente importante. Siamo partiti da una versione molto “idiomatica” dell’algoritmo, ma inefficiente. Il codice che segue itera sugli elementi usando la funzione zip(), che date due sequenze costruisce la lista delle coppie di eguale posizione. Costruisce quindi mediante una list comprehenshion, da cui il nome dell’algoritmo, una lista di booleani che assumeranno valore True in caso di diversità della coppia di elementi e False in caso contrario. A questo punto, la funzione sum() somma gli elementi della lista e considera False uguale a 0 e True uguale a 1, ritornando infine la distanza di Hamming. def d i s t a n c e c o m p r e h e n s i o n ( s1 , s 2 ) : return sum ( [ ch1 != ch2 f o r ch1 , ch2 in z i p ( s1 , s 2 ) ] ) Per provare a migliorare i tempi di esecuzione abbiamo evitato di costruire una lista, iterando su una sequenza mediante la funzione enumerate e contando quante volte gli elementi dell’altra differiscono: def d i s t a n c e e n u m e r a t e ( s1 , s 2 ) : mismatches = 0 f o r idx , elem in enumerate ( s 1 ) : i f elem != s 2 [ i d x ] : mismatches += 1 return mismatches Come ultima inefficienza eliminabile notiamo che la lunghezza della sequenza è a noi nota a priori. Non è dunque necessario usare la funzione enumerate(), che itera su una sequenza ritornando a ogni iterazione indice e valore, ma ci basta andare da 0 ad N, dove N è la lunghezza delle sequenze, tramite la funzione xrange. def d i s t a n c e x r a n g e ( s1 , s 2 ) : mismatches = 0 f o r i d x in xrange ( c o n f i g .SEQUENCE LENGTHS ) : i f s 1 [ i d x ] != s 2 [ i d x ] : mismatches += 1 return mismatches 22 Come benchmark abbiamo scelto di calcolare 100 volte la distanza tra una sequenza del tutto “sbagliata”, perché composta solo da syscall inesistenti, e le sequenze prodotte dagli eseguibili gnome-screensaver e firefox in 4 ore di raccolta dati. Scegliendo una sequenza composta da sole syscall inesistenti valutiamo il comportamento degli algoritmi nel caso peggiore, visto che sarà necessario calcolare la distanza rispetto a tutte le sequenze note. Il benchmark è stato ripetuto 3 volte e di seguito riportiamo il minore dei tempi impiegati nel corso delle esecuzioni. algoritmo comprehension enumerate xrange gnome-screensaver (771) 1.330 s 1.061 s 0.99 s firefox (15558) 28.9 s 23.3 s 21.3 s Concludiamo dunque che, anche nel caso peggiore, la versione dell’algoritmo che utilizza la funzione xrange è da preferirsi. 6 6.1 Valutazione del sistema proposto Impatto sulle performance del sistema operativo Ogni processo può eseguire un numero considerevole di chiamate di sistema al secondo, che vanno organizzate in sequenze di una data lunghezza. L’elaborazione di una mole di dati di questo tipo porta in pochi secondi al superamento delle soglie descritte nel paragrafo 2.3.3. Proviamo a compilare lo script presentato nella sezione 5.1.2 e ad eseguirlo: $ s t a p −p 4 a l l −s e q u e n c e s . s t p −m t e s t a l l s e q u e n c e s . ko 6 $ time s t a p r u n t e s t a l l s e q u e n c e s . ko > /tmp/ s e q u e n c e s ERROR: probe o v er he ad e x c e e d e d t h r e s h o l d WARNING: Number o f e r r o r s : 1 , s k i p p e d p r o b e s : 0 real 4m31 . 7 8 9 s user 0m0. 0 2 8 s s y s 0m1. 3 3 2 s Lo script ha catturato 1671907 sequenze di 6 chiamate di sistema consecutive in circa 4 minuti e mezzo prima di venire fermato dalla logica di controllo dell’overhead. Decisamente troppo poco per riuscire a costruire un database utilizzabile. 23 Compilando invece il modulo con -DSTP_NO_OVERLOAD è possibile disabilitare i controlli sull’overhead, consentendo al nostro script di procedere nell’esecuzione. Ovviamente, però, le operazioni svolte continueranno a essere parecchio impegnative per il sistema: per evitare di renderlo inutilizzabile è sufficiente impostare la priorità di scheduling di staprun a 19, in modo da garantire ai processi utente di lavorare senza intoppi. Si veda, ad esempio, come si è proceduto per lo script che si occupa del controllo a runtime: #! / b i n / sh nice −n 19 s t a p r u n a l l s e q u e n c e s . ko | nice −n 19 python r u n t i m e c h e c k . py Cosı̀ facendo è possibile costruire un database di dimensioni arbitrarie e verificare la presenza di comportamenti anomali senza per questo rendere inutilizzabile la macchina in esame. Ad esempio, in 4 ore siamo stati in grado di costruire un database contenente i dati di 103 eseguibili per un totale di 69974 sequenze uniche. Di seguito riportiamo il numero di sequenze uniche identificate per i dieci eseguibili più “attivi”: eseguibile rhythmbox firefox transmission Xorg evince bash pulseaudio vim gnome-terminal gnome-panel 6.2 sequenze uniche 15622 15558 6987 5368 3306 2067 1745 1425 1327 1069 Riconoscimento di comportamenti anomali Analizziamo, finalmente, il funzionamento del nostro sistema, sia per quanto concerne l’individuazione di attacchi e comportamenti di dubbia natura che dal punto di vista dei falsi positivi, cioè dei comportamenti legittimi erroneamente ritenuti anomali. 24 6.2.1 Individuazione di rootkit Un rootkit è una collezione di programmi che, tra le altre cose, vengono utilizzati da un attaccante che ha ottenuto accesso al sistema per nascondere l’avvenuta intrusione agli amministratori. Abbiamo installato la versione dell’eseguibile netstat presente nel rootkit ark versione 1.0.1 su una macchina protetta dal nostro IDS. Questa versione di netstat è modificata in modo da non mostrare le connessioni in corso che rispettano determinati criteri scelti a piacere dall’attaccante. Come criterio abbiamo scelto di nascondere tutti i server in ascolto sull’indirizzo 127.0.0.1. Ecco l’output prodotto dall’eseguibile netstat originale: ema@mars : ˜ / Desktop / ark − 1 . 0 . 1 $ sudo n e t s t a t . o l d −napt | g r e p 127 tcp 0 0 127.0.0.1:631 0.0.0.0:∗ LISTEN 4294/ cupsd Il server TCP cups è dunque in ascolto sulla porta 631, indirizzo 127.0.0.1 Cercando di ottenere la stessa informazione con l’eseguibile netstat fornito con il rootkit ark non otteniamo invece nessun output, proprio come ci aspettavamo: ema@mars : ˜ / Desktop / ark − 1 . 0 . 1 $ sudo n e t s t a t −napt | g r e p 127 ema@mars : ˜ / Desktop / ark − 1 . 0 . 1 $ Il nostro IDS identifica il comportamento di netstat come potenzialmente malizioso, scovando ben quattro sequenze del tutto anomale (sei syscall su sei): 6 netstat ( ’ rt sigprocmask ’ , ’ fork ’ , ’ r t s i g a c t i o n ’ , ’ r t s i g a c t i o n ’ , ’ r t s i g p r o c m a s k ’ , ’ wait4 ’ ) 6 netstat ( ’ fork ’ , ’ r t s i g a c t i o n ’ , ’ r t s i g a c t i o n ’ , ’ r t s i g p r o c m a s k ’ , ’ wait4 ’ , ’ r t s i g a c t i o n ’ ) 6 netstat ( ’ r t s i g a c t i o n ’ , ’ r t s i g a c t i o n ’ , ’ rt sigprocmask ’ , ’ wait4 ’ , ’ r t s i g a c t i o n ’ , ’ r t s i g a c t i o n ’ ) 6 n e t s t a t ( ’ r t s i g a c t i o n ’ , ’ r t s i g p r o c m a s k ’ , ’ wait4 ’ , ’ r t s i g a c t i o n ’ , ’ r t s i g a c t i o n ’ , ’ rt sigprocmask ’ ) 6.2.2 Scansioni con Nessus Nessus è uno strumento per scoprire vulnerabilità su sistemi remoti, caratterizzato da un gran numero di plugin che consentono di abilitare controlli 25 di vario genere. Alcuni di questi plugin cercano di sfruttare vulnerabilità note di servizi quali OpenSSH e Samba in modo a volte piuttosto invasivo; Nessus è dunque un ottimo banco di prova per il nostro IDS. Il nostro IDS è stato allenato ad un uso molto basilare dei servizi disponibili (Cups, Samba e OpenSSH). Via Cups abbiamo stampato un paio di pagine di prova, Samba è stato usato per la condivisione di uno share Windows e OpenSSH per una mezza dozzina di accessi da remoto, alcuni a buon fine e altri sbagliando password. Abbiamo provato a effettuare una scansione Nessus da remoto, usando tutti i plugin forniti con l’installazione di default dello scanner. In tabella sono riassunti i dati relativi agli alert prodotti dal nostro IDS; per ogni eseguibile riportiamo il numero di sequenze uniche presenti nel database, il numero di sequenze anomale identificate durante la scansione (con ripetizioni) ed il numero massimo di mismatch. servizio sshd cupsd smbd 6.2.3 sequenze uniche 1173 1100 864 sequenze anomale 468 10 215 numero massimo differenze 5 4 5 Falsi positivi È ovviamente possibile che alcuni comportamenti legittimi vengano erroneamente ritenuti anomali. Per quanto durante la fase di creazione del database ci si sforzi di riprodurre quanti più casi d’uso comune del sistema, può capitare che alcuni eseguibili effettuino delle operazioni particolari a runtime, per esempio per qualche cambiamento nell’ambiente operativo della macchina analizzata. Supponiamo il caso di una rete casalinga a cui si colleghi una nuova macchina, non presente durante la fase di creazione del database: il tentativo da parte di quest’ultima di scoprire la presenza di una stampante di rete potrebbe essere visto dal demone di stampa del calcolatore sotto controllo come anomalo. Per portare un esempio ancor più concreto, in fase di test del sistema abbiamo modificato uno script usato dalla macchina aggiungendo l’uso del comando sort. Dopo pochi minuti di analisi, la macchina (chiaramente non sotto attacco) ha iniziato a produrre i seguenti alert: 4 s o r t ( ’ read ’ , ’ read ’ , ’ read ’ , ’ read ’ , 26 ’ read ’ , ’ read ’ ) 4 4 4 4 sort sort sort sort ( ’ read ( ’ read ( ’ read ( ’ read ’ ’ ’ ’ , , , , ’ read ’ read ’ read ’ read ’ ’ ’ ’ , , , , ’ read ’ read ’ read ’ read ’ ’ ’ ’ , , , , ’ read ’ read ’ read ’ read ’ ’ ’ ’ , , , , ’ read ’ read ’ read ’ read ’ ’ ’ ’ , , , , ’ read ’ read ’ read ’ read ’) ’) ’) ’) Il comportamento suddetto è perfettamente accettabile; abbiamo dunque fatto girare lo script di creazione del db, il quale in caso di db già esistente si limita ad aggiungere le nuove sequenze piuttosto che ripartire da zero, controllando in seguito tramite l’interprete Python la presenza della sequenza ritenuta anomala. ema@mars : ˜ / dev / systemtap−i d s −poc$ python iPython 2 . 6 . 2 ( r e l e a s e 2 6 −maint , Apr 19 2 0 0 9 , 0 1 : 5 6 : 4 1 ) [GCC 4 . 3 . 3 ] on l i n u x 2 Type ” h e l p ” , ” c o p y r i g h t ” , ” c r e d i t s ” o r ” l i c e n s e ” f o r more i n f o r m a t i o n . >>> import d b a c c e s s >>> data = d b a c c e s s . g e t d a t a ( ) >>> p r i n t ( ’ read ’ , ’ read ’ , ’ read ’ , ’ read ’ , ’ read ’ , ’ read ’ ) \ i n data . e x e c u t a b l e s [ ’ s o r t ’ ] True >>> La sequenza è stata aggiunta al database, scongiurando cosı̀ il verificarsi del falso positivo causato dal “nuovo” utilizzo del comando sort. 7 Conclusioni SystemTap è un noto sistema per il tracciamento dinamico del funzionamento del kernel Linux; questo genere di strumenti consente in modo relativamente semplice di analizzare nel dettaglio il comportamento di un sistema operativo in produzione. In questa prova finale abbiamo dimostrato come sia possibile scrivere degli script SystemTap per individuare determinati tipi di comportamenti potenzialmente ostili come ad esempio dei portscan. Abbiamo inoltre sviluppato un Anomaly Based Intrusion Detection System usando l’analisi di brevi sequenze di chiamate di sistema per individuare i comportamenti potenzialmente anomali. Il sistema sviluppato, composto principalmente da uno script SystemTap e una mezza dozzina di script Python, si è rivelato in grado di riconoscere tentativi di intrusione da remoto generati tramite lo scanner di sicurezza Nessus, nonché la presenza di rootkit locali, nella fattispecie ark. 27 Il sistema è stato rilasciato come software libero secondo i termini della Apache License, versione 2.0 ed è esaminabile online all’indirizzo seguente: http://bitbucket.org/ema/systemtap-ids-poc/src/. Per utilizzarlo è sufficiente scaricarne i sorgenti, disponibili all’indirizzo http://bitbucket.org/ema/systemtapids-poc/downloads/ e seguire il file README. 28 Riferimenti bibliografici [1] Bryan M. Cantrill et al. Dynamic Instrumentation of Production Systems Sun Microsystems Inc, 2004. [2] Vara Prasad et al. Locating System Problems Using Dynamic Instrumentation IBM Corp, Red Hat Inc, Intel Corporation, 2005. [3] Elena Zannoni, Linux Tips and Tricks for Developers Oracle Corporation, 2007. [4] Jonathan Corbet, On DTrace envy Linux Weekly News, August 2007. [5] Frank Ch. Eigler, Vara Prasad et al. Architecture of SystemTap: a Linux trace/probe tool Red Hat Inc, IBM Corp, July 2005. [6] The SystemTap Community, stap manual page Red Hat Inc [7] Bart Jacob, Paul Larson et al. SystemTap: Instrumenting the Linux Kernel for Analyzing Performance and Functional Problems IBM Corp, 2009. [8] The SystemTap Community, SystemTap Language Reference Red Hat Inc, IBM Corp, Intel Corporation, 2009. [9] Mark Wielaard, A SystemTap update Linux Weekly News, January 2009. [10] Stephanie Forrest, Steven A. Hofmeyr, Anil Somayaji, Thomas A. Longstaff A Sense of Self for Unix Processes University of New Mexico, CERT Coordination Center, 1996. [11] Steven A. Hofmeyr, Stephanie Forrest, Anil Somayaji Intrusion Detection using Sequences of System Calls University of New Mexico, 1997 [12] Christina Warrender, Stephanie Forrest, Barak Pearlmutter Detecting Intrusions Using System Calls: Alternative Data Models University of New Mexico, 1999. [13] Ke Wang, Salvatore J. Stolfo Anomalous Payload-based Network Intrusion Detection Columbia University. 29