Skip to content

Commit f354533

Browse files
committed
Made the 2024-11-15 page more readable and understandable.
Added a new page to guide the user in the creation of a virtual machine with VirtualBox.
1 parent d49e857 commit f354533

File tree

3 files changed

+170
-33
lines changed

3 files changed

+170
-33
lines changed

content/incontri/2024-11-15/index.md

Lines changed: 136 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ author = "Renato"
77
description = "Buffer Overflow, C internals e pwntools"
88
+++
99

10-
1110
## Preambolo
1211

13-
cosa ci serve:
12+
Durante questo incontro vedremo come funziona un buffer overflow, come sfruttarlo e una breve introduzione a pwntools.
13+
14+
Cosa ci serve:
1415

1516
- `gcc`
1617
- `gdb`
@@ -23,12 +24,22 @@ Tutti i tool sono disponibili di default su Kali.
2324

2425
Su Ubuntu si installano con :
2526

27+
```bash
28+
$ sudo apt install build-essential gdb python3 python3-pip python3-dev git libssl-dev libffi-dev gcc-multilib
2629
```
27-
sudo apt install build-essential gdb python3 python3-pip python3-dev git libssl-dev libffi-dev
28-
pip install --break-system-packages pwntools
30+
31+
```bash
32+
$ pip install --break-system-packages pwntools
2933
```
3034

31-
## ma come funziona veramente C?
35+
> [!TIP] Comandi shell
36+
> Di solito i comandi shell nelle varie risorse online sono preceduti da un `$`, che rappresenta il prompt del terminale.
37+
> Questo viene usato per:
38+
>
39+
> - indicare che il comando va eseguito come **utente normale** (ovvero non come root).
40+
> - rendere il copia e incolla più difficile e costringere a modificare il comando prima di eseguirlo, riducendo il rischio di eseguire comandi pericolosi per errore.
41+
42+
## Buffer Overflow
3243

3344
Ogni problema si può comprendere su diversi livelli di astrazione. Solitamente, ragioniamo su un programma C (o qualsiasi altro linguaggio) al livello del linguaggio stesso.
3445
Da questo punto di vista il programma non è altro che un insieme di variabili e funzioni, e tutta l'esecuzione parte dal `main`. Sappiamo però che ciò che esegue il nostro computer è un binario generato dal compilatore e dal linker -> **cosa succede tra un' astrazione e l'altra?**
@@ -38,7 +49,9 @@ Da questo punto di vista il programma non è altro che un insieme di variabili e
3849
Senza entrare nelle specifiche, concettualmente il nostro computer funziona in modo "lineare" consuma un "nastro" di codice, eseguendone le istruzioni.
3950
Questo differisce abbastanza dalla visione strutturata di un programma C, dove abbiamo diverse funzioni innestate che si chiamano fra di loro (o se stesse)
4051

41-
### cos'è veramente una funzione?
52+
### Come funziona una funzione?
53+
54+
Oltre al gioco di parole, è importante capire come funziona una funzione a basso livello.
4255

4356
Non esiste un concetto nativo di funzione per il computer esegue un programma : il programma è semplicemente un insieme (tendenzialmente) monolitico di istruzioni, da caricare in memoria (il nastro).
4457

@@ -56,9 +69,9 @@ A un alto livello, cosa serve per implementare una funzione?
5669

5770
D'ora in poi considereremo nello specifico l'architettura x86_32, i cui eseguibili sono facili da eseguire sulla maggior parte dei computer moderni
5871

59-
### un semplice programma-cavia
72+
### Un semplice programma-cavia
6073

61-
Prendiamoci un attimo di pausa dalla teoria per analizzare un semplice programma C che prende input dall'utente con la `gets`:
74+
Per capire meglio come funzionano le chiamate a funzione, consideriamo il seguente programma:
6275

6376
```c
6477
#include <stdio.h>
@@ -81,73 +94,163 @@ int main() {
8194
}
8295
```
8396

