INNOVA, version 0.0.1 Claudio Agosti - vecna@s0ftpj.org - Sun Jan 11 18:32:54 CET 2004 innova e` un software finalizzato a manipolare le proprie connessioni in modo trasparente all'applicazione che gestisce la comunicazione (client web, mail, ecc...), usa un sistema a plugin per definire le azioni da intraprendere sulla connessione. Principalmente le manipolazioni che possono venire in mente sono: - cifratura - steganografia - sistemi anti-IDS/anti-sniffing - duplicazioni/riproduzioni di traffico - cambi runtime dei pattern all'interno delle sessioni - molte altre cose... Normalmente e` stato necessario lavorare a livello kernel per fare manipolazioni di questo genere, tramite dei moduli che hookavano qualche funzione dopo l'ip_send (per quanto riguarda Linux), ma per varie ragioni e` meglio programmare in userspace. Gestire i raw sock in userspace ha il problema di generare pacchetti di connessioni non tracciate dal kernel (cosa che avviene con la connect(2)) quindi il nostro kernel rispondera` alla macchina che abbiamo contattato tramite sock raw interrompendo la connessione che non ha richiesto. innova e` un software in grado di manipolare le connessioni da userspace senza interferire con il lavoro del kernel e delle applicazioni, tramite sistemi di dirottamento locale e filtraggio che fan parte di netfilter. Se vogliamo modificare i pacchetti in modo trasparente, dobbiamo lavorare dopo il comune stack tcp/ip. innova funziona completamente in userspace: sfruttando alcuni moduli netfilter e` in grado di manipolare i pacchetti prima che il kernel li processi ed anche dopo che il kernel li abbia rilasciati (prima del codice del driver di rete). Le chiamate a netfilter/iptables e il networking setup e` gestito dal framework di innova. userspace | kernel | innova (userspace) --------------\ \---------------------------- \ \ BitchX --------> tcp ip \ innova legge e modifica il pacchetto \ \ in modo trasparente lynx --------> tcp ip \ \ -------> bind --------> udp ip \ il kernel tiene traccia della sessione \ \ su cui l'applicazione userspace lavora tftp --------> udp ip \ \ \ innova opera prima della lettura e dopo --------------\---------------\ l'invio da parte del kernel. innova in se` non fa alcun lavoro di manipolazione, e` un framework che gestisce il sistema per il dirottamento trasparente del traffico mettendo a disposizione un set di funzioni chiamabili da uno shared object che lavora come un plugin. Su http://www.s0ftpj.org/projects/innova e` pubblicata una lista dei plugin presenti nel pacchetto (e quelli che a poco a poco verranno aggiunti, si accettano volentieri contributi :) Scendendo nei dettagli, innova per applicare il dirottamento fa queste operazioni (la lista che segue potrebbe sembrare sbagliata, lunga, brutta, etc ma e` l'unica per risolvere il problema: alternative piu` semplici potrebbero presentare problemi): Per i pacchetti in arrivo, che non devono essere letti dal kernel prima di innova: 1) Viene messa una regola di DROP per bloccare i pacchetti selezionati (innova dovrebbe prendere come argomento della linea di comando o con i plugin qualche informazione su host/netmask, protocollo o porta per permettere di discriminare tra il traffico). I pacchetti che innova deve leggere, vanno droppati per il kernel. Questo vale per quelli specificati nella table "filter", INPUT chain, e NON per i pacchetti con TTL pari a 255. 2) Viene tenuto (bindato con la feature packet(7)) un socket a datalink layer che analizza i singoli pacchetti ricevuti, quando ne trova uno che matcha con le richieste di innova (e che quindi e` gia` stato droppato) lo passa al plugin (c'e` una funzione apposita per i pacchetti letti da remoto ed una per i pacchetti letti da locale). 3) I pacchetti vengono rispediti al localhost con un TTL a 255 (in tal modo non matcharanno piu` la regola di drop e saranno presi dal kernel). (Questo sara` migliorabile in futuro appena sara` inserita nel kernel stable il target ROUTE) Per i pacchetti in uscita, che devono essere presi da innova dopo che l'applicazione ha passato i dati che deve inviare e dopo che il kernel li ha adattati alla connessione: 1) Viene messa una regola di iptables sulla chain di OUTPUT tabella mangle (e` il primo tra tutti gli hook dei pacchetti in uscita ad essere chiamato ed analizzato dal framework di netfilter) che marchi (con -j MARK) tutti i pacchetti provenienti dal pid di innova. E` necessaria la match extension relativa all'owner dei pacchetti. 2) Viene messa una regola di iptables sulla chain di OUTPUT tabella filter, con target -j ULOG, che inoltra al netlink device (netlink(7) e netlink(3)) tutti i pacchetti che devono essere passati ad innova, eccetto quelli generati dal pid di innova (altrimenti si creerebbe un loop se innova mandasse pacchetti con le stesse discriminanti di quelli matchati da questa regola). 3) Sulla chain di OUTPUT, ultima tabella, quella di nat, viene applicata una regola di MASQUERADING a tutti i pacchetti che sono stati passati a netlink (la discriminante e` la stessa, tutti i pacchetti tranne quelli dal pid di innova). Netlink infatti, con la seconda regola, non ha eliminato i pacchetti, ma solo duplicati (LOG e ULOG non interferiscono con il normale ciclo di vita dei pacchetti, come spiegato sulla man page). Applicando una regola di MASQUERADING essi non vengono inoltrati, questo e` necessario perche` i pacchetti dell'applicazione wrapperata non devono uscire, ma e` vero anche che non ho potuto usare un semplice -j DROP visto che quel target restituisce un -EPERM alla chiamata di sistema che genera il pacchetto se questo e` generato dall'host locale. Questo puo' causare problemi su tutti i software che controllano il valore di ritorno della loro sendto/connect ecc... Per questo motivo i pacchetti non potevano essere droppati immediatamente, la soluzione e` stata dirottarli su localhost. Il target MASQUERADE e` necessario perche` venga ricomputata la regola di routing, che viene aggiunta tramite iproute e le policy. 4) Si crea una tabella di routing per cui i pacchetti markati dalla prima regola vengano discriminati, per questo motivo nel kernel e` necessario abilitare [*] IP: advanced router [*] IP: policy routing [*] IP: use netfilter MARK value as routing key ed avere la suite di iproute installata. Le necessita` di moduli da parte di netfilter riguardano invece: IP table support (required for filtering/masq/NAT) netfilter MARK match support TTL match support Owner match support (EXPERIMENTAL) Full NAT MASQUEREADE target support Packet mangling MARK target support ULOG target support Per quanto sembri macchinoso il giro compiuto dei pacchetti, non ho trovato al momento altre soluzioni che mi consentano di fare la stessa cosa (vi consiglio di leggere in netlink.c la funzione di generazione delle regole). Sono comunque sicuro che innova sara` migliorabile con il target ROUTE e il supporto di /dev/netfilter_ipv4 per il matching dei pacchetti in userspace (potendo quindi arrivare a fare discriminiazioni a layer 5 o oltre...). Presto innova verra` provvisto tuttavia di un modulo kernel che si appoggera` a netfilter e fara` l'equivalente di quelle chiamate a iptables, rendendo la cosa piu' pulita e performante. innova ha 3 strutture utili, che e` essenziale conoscere nel caso si voglia programmare dei plugin, nel caso si sia interessati al semplice funzionamento suggerisco di andare a leggere README.example . innova_struct: descrive il funzionamento corrente del framework, i file descriptor che ha aperti ed ha un puntatore chiamato "foo" a void che puo' essere usato a piacimento dell'utente per le finalita` del plugin. innova_options: contiene i riferimenti alle opzioni applicate ad innova, si tratta di dati che descrivono l'host remoto, il servizio, il protocollo o eventuali estensioni, mette a disposizione dell'utente il puntatore a funzione foo_options_f e il puntatore "foo" utilizzabile a discrizione del programmatore del plugin. innova_packet: descrive il pacchetto che sta attraversando innova, ha a sua disposizione un puntatore a funzione che viene eseguito prima dell'invio (dopo le funzioni di mangle). Il funzionamento del framework di innova segue questa sequenza: 1) lettura delle opzioni 2) inizializzazione del network 3) inizializzazione dei plugin Le API dei plugin, ovvero le funzioni dove un plugin dovrebbe intercettare i pacchetti selezionati e processarli secondo il suo codice, sono: char *(* get_io_desc)(int *); Ritorna un puntatore a un buffer che contiene il nome/la descrizione del plugin, serve giusto per scrivere il nome del plugin avviato. Il puntatore a int serve per avere un valore assegnato dal modulo in modo che comunichi il valore di PLUGIN_FORMAT, necessario per tenere traccia delle versioni di moduli e del fatto che siano compilati o meno per la versione di innova che le fa girare. int (* mangle_opt_parse)(v_innova_struct_p, v_innova_options_p); Viene chiamata prima dell'inizializzazione di rete, normalmente viene usata per analizzare le opzioni passate al plugin (sono quelle indicate con lo switch -o di innova, poi divise in argc e argv per aiutare l'analisi o usare getopt all'interno di questa funzione). Visto che viene chiamato prima di ogni inizializzazione, e` possibile cambiare tutti i parametri di innova_struct e di innova_options, indipendentemente dal funzionamento comune di innova o dalle opzioni passate dall'utente. Dato che il plugin deve poter avere controllo totale sulle operazioni che vengono svolte da innova, in questa funzione si puo' accedere a parecchi dati. int (* mangle_init)(v_innova_struct_p); Questa funzione viene chiamata dopo l'inizializzazione del network, serve per inizializzare il plugin, puo' essere richiamata a seconda del valore di ritorno della funzione mangle_cleanup. Prende come argomenti la funzione innova_struct, si suppone che i dati dei quali puo' aver bisogno o sono presenti in innova_struct o in innova_options (o meglio ancora, siano stati parsati da mangle_opt_parse e vengano conservati all'interno del plugin). Se mangle_opt_parse puo' essere usato come inizializzatore del plugin, questo e` l'inizializzatore di ogni singola sessione che innova puo' gestire. int (* mangle_cleanup)(v_innova_struct_p, int); mangle_cleanup riceve innova_struct e il codice di errore che ha generato la chiamata della funzione di cleanup: a seconda di questo valore o del plugin il codice di ritorno di mangle_cleanup puo' far tornare innova all'esecuzione di mangle_init, e riprendere... o puo' generare l'uscita del programma. Se mangle_init puo' essere considerata l'inizializzazione di una singola sessione gestita attraverso innova, mangle_cleanup gestira` la sua chiusura. int (* local_mangle)(v_innova_struct_p, v_innova_packet_p); local_mangle e` la funzione che analizza i pacchetti locali che matchano le regole impostate da innova, prende come parametri innova_struct e innova_packet, a seconda del valore di ritorno (se < di 0 e` un errore, altrimenti continua) l'esecuzione di innova continuera` ed il pacchetto verra` inoltrato verso l'host remoto (o a seconda di quello che il plugin sta facendo fare ad innova). int (* remote_mangle)(v_innova_struct_p, v_innova_packet_p); remote_mangle e` la stessa cosa, chiamata per i pacchetti ricevuti da remoto. int (* io_timeexceed)(v_innova_struct_p, int *); La funzione io_timeexceed viene chiamata quando il timeout della system call select(2) scade perche` non si riceve nessun pacchetto da processare in base alle regole di innova ne` dall'interno ne` dall'esterno. Questa funzione puo' essere usata in situazioni di keepalive, il timeout viene resettato all'arrivo di ogni pacchetto.