La memoria dei processi in esecuzione è suddivisa in
- sezioni
- segmenti
Diversi segmenti contengono diversi tipi di dato che vengono initializzati in fase di load in memoria
- init = Contiene il codice di inizializzazione del programma che viene eseguito all'avvio, prima del `main()``
- PLT =
Procedure Linkage Table
, serve per gestire le chiamate a funzione di di libreria - text = Contiene codice eseguibile del programma
- fini = Come init, codice eseguito al termine del programma
- rodata =
Read Only Data
, contiene le variabili globali o statiche dichiarate comeconst
- eh_frame = Contiene i puntatori ad
Exception handler
( solo linguaggi supportati ) - data = contiene le variabili globali e/o statiche inizializzate
- ctors = Contiene puntatori alle funzioni con attributo
constructor
- dtors = Contiene puntatori alle funzioni con attributo
distructor
- GOT =
Global Offset Table
, assieme alla sezione PLT è utilizzata per gestire le chiamate alle funzioni di libreria. - BSS = Contiene le variabili globali e/o statiche non inizializzate. All'inizio tutta l'area d memoria è impostata a zero
- Heap = è una sezione dinamica, contiene memoria allocata tramite
malloc()
&c. Cresce verso indirizzi di memoria alti - Stack = è una sezione di memoria dinamica, organizzata secondo LIFO. Cresce da indirizzi alti verso indirizzi bassi.
< inserire qui immagine >
tramite comando objdump
è possibile ottenere informazioni dettagliate dei file eseguili ad esempio i flag ( i flag sono il motivo per il quale la memoria è divisa in sezioni e segmenti in modo da associare flag differenti +r,+w,+rw etc).
objdump -h helloworld
helloworld: formato del file elf64-x86-64
Sezioni:
Ind Nome Dimens VMA LMA Pos file Allin
0 .interp 0000001c 0000000000400238 0000000000400238 00000238 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .note.ABI-tag 00000020 0000000000400254 0000000000400254 00000254 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .note.gnu.build-id 00000024 0000000000400274 0000000000400274 00000274 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .gnu.hash 0000001c 0000000000400298 0000000000400298 00000298 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .dynsym 00000048 00000000004002b8 00000000004002b8 000002b8 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .dynstr 00000038 0000000000400300 0000000000400300 00000300 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .gnu.version 00000006 0000000000400338 0000000000400338 00000338 2**1
CONTENTS, ALLOC, LOAD, READONLY, DATA
7 .gnu.version_r 00000020 0000000000400340 0000000000400340 00000340 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
8 .rela.dyn 00000018 0000000000400360 0000000000400360 00000360 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
9 .rela.plt 00000018 0000000000400378 0000000000400378 00000378 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
10 .init 0000001a 0000000000400390 0000000000400390 00000390 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
11 .plt 00000020 00000000004003b0 00000000004003b0 000003b0 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
12 .plt.got 00000008 00000000004003d0 00000000004003d0 000003d0 2**3
CONTENTS, ALLOC, LOAD, READONLY, CODE
13 .text 00000182 00000000004003e0 00000000004003e0 000003e0 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
14 .fini 00000009 0000000000400564 0000000000400564 00000564 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
15 .rodata 00000004 0000000000400570 0000000000400570 00000570 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
16 .eh_frame_hdr 00000034 0000000000400574 0000000000400574 00000574 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
17 .eh_frame 000000f4 00000000004005a8 00005a8 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
18 .init_array 00000008 0000000000600e10 0000000000600e10 00000e10 2**3
CONTENTS, ALLOC, LOAD, DATA
19 .fini_array 00000008 0000000000600e18 0000000000600e18 00000e18 2**3
CONTENTS, ALLOC, LOAD, DATA
20 .jcr 00000008 0000000000600e20 0000000000600e20 00000e20 2**3
CONTENTS, ALLOC, LOAD, DATA
21 .dynamic 000001d0 0000000000600e28 0000000000600e28 00000e28 2**3
CONTENTS, ALLOC, LOAD, DATA
22 .got 00000008 0000000000600ff8 0000000000600ff8 00000ff8 2**3
CONTENTS, ALLOC, LOAD, DATA
23 .got.plt 00000020 0000000000601000 0000000000601000 00001000 2**3
CONTENTS, ALLOC, LOAD, DATA
24 .data 00000010 0000000000601020 0000000000601020 00001020 2**3
CONTENTS, ALLOC, LOAD, DATA
25 .bss 00000008 0000000000601030 0000000000601030 00001030 2**0
ALLOC
26 .comment 00000034 0000000000000000 0000000000000000 00001030 2**0
CONTENTS, READONLY
Sezioni utilizzate per gestire librerie condivise ( libc per funzioni come printf ) le librerie vengono caricate in memoria quando il primo programma ne richiedere l'utilizzo e il codice viene posto in condivisione con tutti i programmi che in un momento successivo le richiedono. Servono meccanismi per detectare se:
- individuare se libreria è già caricata
- trovare indirizzo da utilizzare
PLT = contiene codice eseguibile e ha atributo di sola lettura GOT = contiene solamente dati
Tramite comando objdump -d helloworld
Disassemblamento della sezione .plt.got:
0000000000400420 <.plt.got>:
400420: ff 25 d2 0b 20 00 jmpq *0x200bd2(%rip) # 600ff8 <_DYNAMIC+0x1d0>
400426: 66 90 xchg %ax,%ax
Disassemblamento della sezione .plt:
00000000004003f0 <printf@plt-0x10>:
4003f0: ff 35 12 0c 20 00 pushq 0x200c12(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
4003f6: ff 25 14 0c 20 00 jmpq *0x200c14(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
4003fc: 0f 1f 40 00 nopl 0x0(%rax)
0000000000400400 <printf@plt>:
400400: ff 25 12 0c 20 00 jmpq *0x200c12(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
400406: 68 00 00 00 00 pushq $0x0
40040b: e9 e0 ff ff ff jmpq 4003f0 <_init+0x28>
0000000000400410 <__libc_start_main@plt>:
400410: ff 25 0a 0c 20 00 jmpq *0x200c0a(%rip) # 601020 <_GLOBAL_OFFSET_TABLE_+0x20>
400416: 68 01 00 00 00 pushq $0x1
40041b: e9 d0 ff ff ff jmpq 4003f0 <_init+0x28>
Disassemblamento della sezione .plt.got:
0000000000400420 <.plt.got>:
400420: ff 25 d2 0b 20 00 jmpq *0x200bd2(%rip) # 600ff8 <_DYNAMIC+0x1d0>
400426: 66 90 xchg %ax,%ax
...
0000000000400526 <main>:
400526: 55 push %rbp
400527: 48 89 e5 mov %rsp,%rbp
40052a: bf c4 05 40 00 mov $0x4005c4,%edi
40052f: b8 00 00 00 00 mov $0x0,%eax
400534: e8 c7 fe ff ff callq 400400 <printf@plt>
400539: b8 00 00 00 00 mov $0x0,%eax
40053e: 5d pop %rbp
40053f: c3 retq
Si nota nella sezione main
offset 400534
sia effettuata callq
alla funzione printf@plt
nella sezione PLT
all'indirizzo 400400
che successivamente viene avviato il controllo all'indirizzo contenuto in *0x200c12(%rip)
che rappresenta un indirizzo in
<_GLOBAL_OFFSET_TABLE_+0x18>
caricato dalla libc, questo perchè la libreria era già caricata e quindi non ha avuto necessità di recuperare nuovi indirizzi.
https://www.codeproject.com/Articles/70302/Redirecting-functions-in-shared-ELF-libraries
Sono contenute le variabili globali e statiche inizializzate.
File: static1.c
Per visualizzare tale sezione si utilizza il seguente comando objdump -s -j .data static1
static1: formato del file elf64-x86-64
Contenuto della sezione .data:
601020 00000000 00000000 00000000 00000000 ................
601030 41 A
41
rappresenta il valore hex del carattere A
File: ctors_dtors1.c
Le funzioni constructor
sono eseguite prima della funzione main()
, mentre deconstructor
dopo.
Queste sezioni contengono i puntatori alle funzione dichiarate, per visualizzare tali puntatori comando objdump -s -j .ctors -j .dtors ctors_dtors1
che però non mostra nulla perchè può dipendere dal compilatore come rinomina tali sezioni, ad esempio tramite il comando readelf -S ctors_dtors1
possiamo visualizzare i campi:
- init_array
- fini_array
[19] .init_array INIT_ARRAY 0000000000600e00 00000e00
0000000000000010 0000000000000000 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000600e10 00000e10
0000000000000010 0000000000000000 WA 0 0 8
per approfondire le sezioni si visualizza objdump -s ctors_dtors1
0000000000400520 <__do_global_dtors_aux>:
400520: 80 3d 19 0b 20 00 00 cmpb $0x0,0x200b19(%rip) # 601040 <__TMC_END__>
400527: 75 11 jne 40053a <__do_global_dtors_aux+0x1a>
400529: 55 push %rbp
40052a: 48 89 e5 mov %rsp,%rbp
40052d: e8 6e ff ff ff callq 4004a0 <deregister_tm_clones>
400532: 5d pop %rbp
400533: c6 05 06 0b 20 00 01 movb $0x1,0x200b06(%rip) # 601040 <__TMC_END__>
40053a: f3 c3 repz retq
40053c: 0f 1f 40 00 nopl 0x0(%rax)
oppure tramite objdump -x ctors_dtors1
0000000000400520 l F .text 0000000000000000 __do_global_dtors_aux
Queste sezioni sono utili per implementare tecniche anti debugging e packer per spacchettare codice a runtime criptato e pacchettizato, ftp://hackbbs.org/milworm/9
Contiene variabili globali e statiche non inizializzate, non esiste un'immagine della sezione BSS nel file eseguibile, viene creata al momento dell'esecuzione del programma e le variabili presenti al suo interno sono inizializzate con byte NULL.
File: bss1.c
Tramite il comando objdump -x bss1
è possibile verificare la dimensione 0x40 ( 64 byte ) della sezione bbs indirizzo e offset
25 .bss 00000040 0000000000601030 0000000000601030 00001030 2**4
ALLOC
Mentre tramite il comando objdump -t bss1 | grep bss
si ottiene output con le dimensioni delle singole variabili presenti nella sezione BBS, rispettivamente:
- a = 0x10 = 16 byte (array char) -> indirizzo 0x0000000000601060
- b = 0x10 = 16 byte (array char ) -> indirizzo 0x0000000000601050
- c = 0x4 = 4 byte ( intero ) -> indirizzo 0x0000000000601040
objdump -t bss1 | grep bss
bss1: formato del file elf64-x86-64
0000000000601030 l d .bss 0000000000000000 .bss
0000000000601030 l O .bss 0000000000000001 completed.7585
0000000000000000 l df *ABS* 0000000000000000 bss1.c
0000000000601040 l O .bss 0000000000000004 c.1834
0000000000601050 l O .bss 0000000000000010 b.1833
0000000000601070 g .bss 0000000000000000 _end
0000000000601060 g O .bss 0000000000000010 a
0000000000601030 g .bss 0000000000000000 __bss_start
Tramite debugger gdb
possiamo verificare che i dati siano effettivamente inizializzati a NULL
0x00
tramite i seguenti comandi
gdb bss
<- avviogdb
sul binariob main
<- setto breakpoint esecuzione funzionemain()
x/60b 0x0000000000601030
<- visualizza il contenuto della memoria all'indirizzo richiestox
per64
elementi mostrati come formatobyte
http://visualgdb.com/gdbreference/commands/x
gdb bss1
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from bss1...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x4004da
(gdb) r
Starting program: /home/rhpco/PROJECT/exploiting/lab1/ale/bss1
Breakpoint 1, 0x00000000004004da in main ()
(gdb) x/64x 0x0000000000601030
0x601030 <completed.7585>: 0x00000000 0x00000000 0x00000000 0x00000000
0x601040 <c.1834>: 0x00000000 0x00000000 0x00000000 0x00000000
0x601050 <b.1833>: 0x00000000 0x00000000 0x00000000 0x00000000
0x601060 <a>: 0x00000000 0x00000000 0x00000000 0x00000000
0x601070: 0x00000000 0x00000000 0x00000000 0x00000000
0x601080: 0x00000000 0x00000000 0x00000000 0x00000000
0x601090: 0x00000000 0x00000000 0x00000000 0x00000000
0x6010a0: 0x00000000 0x00000000 0x00000000 0x00000000
0x6010b0: 0x00000000 0x00000000 0x00000000 0x00000000
0x6010c0: 0x00000000 0x00000000 0x00000000 0x00000000
0x6010d0: 0x00000000 0x00000000 0x00000000 0x00000000
0x6010e0: 0x00000000 0x00000000 0x00000000 0x00000000
0x6010f0: 0x00000000 0x00000000 0x00000000 0x00000000
0x601100: 0x00000000 0x00000000 0x00000000 0x00000000
0x601110: 0x00000000 0x00000000 0x00000000 0x00000000
0x601120: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb) x/64b 0x0000000000601030
0x601030 <completed.7585>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x601038: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x601040 <c.1834>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x601048: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x601050 <b.1833>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x601058 <b.1833+8>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x601060 <a>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x601068 <a+8>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
(gdb)
Porzione memoria dinamica gestita LIFO
, operazioni base:
push
pop
L'ultimo elemento inserito è memorizzato ad un indirizzo pià basso rispetto agli elementi inseriti precedentemente. Lo stack cresce da indirizzi alti verso indirizzi basi di memoria. Nello stack vengono memorizzate le seguenti informazioni:
- i parametri passati alle funzioni
- le variabili locali delle funzioni
- i dati necessari per gestire le chiamate a funzione ( registri
EIP, ESP, EBP
)
La gestione delle funzioni è quindi strettamente legata allo Stack
, ogni volta che una funzione è eseguita viene riservata una porizione di stack associata a tale funzione che servirà a contenere i dati precedenemnte elecanti.
Questa porizione di Stack viene chiamata frame
ed il frame associato alla singola funzione in esecuzione frame corrente
, nei processori x86
i frame sono gestiti tramite due registri:
-
ESP =
Extended Stack Pointer
registro a 32 bit contenente l'indirizzo della cima del frame corrente, ultimo elemento occupato ( full descending g) -
EBP =
Extended Base Pointer
registro a 32 bit che contiene l'indirizzo della base del frame corrente. per non far confunzione tra "base" e "cima" si ricordi che lo stack cresce verso il basso e che quindiESP
contiene sempre un indirizzo<=
aEBP
. -
EIP =
Extendend Instruction Pointer
registro a 32bit che contiene l'indirizzo della prossima instruzione da eseguire.
Ogni volta che si esegue una funzione viene ceato un nuovo frame. Prima di crearlo però vengono salvati i riferimenti del frame precednente in modo da poterlo ripristinare ( il frame in corso ) una volta che la funzione avrà terminato la sua esecuzione.
Di fatto il registro EBP
viene utilizzato come frame pointer
della funzione corrente, ovvero punta al primo elemento del frame, è possibile indirizzare le variabili locali ( locali del frame quindi della funzione in esecuzione in corso ) sommando a EBP
un indirizzo di Offset
.
Il processore ha istruzioni dedicate alla gestione dello Stack
:
- PUSH = inserisce un elemnto sulla cima dello stack e decrementa ( perchè cresce verso il basso ) il registro
ESP
del numero di byte inseriti. - POP = preleva un elemento dalla cima dello Stack, salvandolo in un registro e incrementa il registro
ESP
- CALL = esegue una funzione, per farlo salva il valore del registro
EIP
sullo stack e passa il controllo alla prima istruzione della nuova funzione chiamata. L'elemento salvato sullo Stack è chiamato indirizzo di ritorno oActivation Record
- LEAVE = copia il
EBP
inESP
poi ripristina l'ultimoEBP
salvato sullo stack
mov %ebp, %esp <- copia ebp in esp
pop %ebp <- recupera ultimo valore come ebp incrementa lo esp
- RET = è usato per il ritorno da una funzione, preleva dallo stack l'indirizzo di ritorno che era stato salvato in precedenza dall'istruzione call e lo mette in
EIP
equivale idealmente alla seguente operazione
pop %eip