97+
Il programma è molto semplice, chiede il nostro nome e lo stampa a schermo. Da notare la funzione `gets`, che è **molto** pericolosa e non dovrebbe essere usata mai in un programma reale.
98+
8499
Se provate a compilare questo programma normalmente, il compilatore si arrabbierà (con buone ragioni). **Perchè**?
85100

101+
```bash
102+
$ gcc dim.c
103+
104+
dim.c: In function ‘funzione_normalissima’:
105+
dim.c:10:5: error: implicit declaration of function ‘gets’; did you mean ‘fgets’? [-Wimplicit-function-declaration]
106+
10 | gets(buffer); // Non usarmi!
107+
| ^~~~
108+
| fgets
109+
```
110+
86111
{{% resources pattern="ex1-gets/" style="primary" /%}}
87112

88-
### che succede se passo più di 64 caratteri?
113+
### 1. Che succede se passo più di 64 caratteri?
114+
115+
A prima vista, il programma sembra funzionare correttamente. Notiamo però che la variabile `buffer` è grande solo 64. Cosa succede se passiamo più di 64 caratteri?
89116

90117
Facciamolo!
91118

92-
A prima vista, il programma sembra crashare, **perchè**? Cos'altro può succedere?
119+
```shell
120+
$ ./dim
121+
Qualche professore amerebbe questo programma
122+
Come ti chiami? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
123+
Ciao AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!!
124+
Errore di segmentazione (core dump creato)
125+
```
126+
127+
Il programma crasha! Perchè?
93128

94-
### strumenti utili
129+
### 1.a Strumenti utili
95130

96131
- `gdb` : debugger, ci aiuterà a capire meglio cosa fa il nostro programma e a capire perchè crasha
97132
- `pwntools` : libreria di python, ci permette di interagire in modo programmatico con il nostro binario
98133

99134
Entrambi installati di default su Kali!
100135

101-
### cosa sta succedendo?
136+
### 2. Cosa sta succedendo?
137+
138+
Proviamo ad analizzare il problema con `gdb`:
139+
140+
```shell
141+
$ gdb ./dim
142+
(gdb) run
143+
```
102144

103-
Aprendo il programma con gdb ed eseguendolo vediamo qualcosa del genere:
145+
Inseriamo il nostro input e vediamo cosa succede:
104146

105147
```
106148
Program received signal SIGSEGV, Segmentation fault.
107149
0x41414141 in ?? ()
108150
```
109151

110-
Il programma ha provato ad eseguire una "funzione" all'indirizzo 0x41414141. è un caso?
152+
L'errore di segmentazione (SIGSEG) è il più classico, ma cosa significa `0x41414141 in ??`?
153+
154+
Normalmente `gdb` ci mostra sia l'indirizzo di memoria in cui si è verificato l'errore, sia la funzione in cui si è verificato.
155+
156+
- `0x41414141` è un indirizzo di memoria, ma cosa rappresenta?
157+
- `??` indica che `gdb` non sa cosa ci sia in quell'indirizzo di memoria
158+
159+
Il programma ha quindi provato ad eseguire una "funzione" all'indirizzo `0x41414141`. E' un caso?
160+
161+
Se proviamo a convertire `0x41414141` in ASCII otteniamo "AAAA", che è esattamente una parte del nostro input.
111162

112-
"AAAA", codificato come indirizzo è proprio 0x41414141 -> abbiamo sovrascritto qualcosa di importante con il nostro input
163+
Possiamo quindi dedurre che **il programma ha provato ad accedere ad una cella di memoria che è stata sovrascritta con il nostro input**.
113164

114-
### come sono implementate le chiamate a funzione in x86_32?
165+
### 3. Come sono implementate le chiamate a funzione in x86_32?
115166

116-
Non entreremo troppo nei dettagli (layout dello stack e memoria), ciò che ci basta sapere per ora è questo:
167+
Non entreremo troppo nei dettagli (layout dello stack e memoria), ciò che ci basta sapere per ora è questo: durante una chiamata a funzione, il programma salva i metadati della funzione chiamante nello stack, e poi salta (_jump_ incondizionato) alla funzione chiamata.
117168

