-
35C3 Vorspannmusik
-
Herald: Gut dann wollen jetzt beginnen.
Wer von euch hat schon mal eine E-Mail
-
gekriegt mit einem Anhang? Wer von euch
hat eine E-Mail bekommen mit einem Anhang
-
von einer euch unbekannten Person? Das
sind erstaunlich wenige, so 20 Prozent.
-
Wer von euch hat den schon mal aufgemacht?
3, 4. Wer von euch kennt jemanden, der schon
-
mal einen Anhang aufgemacht hat? Ja
deutlich mehr. Wer von euch musste die
-
Probleme beseitigen, die dadurch
entstanden sind? Ja auch so 10 20 Prozent
-
würde ich sagen. Ja es ist natürlich eine
ganz gute Idee, sich zu überlegen, ob man
-
denn jetzt eine Rechnung oder ein Angebot
auf machen möchte, was man von einer
-
völlig unbekannten Person aus dem Internet
bekommen hat. Und was da passiert, wenn
-
man es tatsächlich täte, das wird uns
jetzt haggl erläutern. Applaus.
-
Applaus
-
Haggl: Was? Das sollte so nicht aussehen,
Moment, warum sagt mir das keiner?
-
So, hallo, dann können wir jetzt ja anfangen.
Ja also danke für die schöne Einleitung.
-
Schön, dass ihr alle da seid, dass ihr so
zahlreich erschienen seid. Wie in jedem
-
Jahr eigentlich, gab es auch in 2018 - ich
schiebe mal eben die Maus da weg - 2018
-
wieder ein paar schöne Beispiele, oder
schön ist in Anführungszeichen zu setzen,
-
Beispiele von Sicherheitslücken, die
ziemlich gravierend sind und die
-
geschlossen werden, glücklicherweise,
gingen direkt am Anfang des Jahres los. Im
-
Januar wurden im Firefox und mithin auch
im tor-Browser, der ja drauf basiert, ein
-
paar Sicherheitslücken geschlossen,
für die man nur eine Webseite besuchen
-
musste und dann sind da intern irgendwelche
Buffer Overflows passiert und im
-
schlimmsten Fall konnten Angreifer damit
halt beliebigen Code ausführen. Ein
-
weiteres Ding aus diesem Jahr, da habe ich
leider keine schöne Pressemitteilung zu
-
gefunden, nur so einen CVE-Artikel. Der
Adobe Reader, mal wieder, übrigens guckt
-
mal wie viele Sicherheitslücken der hat,
das ist enorm. Da war auch was drin mit
-
einem Buffer Overflow und ebenfalls
arbitrary code execution. Heißt, ein
-
Angreifer kann beliebigen Code auf dem
Rechner ausführen. Drittes Beispiel, gar
-
nicht so lange her, ist eigentlich im
letzten Jahr gewesen, und zwar die beiden
-
Würmer Petya und WannaCry. Das waren so
sympathische Ransomwares, die die
-
Festplatte verschlüsseln und den Besitzer
dann auffordern, Bitcoins zu überweisen, an
-
eine bestimmte Adresse, damit der Rechner
wieder frei geschaltet werden kann. Auch
-
die waren beide, also Teil der Angriffe,
die die genutzt haben, funktionierten über
-
Buffer Overflows. Man sieht also, es ist
ein Thema mit dem man sehr viel machen
-
kann und deswegen reden wir da jetzt ein
bisschen darüber. Das ist der grobe
-
Fahrplan, es gibt jetzt so eine kleine
Vorstellungsrunde, ich sage etwas zu mir,
-
frage was über euch, sag was zum Vortrag.
Danach machen wir ein paar Grundlagen, die
-
zum Teil sehr technisch und zäh sind, ich
bitte das zu entschuldigen, aber ganz ohne
-
geht es leider nicht. Und dann erkläre
ich, wie halt der Exploit funktioniert, so
-
ein Stack Buffer Overflow Exploit und zum
Schluss gibt es, wenn die Demo-Götter uns
-
weiter gewogen sind, eine Demonstration,
wo ich meinen eigenen Rechner halt mit
-
einem verwundbaren Programm übernehme.
Also über mich: Ich bin im Chaos und
-
Hackspace Siegen aktiv und treibe da so
ein bisschen mein Unwesen, habe so halb
-
den Hut für "Chaos macht Schule" und die
Cryptoparties auf. Wenn es sich nicht
-
vermeiden lässt, mache ich noch
Infrastruktur und ähnliche Geschichten. In
-
meinem sonstigen Leben bin ich
Informatiker, ich programmiere kleine
-
Mikrocontroller für Soundsysteme, ich
mache Judo und bin Musiker. Meine
-
Lieblingsspeise ist Dal. An dieser stelle
herzlichen Dank an das Küchenteam, die uns
-
während dem Aufbau sehr gut verpflegt
haben, unter anderem mit Dal. So, bisschen
-
was über euch. Ein kurzes Funktionscheck:
Wer hört mir zu und ist in der Lage und
-
gewillt, seinen Arm zu heben bei Fragen,
die zutreffen? Das sind ungefähr alle,
-
cool. Dann: Wer weiß, wie so ein Rechner
aufgebaut ist, Stichwort
-
Rechnerarchitektur, Rechenwerk,
Steuerwerk, so was? Das sind auch
-
erstaunlich viele, okay. Wer hat schon mal
programmiert? Im Idealfall mit C, das sind
-
ungefähr alle. Wer hat schon mal gehört,
was ein Stack ist? Was ein Buffer Overflow
-
ist? Wer hat sich bei allem gemeldet? Ihr
werdet euch langweilen! lacht Der
-
Vortrag ist nämlich, ich habe versucht,
ihn möglichst einfach zu halten, weil
-
inspiziert inspiriert, entschuldigung,
inspiriert ist er von einem
-
Arbeitskollegen, den ich eigentlich für
sehr kompetent halte, und der sich auch
-
auskennt mit Mikrocontrollern, mit
Assemblycode und der mich irgendwann mal
-
fragte, oder mehr im Nebensatz beiläufig
erwähnte, dass er nicht verstünde, wie
-
denn eigentlich sein kann, dass ein PDF
einen Rechner übernehmen kann. Challenge
-
accepted habe ich mir gedacht, das muss
man doch irgendwie erklären können. Und
-
dann habe ich den Vortrag gemacht, habe
hier in den Talk eingereicht in Pretalx,
-
der wurde dann halt paar Wochen später
auch angenommen, dann habe ich mir den
-
Vortrag, den ich zwei Wochen vorher
zusammengeschraubt hatte, noch einmal
-
angeschaut und geprüft, ob er den
Anforderungen entspricht, die ich mir
-
selber gesetzt habe, festgestellt oh mein
Gott, er ist viel zu technisch. Deswegen
-
habe ich den weiter abgespeckt und
versucht, alle nicht nötigen Details, alle
-
nicht nötigen Fachwörter, alles raus zu
streichen, was man nicht unbedingt
-
braucht, um das Prinzip eines Stack Buffer
Overflows oder ein Exploit dessen zu
-
verstehen. Der Vortrag ist vor 20 Minuten
fertig geworden, ich habe ihn noch nie
-
Probe gehalten, ich habe gestern bei der
Demo zweimal meinen Rechner abgeschossen,
-
insofern bin ich selber sehr gespannt, was
jetzt passiert. Also fangen wir an.
-
Grundlagen: Wie funktioniert ein Rechner?
Wenn man es ganz ganz ganz ganz grob
-
vereinfacht ausdrückt, gibt es irgendwo
einen Prozessor, da kommen auf der einen
-
Seite Daten rein, nämlich Programmdaten,
und auf der anderen Seite kommen Daten
-
rein und raus, nämlich die Nutzdaten, die
ich verarbeiten möchte. Das kann, weiß
-
ich, ein Office-Dokument sein, ein PDF
oder halt auch Daten aus dem Internet wie
-
HTML-Seiten, bei Web-Browsern sind das halt
die Daten die rein und raus gehen. Daten
-
werden intern im Rechner in dem Speicher
abgelegt und zwar in Paketen von acht 1en
-
und 0en. 1en und 0en sind Bits, das wissen
ja wahrscheinlich alle. So ein Paket von
-
acht davon nennt man ein Byte. Jedes Byte
hat eine Adresse im Speicher. Und der Witz
-
ist, dass das hier so, dass was da
gezeichnet ist, nicht ganz korrekt ist,
-
denn Programme und Daten liegen im
gleichen Speicher. Das heißt, ich kann es
-
theoretisch schaffen, den Prozessor dazu
zu bringen, Programme auszuführen aus dem
-
Speicherbereich, wo eigentlich Daten
liegen sollten. Das ist schon eigentlich
-
wie letztendlich so ein Buffer Overflow
Exploit funktioniert. Wenn ich den
-
Prozessor da in der Mitte doch noch mal
ein bisschen aufbohre, dann sehe ich, dass
-
er da drin so einen Registersatz hat. Man
nennt das so, Register. Das ist so ein
-
bisschen vergleichbar mit dem, man könnte
sagen, das Kurzzeitgedächtnis von
-
Menschen, man sagt Menschen ja nach, sie
könnten sich so größenordnungsmäßig 7
-
Dinge gleichzeitig merken, kurzzeitig. So
ähnlich ist das bei einem Prozessor auch,
-
der hat einen sehr begrenzten Satz von
Registern, also Speicherzellen, die sehr
-
schnell sind, aber halt auch sehr teuer
sind. Deswegen baut man da nicht so viele
-
von, das sind die Register. Und dann merkt
er sich halt zum Beispiel, wo er im
-
Programm gerade steht. Also wo im
Programmspeicher er gerade steht oder wo
-
der Stack gerade steht. Der Stack ist ein
besonderer Speicherbereich im
-
Datenspeicher, also eigentlich kein
Programmspeicher, der verwendet wird um
-
Dinge längerfristig abzulegen, also man
könnte sagen Langzeitgedächtnis. Naja es
-
stimmt nicht ganz, aber grob. Und der
Stack ist wirklich, der Name ist Programm,
-
ist wie ein Stapel, ich lege unten was
drauf, dann lege ich was oben drauf, lege
-
etwas weiteres oben drauf, und ich lege
auch Dinge oder hole Dinge genau in der
-
umgekehrten Reihenfolge wieder runter.
Also wie ein Stapel Papier eigentlich. Der
-
wird gebraucht, um bestimmte Features von
Programmiersprachen zu implementieren, das
-
werden wir gleich noch sehen. Wenn jetzt
also so ein Programm abläuft, also links
-
sieht man einen Ausschnitt aus dem
Programmspeicher. Also ich habe da jetzt
-
byteweise mal irgendwelche random
Zahlen hingemalt. Naja es sind keine
-
random Zahlen, es ist tatsächlich ein Teil
eines Shell-Codes. Also von dem Ding, was
-
ich gleich benutze, um meinen Rechner
aufzumachen. Die sind jetzt von oben nach
-
unten byteweise aufgelistet und man würde
jetzt sehen, also Speicheradressen gehen
-
jetzt hier von oben nach unten. Das heißt,
oben sind die niedrigen Speicheradressen,
-
unten sind die hohen Speicheradressen. Und
die werden einfach der Reihe nach
-
durchgezählt, jedes Byte hat halt so eine
Adresse. Und so weiß der Prozessor genau,
-
wenn er gesagt bekommt, macht mal hier an
der und der Stelle im Programmcode weiter,
-
dann springt er halt dahin und liest
weiter. Und zwar sieht das so aus, da
-
werden halt Dinge, also dass der Opcode
oder der Befehl, der gerade ausgeführt
-
werden soll, wird eingelesen und wird
ausgeführt und der könnte dann zum
-
Beispiel so etwas machen wie ein Datum aus
dem Registersatz in den Stack legen oder
-
auf den Stack oben drauf legen. Im
nächsten Schritt wird dann etwas anderes
-
oben draufgelegt und dann wird da wieder
was runtergeholt und wieder zurück in das
-
Register geschrieben, möglicherweise noch
irgendwomit verrechnet und so, hangelt er
-
sich halt durch den Code durch. Von oben
nach unten. Man sieht auch, es muss nicht
-
unbedingt immer alles, jedes Ding muss
nicht auch ein Befehl sein, da sind auch
-
Daten dazwischen. In diesem Fall halt das,
was auf den Stack gelegt wurde. Aber im
-
Prinzip funktioniert das so. Ein Rechner
geht einfach der Reihe nach die Befehle
-
durch. Es gibt dann halt auch
Sprungbefehle, die sagen können: springe
-
mal irgendwie zurück oder springe mal vor,
aber im Prinzip ist das alles was ein
-
Rechner macht. Okay, der Shell Code, den
ich gleich benutzen werde, sieht so aus.
-
Das ist offensichtlich ein Programm, was
den laufenden Prozess beendet und eine
-
Shell mit dem Namen "sh" startet. Nun ist
das natürlich nicht so offensichtlich, so
-
will ja keiner Code schreiben. Deswegen
haben sich Leute einfallen lassen: Hey,
-
wir müssen irgendwie es schaffen, was
lesbareres zu bekommen und außerdem würden
-
wir ganz gerne, wenn wir ein Programm
geschrieben haben, das nicht für jeden
-
Prozessor dieser Welt neu schreiben
müssen. Weil das da ist Code, der nur auf
-
diesem Prozessor, in diesem Fall ein x86
Prozessor ,läuft. Wenn ich versuche, den
-
auf einem ARM-Prozesor laufen zu lassen,
dann sagt er: Kenne ich nicht den Befehl,
-
mache ich nicht. Beides erreicht man mit
sogenannten höheren Programmiersprachen.
-
Eine davon ist C. Das gleiche Programm
sieht in C so aus. Und hier kann man schon
-
einigermaßen erkennen, okay hier ist
irgendwie ein String drin, offensichtlich
-
also eine Zeichenkette die auf /bin/sh
zeigt. Das ist für Leute, die Unix kennen,
-
die Standard Shell. Also ein
Kommandozeileninterpreter. Und darunter ist
-
eine Zeile, naja die ist wirklich sehr
kryptisch, die muss man schon kennen. Es
-
ruft halt das Programm auf. Aber wie
gesagt, Menschen, die sich damit ein
-
bisschen auskennen, die können so etwas
direkt auf Anhieb lesen, das da oben
-
natürlich nicht. Dieser Code wird jetzt in
einen sogenannten Compiler reingeschmissen
-
und der Compiler macht dann aus dem
Unteren ungefähr das Obere wieder. Zudem
-
gibt es dann, wenn man schon so eine
schöne Abstraktion hat, noch
-
Programmbibliotheken, dass man nicht jeden
Scheiß neu machen muss. So was wie: öffne
-
mir eine Datei und lies den Inhalt. Das
will ich ja nicht jedes mal auf der Ebene
-
neu programmieren. Deswegen lege ich mir
dafür schöne Bibliotheken an, mit
-
Funktionen. Funktionen sind Teile also
wieder verwertbare Programmteile, die
-
einen definierten Satz von
Eingabeparametern haben. Also ich kann
-
einer Datei-öffnen-Funktionen
beispielsweise mitgeben, wo die Datei sich
-
befindet im Dateisystem und ob ich sie
lesend schreiben (öffnen) möchte oder schreibend
-
oder beides. Dann haben die einen
Funktionsrumpf. Das ist der Teil der
-
Funktionen, der was macht. Der also das
tut, was die Funktion tun soll mit den
-
Eingangsparametern und dann gibt es noch
einen Return-Wert, dass ist also ein
-
Rückgabewert, den die Funktion dann zurück
gibt, wenn sie fertig ist mit was immer
-
sie getan hat. Und auf diese Weise baut
man sich halt Bibliotheken und verwendet
-
die halt immer wieder. Dieses execve ganz
unten ist beispielsweise eine
-
Bibliotheksfunktion. Und das war im
Prinzip schon alles, was wir für den
-
Exploit wissen müssen. Wenn nämlich jetzt
so eine Funktionen aufgerufen wird. Nehmen
-
wir mal an, wir hätten eine Funktion, die
drei Parameter hat, einer davon ist, ach
-
Quatsch. Zwei Parameter hat,
entschuldigung, und drei lokale Variablen.
-
Achso, Variable habe ich gar nicht
erklärt. Variablen ist auch ein Konzept
-
von Programmiersprachen. Da kann ich halt
Speicheradressen sprechende Namen geben.
-
Dass ich halt als Programmierer sehen kann,
wo ich was abgelegt habe. Und Funktionen
-
können lokale Variablen haben. Davon
beliebig viele und die werden eben über
-
einen Stack abgebildet, genauso wie die
Funktionsparameter über einen Stack
-
abgebildet werden und der Rückgabewert
weiß ich jetzt gerade nicht, da will ich
-
nichts falsches sagen. Könnte sein, dass
der auch über einen Stack läuft, bin ich
-
mir aber gerade nicht sicher. Nein, ich
glaube, läuft er nicht, whatever. Wenn ich
-
jetzt mir eine Funktion vorstelle, die
zwei Parameter hat und drei lokale
-
Variablen. Davon soll eine ein Buffer
sein, dann passiert, wenn die Funktion
-
aufgerufen wird, folgendes: Zuerst werden
die Funktionsargumente oder Parameter auf
-
den Stack gelegt, und zwar in umgekehrter
Reihenfolge. Fragt nicht, ist so. Danach
-
wird die momentane Adresse oder die
nächste Adresse auf den Stack gelegt,
-
damit das Programm weiß, wenn es aus der
Funktion zurückkommt, wo es weitermachen
-
muss, weil es folgt ja ein Sprung. Dann
kommt noch was, das uns nicht interessiert
-
und dann kommen die lokalen Variablen der
Funktion selber. In diesem Fall eine
-
Variable, dann kommt ein Buffer und dann
kommt noch eine Variable. An dieser Stelle
-
ist sehr schön zu erklären, was ein
Buffer ist. Ein Buffer ist
-
eigentlich nichts anderes als eine
Variable, die aber mehr Speicher hat.
-
Also das ist jetzt nicht eine Zahl,
sondern es sind beispielsweise 512 Zahlen
-
der gleichen Größe. Das ist ein Buffer.
Das hier wäre zum Beispiel jetzt ein
-
Buffer der Größe 5. 5 Bytes groß. Die
können beliebig groß sein, wobei beliebig
-
ist nicht ganz richtig. Hängt von der
Speichergröße ab, wie ich gestern gelernt
-
habe, bei der Demo. lacht Ja, aber im
Prinzip sind die nicht begrenzt und das
-
Schlimme ist, man muss sich wenn man so
low level programmiert, selber darum
-
kümmern, dass man die Grenzen einhält und
nicht zu viel da rein schreibt. Und dann
-
sind wir schon beim Programm. Das Rechte
ist das C Program, was ich exploite. Das
-
ist relativ überschaubar, oben diese Zeile
"int main", das kann man ignorieren, dass
-
braucht man halt, um C zu sagen: hier
beginnt das Programm, hier geht es los und
-
es ist aber wichtig zu wissen, dass main
bereits eine Funktion ist. Also vorher
-
passieren noch andere Dinge, die wir nicht
genauer betrachten, die mir aber sehr
-
viele Steine in den Weg gelegt haben
gestern. lacht Die rufen am Ende dann
-
halt diese Funktion main auf. Es ist also
ein ganz normaler Funktionsaufruf. Die
-
Funktion main hat jetzt eine lokale
Variable, nämlich den Buffer der Größe
-
256. Das ist nicht mehr ganz aktuell, in
der Demo, die ich gleich zeige, ist er ein
-
bisschen größer, spielt aber keine Rolle.
Danach gibt es eine Bibliotheksfunktion
-
string copy, strcpy. Die kopiert, was
immer in argv[1] liegt. Da muss man jetzt
-
dazu sagen, das ist ein
Kommandozeilenparameter. Wenn ich ein
-
Programm aufrufe auf der Kommandozeile,
kann ich dem noch Argumente mitgeben und
-
das erste Argument, was ich dem mitgebe in
diesem Fall, würde halt in den Buffer
-
kopiert. Danach wird eine nette Nachricht
ausgegeben "Hallo, was immer in dem Buffer
-
steht" und dann folgt das return. Sprich,
die Funktionen kehrt zurück und dieser
-
ganze Stack wird abgeräumt und der
Prozessor springt dahin, an die Stelle,
-
deren Adresse jetzt in return links rot
markiert steht. Also weil vorher hat sich
-
ja das Programm gemerkt, wo es nachher hin
zurück muss, indem es die Adresse extra da
-
hingelegt hat. Und wenn ich jetzt zu viele
Daten in diesen Buffer rein schreibe, der
-
ist links verkürzt dargestellt, also
jetzt die letzten 5 Bytes von diesem 256
-
Bytes Buffer, wenn ich jetzt zu viele
Daten da reinschreibe, dann kommt
-
irgendwann der Punkt, wo ich halt die
letzten paar Bytes überschreibe. Dann
-
überschreibe ich das Ding, was uns nicht
interessiert und irgendwann überschreibe
-
ich die Return-Adresse. Was jetzt
passiert, erst mal noch nichts. Dann kommt
-
das return 0 und was das return macht: Es
lädt was immer an dieser stelle steht
-
zurück in den instruction pointer, also an
die Stelle, wo sich der Prozessor intern
-
merkt, wo das Programm gerade steht und
macht an der Stelle weiter. Wenn ich es
-
jetzt schaffe, in diesen Buffer meinen
Shell Code reinzuladen, also das kleine
-
Programmstück, was ihr eben gesehen habt,
was das laufende Programm beendet und ein
-
neues Programm, nämlich einen
Kommandozeileninterpreter startet, wenn
-
ich das jetzt schaffe das da oben rein zu
schreiben und zudem noch es schaffe, an
-
die Return-Adresse den Anfang von diesem
Code reinzuschreiben, dann passiert genau,
-
was nicht passieren darf: Nämlich das
Programm macht, was ich ihm vorher gesagt
-
habe und was ich vorher in diesem Buffer
reingeschrieben habe. Und jetzt kommt der
-
Punkt, an dem es interessant wird. Jetzt
muss ich erst mal meine Maus wiederfinden,
-
dass habe ich mir alles anders
vorgestellt. Sieht man das? Das ist ein
-
bisschen klein. Könnt ihr das
lesen, nein oder? Da hinten, könnt ihr
-
das da hinten lesen? Nein. Bis gerade eben
konnte ich auch noch die Größe von meinem
-
Terminal verstellen. Das scheint jetzt
nicht mehr zu gehen. Aha, geht nur auf dem
-
linken Bildschirm, aus Gründen! Könnt ihr
das jetzt einigermaßen lesen? Gut. Also
-
ich habe hier dieses Programm, ich habe
das mal vorbereitet. Es macht, was es
-
soll, es liest halt den ersten Parameter
ein, also "vuln" ist das Programm, das
-
verwundbar ist. Ich habe ihm den Parameter
"haggl" mit gegeben, also sagt das "Hallo,
-
haggl", alles cool. Wenn ich jetzt aber
Dinge da rein schreibe, die ein bisschen
-
länger sind, beispielsweise so etwas. Ich
hoffe, das ist die richtige Syntax um in
-
Python Dinge auf der Kommandozeile direkt
auszuführen. Ja. Dann kriege ich einen
-
segmentation fault. Ein segmentation fault
ist der nette Hinweis vom
-
Betriebssystemen: Kollege, du hast gerade
Speicher lesen und/oder schreiben wollen,
-
auf den du keinen Zugriff hast. Wenn Leute
die Exploits schreiben, so etwas kriegen,
-
also das ist das was normalerweise das
kennt wahrscheinlich jeder, was passiert
-
wenn ein Programm einfach so ohne
irgendetwas zu sagen abstürzt, dann ist
-
das oft ein segmentation fault. Das ist
für die meisten Menschen sehr nervig, weil
-
man dann das Programm neu starten muss,
gegebenenfalls Daten verloren gegangen
-
sind. für Leute, die Exploits schreiben,
ist das der heilige Gral, weil
-
segmentation faults sind oft ein Indiz
dafür, dass es da gerade einen Buffer
-
Overflow gegeben hat. Und ein Buffer
Overflow, damit kann man eine Menge
-
anfangen. Man kann beispielsweise einen
Shell Code reinladen. xxd -p shellcode,
-
das ist das Ding, was wir eben in den
Slides gesehen haben. Das ist also dieses
-
Programm in Maschinencode, was das
laufende Programm beendet und eine Shell
-
startet. Und wenn ich die jetzt da rein
injecte, also anstatt jetzt diesem
-
Pythonding oder dem einfachen String
haggl, dieses Teil jetzt da rein pipe,
-
dann bekomme ich eine Shell. Und zwar eine
Shell, die nicht die andere Shell ist,
-
sondern eine neue Shell ist. An der Stelle
habe ich es geschafft. Jetzt bin ich halt
-
drin und kann hier auf dem System
beliebige Dinge machen. Das ist erstmal so
-
noch nicht so schlimm und das ist ja auch
ein sehr akademisches Beispiel, weil ich
-
es alles selber geschrieben habe. Ich muss
auf dem eigenen Rechner sein, ich bin eh
-
schon der Benutzer, aber interessant wird
es natürlich, wenn so eine Verwundbarkeit
-
irgendwo an der Stelle sitzt, die auf das
Netzwerk horcht. Wenn man von außen
-
irgendwie Pakete einschleusen kann, die so
etwas hervorrufen. Und ich dann irgendwie
-
eine Shell oder ähnliches bekomme. Auch
interessant wird es, wenn das ein Dienst
-
ist, der aus Gründen mit root-Rechten
laufen muss. Dann habe ich nämlich auf
-
einmal eine root-Shell. Und so weiter und
so fort. Also man kann da eine Menge
-
lustige Sachen mit machen. Ich habe noch
etwas anderes vorbereitet. Und zwar, wenn
-
ich jetzt einen Debugger über das Ding
laufen lassen. Ein Debugger ist ein
-
Programm, mit dem ich zur Laufzeit rein
gucken kann, in den Speicher, und schauen
-
kann, was denn der Prozess so gerade da
macht, und was wo an welcher Stelle im
-
Speicher geschrieben wird. Hier sehe ich
jetzt noch mal den Quellcode von dem
-
Programm. Das ist das gleiche wie eben,
bis auf das der Buffer ein bisschen größer
-
ist. Das spielt aber keine große Rolle und
ich habe zwei breakpoints gesetzt.
-
Breakpoint heißt, das Programm oder der
Debugger vielmehr, hält das Programm an
-
der Stelle an, damit man halt schauen
kann, was an bestimmten Speicheradressen
-
steht. Und einer ist hier auf dieser Zeile
stringcopy, Zeile 6. Also der macht, hält
-
an bevor das reinkopiert wurde, und der
nächste breakpoint hält kurz vor dem
-
return an. Und da müsste ich halt dann
schon sehen, was im Buffer passiert ist.
-
Wenn ich das Programm jetzt laufen lasse,
mit den richtigen Argumenten, das ist
-
alles schon hoffentlich konfiguriert, ja,
dann sehe ich hier, das es ist ein
-
Ausschnitt aus dem Buffer, und zwar die
letzten, irgendwo in den letzten paar
-
hundert Bytes, da steht ziemlich
zufälliger Quatsch drin. Keine Ahnung, was
-
das ist das. Das hat irgend ein Prozess
vorher, oder vielleicht möglicherweise
-
auch der init Prozess, also das was vor
meinem eigentlichen main Programm läuft,
-
da hingelegt, keine Ahnung, weiß ich
nicht. Und die return Adresse steht im
-
Moment noch auf dieser lustigen,
kryptischen Zahl. Das wird irgendwas sein,
-
was, nachdem main beendet wurde, also die
Funktion main beendet wurde,
-
wahrscheinlich noch irgendwie Speicher
aufräumt oder so etwas. So, jetzt sind wir
-
also an der Stelle stringcopy, also es ist
noch nichts passiert. Wenn ich jetzt zum
-
nächsten breakpoint weiterlaufe, dann seht
ihr? Hier oben sind ganz viele 0x90
-
Instruktionen. Das sind die Anweisungen
für den Prozessor: mach mal nichts, warte
-
mal einen Takt lang. Und davon habe ich
ganz viele da rein geschrieben und
-
irgendwann kommt dann hier dieser Shell
Code. Das war das Ding, was eben das
-
Programm beendet und die Shell startet.
Und ich kann auch schon sehen, die return
-
Adresse ist jetzt überschrieben worden mit
einer Adresse, die ich selber gewählt
-
habe. Und diese Adresse, die zielt
irgendwo oben vor den Shell Code in diese
-
ganzen no-operation Instruktionen, das
nennt man eine NOP-slide. Das macht man,
-
um ein bisschen zu puffern und ein
bisschen Platz zu haben und ein bisschen
-
Freiheit zu haben, weil wenn so ein
Prozess gestartet wird, dann hängen da
-
oben über dem Stack noch ganz viele andere
Sachen und die können sich, nicht zur
-
Laufzeit, die können sich aber auch von
Programmausführung zu Programmausführung
-
ändern. Wenn ich zum Beispiel eine neue
Variable definiere oder whatever.
-
Jedenfalls habe ich die Sachen nicht immer
exakt an der gleichen Speicheradresse und
-
deswegen macht man gerne so einen NOP-
slide. Also man nimmt seinen Shell Code,
-
packt den irgendwo in die Mitte, packt
davor ganz viele NOPs, wo der Rechner
-
einfach so durchtingelt und nichts tut,
und dann irgendwann an dem Shell Code
-
ankommt und dahinter packt man ganz viele
return Adressen, die oben in diese NOP-
-
Slide reinspringen. Das heißt, egal ob ich
den Bereich vor dem Shell Code treffe oder
-
den Bereich hinter dem Shell Code treffe,
es passiert immer was passieren muss,
-
nämlich er springt im ungünstigsten Fall
von hinterm Shell Code zu vor dem Shell
-
Code und hangelt sich dann durch bis zum
Shell Code, der dann das tut, was er tut.
-
Und das ist der Buffer Overflow Exploit.
Demo Time! Ja wobei, wie ich schon sagte,
-
das ist ein sehr akademisches Beispiel,
weil ich das alles halt sehr, um es
-
einfach zu halten, sehr klein gehalten
habe. In der Realität würde man sagen, es
-
ist ja keiner so blöd, wenn da irgendwas
in den Puffer rein schreibt, nicht zu
-
checken, wie groß denn der Puffer ist und
mal zu gucken, ob man überhaupt noch da
-
rein schreiben darf. Das stimmt auch,
meistens. lacht Das Problem ist: selbst,
-
wenn es immer gemacht würde, habe ich oft
das Problem, dass ich das zu der Zeit, wo
-
ich das Programm schreibe, noch gar nicht
wissen kann, wie groß dieser Buffer sein
-
muss, weil ich es erst zur Laufzeit
mitbekomme, wie viele Pakete da jetzt
-
gleich ankommen, wenn es zum Beispiel ein
Netzwerkdienst ist. Das heißt, die Größe
-
von so einem Buffer wird oft berechnet und
wenn ich jetzt bei der Berechnung der
-
Größe irgend einen Fehler habe, der dazu
führt, dass ich eine falsche Größe raus
-
bekomme, die dann dazu führt, dass der
Rechner, oder das Programm vielmehr,
-
denkt, der Buffer ist riesig groß, dann
schreibt das Ding halt weiter. Das ist
-
eine Sache, die oft ausgenutzt wird und
Daten kommen halt eben, let's face it,
-
meistens nicht von der Kommandozeile, aber
stattdessen kommen die aus Dateien, die
-
ich irgendwie manipulieren kann oder sogar
aus dem Netzwerk. Da habe ich natürlich
-
dann beliebige Angriffsvektoren. Das
heißt, was in der Realität passieren
-
würde, also jetzt beispielsweise bei
diesen drei Dingern die wir da hatten: Das
-
Erste war ja der Firefox Browser. Da würde
man vermutlich ein Bild beispielsweise
-
sich zusammen bauen, was irgendwie den
Bild Standard nicht ganz erfüllt oder wo
-
irgendwo ein Byte falsch ist, so dass, wenn
der Browser das Bild lädt, sich irgendwo
-
verhaspelt und irgendwo es zu einem Buffer
Overflow kommt, weil z. B. ein Puffer zu
-
klein ausgelegt wird. Oder ich kann
versuchen, es über Javascript Dinge zu
-
machen, aber das ist eigentlich noch was
anderes. Im zweiten Fall, von dem Adobe
-
Reader, sind es auch oft tatsächlich
irgendwelche Bilder. Das Problem bei dem
-
Adobe Reader ist halt so ein bisschen das
der so eine eierlegende Wollmilchsau ist.
-
Ich kann mit dem Teil ja beliebige
Bildformate anzeigen lassen, Text mit
-
eigenen Schriften, dass wäre auch ein
Angriffsvektor, wenn ich da eine eigene
-
Schrift einbauen und die Schrift halt
irgendwie so baue, dass beim Einlesen und
-
beim Parsen und Bauen dieser Schrift der
Reader irgendwie einen Buffer Overflow
-
kriegt. Bis hin zu 3D Elemente, ich habe
gehört, sie haben teile von Flash in den
-
Adobe Reader eingebaut, damit man damit
dann 3D Modelle irgendwie anzeigen und
-
auch schön drehen kann. Was man ja alles
braucht, alles im gleichen Programm, gute
-
Idee! Naja jedenfalls, auch da bieten sich
verschiedenste Angriffsvektoren und was
-
man machen würde ist halt eine Datei
bauen, die halten den Fehler ausnutzt und
-
einen Buffer Overflow erzeugt. Der Angriff
den ich hier gezeigt habe, der ist heute
-
aus verschiedenen Gründen nicht mehr
möglich. Ich musste drei Dinge abschalten
-
um den überhaupt laufen lassen zu können.
Das Eine ist, wie ihr euch wahrscheinlich
-
jetzt schon gedacht habt, naja dann könnte
ich doch einfach sagen ich habe einen
-
bestimmten Programmspeicher, ich habe
einen bestimmten Datenspeicher und was im
-
Datenspeicher liegt, darf niemals nicht vom
Prozessor ausgeführt werden. Das gibt es.
-
Das nennt sich "write XOR execute" und ist
seit einigen Jahren im Kernel und sogar in
-
Hardware drin. Wenn es angeschaltet ist.
Das musste ich ausschalten, dann gibt es
-
noch die Möglichkeit sogenannte Stack
Canaries, also Kanarienvögel, in den Stack
-
einzubauen. Das ist so ein Magic Byte, was
da reingeschrieben wird, und ein bisschen
-
Code, was vor dem rückkehren der Funktion
noch einmal testet, ob dieses Byte, was
-
ich da reingeschrieben habe, also zwischen
den lokalen Variablen und der
-
Returnadresse, moment ich mache das mal
eben grafisch, damit man das sehen kann,
-
genau, also an diese Stelle, die jetzt da
schwarz markiert ist, zwischen dem Return
-
und dem Buffer, würde man ein zur Laufzeit
gewähltes beliebiges Byte reinschreiben
-
und bevor man aus der Funktion
zurückkehrt, würde man testen, ob dieses
-
Byte, was man da vorher reingeschrieben
hat, noch das gleiche ist wie vorher. Wenn
-
nicht, ist ein Buffer Overflow passiert
und dann lässt man das Programm besser
-
sofort abstürzen, anstatt abzuwarten, was
jetzt kommt. Das ist ein Stack Canary.
-
Auch das musste ich ausschalten. Und das
Dritte ist bei modernen Betriebssystemen,
-
mittlerweile haben es glaube ich alle
drin, ist das so, dass jeder Prozess mit
-
einem zufälligen Speicherlayout läuft. Das
heißt, ich kann nicht mehr wirklich sagen
-
wo meine Dinge im Speicher liegen und das
macht es sehr sehr schwierig, weil ich ja
-
irgendwo an den Shell Code, an das Ende
vom Shell Code, eine Adresse schreiben
-
muss, die vorne in diese NOP slide
reinführt. Und wenn ich absolut keine
-
Ahnung habe, wo diese Adresse ist, also
ich kann nicht mehr durch Vergrößern des
-
Buffers oder durch mehr NOPs einfügen kann
ich das irgendwann nicht mehr schaffen,
-
weil es einfach so zufällig ist, dass ich
keine Chance mehr habe herauszufinden, wo
-
zur Laufzeit denn dieser Shell Code liegt,
und wenn ich nicht weiß wo der liegt, kann
-
ich da nicht rein springen, kann ich also
keinen Buffer Overflow Exploit machen.
-
Diese drei Dinge gibt es heute, die sind
in der Regel, also zwei davon macht der
-
Compiler, das Dritte macht das
Betriebssystem, und die musste ich jetzt
-
alle umgehen, um das überhaupt
demonstrieren zu können. Aber die zugrunde
-
liegenden Probleme bestehen weiterhin, die
sind nämlich: Speichermanagement ist
-
fehleranfällig, Menschen machen immer
Fehler, ist oft auch nicht so
-
übersichtlich, Datenformate sind zu
komplex, Programme müssen zu viel
-
gleichzeitig können und das führt alles
dazu, dass halt immer mehr Fehler
-
passieren. Je höher die Komplexität ist,
desto höher ist natürlich die
-
Wahrscheinlichkeit, das ich Fehler mache.
Und das ist nicht gefixt und die
-
Programmiersprachen, die man dafür
verwendet, sind eigentlich einer Zeit
-
entstammend, in der das Internet noch ein
freundlicher Ort war. Man kannte sich und
-
da hat man über Security nicht so richtig
nachgedacht. Es gibt außerdem Nachfolger
-
von dieser Praxis, also diese Praxis, die
ich jetzt hier gezeigt habe, geht halt
-
nicht, aber es gibt Nachfolger davon, also
Modifikationen davon, die sind ein
-
bisschen gewiefter, ein bisschen
trickreicher und die schaffen es dann,
-
unter Umständen eben doch noch so etwas
nicht zu bekommen. Also das funktioniert
-
heute immer noch, wie ihr halt an den
Nachrichten gesehen habt. Oft werden auch
-
nicht alle Gegenmaßnahmen ergriffen, das
ist halt Teil der Betriebssystemhersteller
-
bzw. Benutzer. An der Stelle danke ich und
Fragen?
-
Applaus
-
Herald: Herzlichen Dank haggl für diesen
wundervollen Vortrag! So, Frage-Antwort-
-
Runde, es gibt zwei Mikrofone, die sind
dort und dort, beleuchtet, wer eine Frage
-
hat, stellt sich bitte hinter die
Mikrofone und ich rufe dann auf. Keine
-
Fragen? Das scheint ein ganz schön
umfangreicher Vortrag gewesen zu sein.
-
Haggl: Keine Fragen, oder seid ihr
bedient? Keine Fragen, weil es zu viele
-
gäbe, oder keine Fragen, weil es gar keine
mehr gibt? Achso, eine Entweder-Oder-Frage
-
kann man nicht mit Handzeichen
beantworten, das ist schwierig. lacht
-
Herald: Dort steht jemand, bitte.
Frage: Ich hätte mal eine Frage und zwar:
-
Ich habe die Stelle nicht gefunden, wo die
Return-Adresse ausgerechnet wird und
-
reingeschrieben wird, also der Return-
Wert.
-
Haggl: Ja, die konntest du auch nicht
finden, weil ich die garnicht gezeigt
-
habe. Ich habe dazu noch ein kleines
Pythonskript, das das alles macht. Das ist
-
diese hier. Also der der Shellcode, den
ich vorher gezeigt habe, das ist wirklich
-
nur der Part, der halt execve aufruft mit
/bin/sh und der wird halt hier eingelesen
-
in diesem Pythonskript und dann baue ich
mit dem Pythonskript halt eben ganz viele
-
NOPs davor, das ist an der Stelle, also
hier berechne ich erstmal wie lange diese
-
NOP-slide sein soll und packe halt
den NOP-slide davor, dann kommt noch ein
-
bisschen Padding und Alignment, damit
das alles richtig hinten rauskommt, dass
-
die Rücksprungadresse auch an der
richtigen Stelle liegt im stack. Genau und
-
hier oben habe ich halt diese Target-
Adresse und die kriegst du halt nur raus,
-
wenn du das Programm einmal laufen lässt
und mit dem Debugger schaust, na wo ist
-
denn der Buffer. Und dann kannst du halt
irgendwie eine Adresse in der Mitte von
-
dem Buffer aussuchen, den Shellcode
dahinter packen, ein par NOPs davor, dann
-
tut es das schon. So weit die Theorie, die
Praxis, habe ich schmerzhaft gelernt, ist
-
ein bisschen komplizierter. Genau, also
mehr macht dieses Skript auch nicht, es
-
nimmt den Shellcode, packt NOPs davor,
return-Adresse dahinter, fertig.
-
Frage: Ist die Adresse eine relative oder
eine absolute Adresse?
-
Haggl: Das ist eine absolute Adresse. Und
das ist halt das Ding, an der Stelle
-
greift halt dieses address space layout
randomization, also dieser
-
Abwehrmechanismus vom Betriebssystem, der
dafür sorgt, dass jedes mal, wenn ich das
-
Programm aufrufe, diese Adresse nicht mehr
stimmt. Das kann ich euch demonstrieren.
-
Wenn ich jetzt dieses Ding, was nämlich
der make Befehl macht, weil, wenn ich
-
dieses make exploit mache, was ich euch
eben gezeigt habe, da wird vorher noch mit
-
diesem Befehl hier für den nächsten
Prozess, dieses address space layout
-
randomization ausgeschaltet. Standardmäßig
ist das an im Linux Kernel und wenn ich
-
das nicht mache, sondern einfach nur das
Programm aufrufe und da meine payload
-
reinschiebe, dann gibt es zwar auch einen
segmentation fault, natürlich, aber es
-
gibt halt, ich kriege halt keine shell.
Und das ist genau aus dem Grund, weil der
-
buffer nicht an der Stelle liegt, wo ich
ihn vermutet habe und deswegen meine
-
Rücksprungadresse irgendwo im Speicher
zeigt, da springt er dann hin und, ups,
-
gibts einen segmentation fault. Die beiden
anderen Dinge könnte ich auch noch eben
-
zeigen, ich weiß nicht, wir haben noch
Zeit oder? Die beiden an Dinge könnte ich
-
eben auch noch zeigen, also was man machen
muss, um die beiden anderen
-
Sicherheitsmechanismen abzuschalten. Da
muss man das Programm nämlich mit diesen
-
beiden "-z execstack", das ist dieses was
den Softwarecheck ausschaltet, ob ich
-
gerade im stack versuche Programme, also
generell im Datenspeicher, nein im stack,
-
versuche, Programme auszuführen. Das muss
man ausschalten. Und dann gibt es noch
-
diesen stack protector, dass ist ein stack
canarie, dass wird auch standardmäßig
-
eingebaut. Muss man also auch explizit
ausschalten, um das möglich zu machen. Das
-
heißt , Programme, die mit modernen
compilern und modernen compiler flags
-
kompiliert wurden, sollten trade mark
eigentlich nicht so leicht verwundbar
-
sein. Und wenn dann noch address space
layout randomization dazu kommt, dann ist
-
das schon relativ gut, da muss man schon
echt eine Menge Zeug machen, um dann noch
-
irgendwie reinzukommen. Aber es geht halt
trotzdem.
-
Herald: Gut ich sehe da drüben an dem
Mikrofon noch jemanden, der wartet, bitte.
-
Frage: Kann man nicht einfach relative
Adressen verwenden, um ASLR auszuhebeln
-
und wenn nein, warum nicht?
Haggl: Was meinst du mit relativ, relativ
-
wozu?
Frage: Naja, also ich meine, ah nein, ich
-
habe es gerafft, okay.
Haggl: Aber im Prinzip bist du auf dem
-
richtigen Weg, also was man halt machen
kann, ist, man kann herausfinden, wo die
-
libc zum Beispiel anfängt. Und wenn ich
weiß, wo die libc anfängt, dann weiß ich
-
auch, wenn ich zudem noch die Version von
der libc kenne, die da reingelinkt wurde,
-
weiß ich dann auch, wo bestimmte Dinge aus
der libc im Speicher liegen. Und sobald
-
ich das habe, kann ich halt dann anfangen,
mir wieder Dinge zusammenzubauen. Das geht
-
aber dann eher in Richtung return oriented
programming.
-
Herald: Gut, sieht aus, als sei die Frage
beantwortet, gibt es noch weitere Fragen?
-
Das scheint nicht der Fall zu sein, aus
dem Internet scheint auch keine Frage zu
-
kommen. Dann würde ich hiermit den Vortrag
schließen, herzlichen Dank haggl!
-
Applaus
-
35c3 Abspannmusik
-
Untertitel erstellt von c3subtitles.de
im Jahr 2020. Mach mit und hilf uns!