118-
- è che i _metadati_ di una chiamata sono conservati in modo adiacente ai _dati_ -> per ora immaginiamoci che vengano direttamente dopo in memoria
119-
- accedere ad un elemento in un array (array[i]) vuol dire accedere ad un elemento in un indirizzo (array + i * sizeof(type)) -> buffer[65] ad ex. è un'espressione sensata in questo contesto
169+
Lo stack è una struttura dati a pila, ovvero una struttura dati in cui l'ultimo elemento inserito è il primo ad essere rimosso, quindi, al momento di chiamare la funzione, i metadati della funzione chiamante si trovano in cima allo stack.
120170

121-
Possiamo sovrascrivere l'indirizzo di ritorno -> possiamo eseguire una funzione qualsiasi!
171+
Durante l'inizializzazione della funzione chiamata, vengono pushati sullo stack i dati della funzione chiamata (ovvero le variabili locali) e poi viene eseguito il codice della funzione.
122172

123-
### cosa eseguiamo?
173+
Quindi:
124174

125-
Sappiamo che una funzione è identificata da un indirizzo in memoria -> come troviamo quello di `impossibile_da_chiamare`?
175+
> I _metadati_ di una chiamata a funzione sono conservati in modo adiacente ai _dati locali_ della funzione chiamante;
176+
per ora immaginiamoci che siano situati negli indirizzi di memoria appena successivi.
126177

127-
Riapriamo gdb:
178+
Inoltre ricordiamoci che In C accedere ad un elemento in un array (`array[i]`) vuol dire accedere ad un elemento in un indirizzo = `array + i * sizeof(type)`.
179+
180+
Ad esempio, `buffer[65]` nonostante sia "fuori dal buffer" è un modo per accedere al 65° byte del buffer, che però non è del buffer stesso ma di un'altra variabile in memoria (o, in questo caso, dei metadati della funzione chiamante).
181+
182+
Utilizzando queste due informazioni, possiamo capire cosa è successo:
183+
184+
1. Abbiamo sovrascritto il buffer con più di 64 caratteri;
185+
2. Abbiamo sovrascritto i metadati della funzione chiamata;
186+
3. Nel momento in cui la funzione chiamata tenta di ritornare alla funzione chiamante (`return`), il program counter (PC) si sposta all'indirizzo che abbiamo scritto come 65° byte del buffer (in realtà non è detto sia proprio il 65° byte, in quanto il compilatore può inserire padding tra le variabili).
187+
188+
Possiamo quindi controllare il PC, ergo possiamo eseguire una funzione qualsiasi (con alcuni limiti, ma per ora non ci interessano)!
189+
190+
### 4. Cosa eseguiamo?
191+
192+
Proviamo, ad esempio, a chiamare la funzione `impossibile_da_chiamare`.
193+
194+
Sappiamo che ogni funzione è caricata in memoria ad un indirizzo specifico dello spazio di indirizzamento del processo
195+
. Come facciamo a sapere quale è l'indirizzo di `impossibile_da_chiamare`?
196+
197+
Riapriamo `gdb`:
128198

129199
```
130-
x *impossibile_da_chiamare
200+
gdb ./a.out
131201
```
132202

133-
NB : per motivi di sicurezza di solito gli indirizzi di memoria cambiano tra un esecuzione e l'altra (ASLR). Possiamo disabilitare questa feature su Linux con:
203+
e chiediamo l'indirizzo della funzione:
134204

135205
```
136-
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
206+
(gdb) x *impossibile_da_chiamare
137207
```
138208

139-
### come scriviamo un indirizzo arbitrario in input?
209+
oppure
140210

141-
Non tutti i byte dell'indirizzo corrispondono a caratteri ASCII. A prima vista dal terminale non riesco a codificare l'indirizzo!
211+
```
212+
(gdb) info functions
213+
```
142214

143-
In realtà un `char` può avere qualsiasi valore da 0 a 255, basta riuscire a mandarlo in input -> `pwntools`
215+
> [!WARNING] Address Space Layout Randomization
216+
> Per motivi di sicurezza di solito gli indirizzi di memoria cambiano tra un esecuzione e l'altra (vedi _ASLR_).
217+
> Possiamo però disabilitarlo temporaneamente con:
218+
>
219+
> ```bash
220+
> $ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
221+
> ```
144222
223+
### 5. Come scriviamo un indirizzo arbitrario in input?
224+
225+
Da terminale non possiamo scrivere un indirizzo arbitrario, in quanto non possiamo scrivere caratteri non ASCII. Per questo è necessario un aiuto da parte di `python3`.
226+
227+
```shell
228+
$ python3 -c "print(b'A'*64 + b'\x01\x02\x03\x04')" | ./dim
145229
```
230+
231+
in questo modo stiamo passando al programma `dim` 64 caratteri `A` seguiti da `0x04030201` (in little-endian, ovvero con gli indirizzi in ordine inverso rispetto a come li scriviamo).
232+
233+
In alternativa, possiamo usare `pwntools`:
234+
235+
```python
236+
r = process("./dim")
146237
r.sendline(b"A"*64 + b"quello che voglio \x01\x02\x03")
238+
r.interactive()
147239
```
148240

149-
Non mi rimane che mandare l'indirizzo giusto. Alcune sfide:
241+
In sequenza:
242+
243+
- `r = process("./dim")` crea un sotto-processo eseguendo il programma `dim`;
244+
- `r.sendline(b"A"*64 + b"quello che voglio \x01\x02\x03")` invia la stringa al programma seguita da un newline (`\n`);
245+
- `r.interactive()` ci permette di interagire con il programma come se fosse un terminale.
246+
247+
Non mi rimane che mandare l'indirizzo giusto!
248+
249+
## Esercizio
250+
251+
Alcune sfide:
150252

151-
- Dopo quanti caratteri lo metto?
152-
- Come lo codifico?
153-
- **Cosa altro posso combinare?**
253+
- Come faccio a trovare il numero di caratteri da scrivere prima dell'indirizzo? (padding)
254+
- Come faccio a trovare l'indirizzo di una funzione nel caso io non abbia accesso al compilato?
255+
- Cosa riesco a fare con questa tecnica?
256+
- Cosa posso fare per evitare che il mio programma sia vulnerabile a questo tipo di attacco?

content/risorse/vm/index.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
+++
2+
title = "Virtual Machine Setup"
3+
description = "Install and configure Kali Linux in a VirtualBox virtual machine."
4+
+++
5+
6+
To participate in the labs, you will need a functioning virtualization environment. As the majority of people use Windows, we will use VirtualBox as our virtualization software (if you are using Linux, you should already know how to create a virtual machine).
7+
8+
VirtualBox is a free and open-source hosted hypervisor, developed by Oracle Corporation. VirtualBox allows you to run multiple operating systems on a single machine.
9+
10+
## 1. VirtualBox Installation
11+
12+
1. Download the latest version of VirtualBox from the [official website](https://www.virtualbox.org/wiki/Downloads).
13+
2. Run the installer and follow the instructions.
14+
15+
You should then be able to open VirtualBox and get a screen similar to the one below:
16+
17+
![](virtualbox-main-empty.png)
18+
19+
## 2. Kali Linux Installation
20+
21+
We will use Kali Linux as our main operating system for the labs. You can download the latest version of Kali Linux from the [official website](https://www.kali.org/downloads/).
22+
23+
1. Go to the [official website](https://www.kali.org/downloads/) and click on the "Virtual Machines" button.
24+
2. Click on the "VirtualBox" button. The download should start automatically.
25+
3. Once the download is complete, extract the files to a folder of your choice.
26+
4. Double click on the `.vbox` file to open it in VirtualBox.
27+
28+
You should now have a Kali Linux virtual machine running in VirtualBox.
29+
30+
## Troubleshooting
31+
32+
If you encounter any issues during the installation process, please refer to the [official VirtualBox documentation](https://www.virtualbox.org/wiki/Documentation).
33+
34+
If you are still having trouble, feel free to ask for help in the Telegram chat or directly during the meetings.
85 KB
Loading

0 commit comments

Comments
 (0)