 Gut, dann wollen wir 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? Drei, vier. 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 aufmachen 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 zackl erläutern. Applaus! Das sollte so nicht aussehen. Moment. Warum sagt man das keiner? So, hallo, dann können wir jetzt sehr anfangen. Ja, also danke für die schöne Einleitung, schön, dass ihr alle da seid, schön, dass ihr so zahlreich erschienen seid. Wie in jedem Jahr eigentlich, gab es auch in 2018, ich tue mal eben die Mauster weg, 2018 wieder ein paar schöne Beispiele oder schön ist ein Anführungszeichen zu setzen. Beispiele von Sicherheitslücken, die ziemlich gravierend sind und die geschlossen werden, grüblicherweise. Ging direkt im Anfang des Jahres los, im Januar wurden im Firefox und mithin auch im Torbrowser, der da ja drauf basiert, ein paar Sicherheitslücken geschlossen, die für die man nun eine Website 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, darf ich leider keine schöne Pressemitteilung zugefunden, nur so ein 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 Abituary 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 Firma Petia und WannaCry, das waren so sympathische Ransomware, die die Festplatte verschlüsseln und den Besitzer dann auffordern Bitcoins zu überweisen in eine bestimmte Adresse, damit der Rechner wieder freigeschaltet werden kann. Auch die waren beide, also ein Teil der Angriffe, die sie benutzt 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 drüber. Das ist der grobe Fahrplan, es gibt jetzt so eine kleine Vorstellungsrunde, ich sag was zu mir, frag was über euch, sag was zum Vortrag, danach machen wir ein paar Grundlagen, die zum Teil sehr technisch uns zäh sind, ich bitte, dass wir uns schuldigen, aber ganz ohne geht es leider nicht. Und dann erkläre ich, wie halt der Exploit funktioniert, so ein Stackbuffer Overflow Exploit, und zum Schluss gibt es, weil die Demokrater uns weitergewogen sind, eine Demonstration, wo ich meinen eigenen Rechner halt mit einem verwundbaren Programm übernehme. Also über mich, ich bin in Chaos und Hexbase 7 aktiv und treib da so ein bisschen mein Umwesen, hab so halb den Hut für Chaos macht Schule und die Kryptopartys auf, wenn es sich nicht vermeiden lässt, mache ich ne Infrastruktur und ähnliche Geschichten. In meinem sonstigen Leben bin ich Informatiker, ich programmiere kleine Mikrocontroller für Soundsysteme, ich mach Judo und bin Musiker. Meine Lieblingsspeise ist DAAL. In dieser Stelle herzlichen Dank an das Küchenteam, die uns während dem Aufbau sehr gut verpflegt haben, unter anderem mit DAAL. So, ein kurzer Funktionscheck. Wer hört mir zu und ist in der Lage und gewillt sein Arm zuheben 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? Das sind alle programmiert. 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. Der Vortrag ist nämlich, ich habe versucht, ihn möglichst einfach zu halten, weil inspiriert ist er von einem Arbeitskollegen, der sich auch auskennt mit Likokontrollern, mit Assembly Code und der mich irgendwann mal fragte oder mehr im Nebensatz, weil ich erwähnte, dass er nicht verstünde, wie es sein kann, dass ein PDF ein Rechner übernehmen kann. Challenge accepted, habe ich mir gedacht, das muss man doch irgendwie erklären können. Dann habe ich den Vortrag gemacht, habe den Talk eingereicht, den Free Talks. Der wurde dann halt ein paar Wochen später auch angenommen. Ich habe ihn nochmal angeschaut und geprüft, ob er den Anforderungen entspricht, ich mir selber gesetzt habe, festgestellt, oh mein Gott, er ist viel zu technisch. Deswegen habe ich den weiter abgespeckt und versucht alle unnötigen Details, alle nicht nötigen Fachwörter alles rauszustreichen, was man nicht unbedingt braucht und das Prinzip eines Stack-Buffer-Overflows oder einen Exploit dessen zu verstehen. Ich habe den 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 da irgendwo ein Prozess, und 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. Ob es ein Office-Dokument sein, ein PDF oder halt auch Daten aus dem Internet, wie HTML-Seiten bei Webbrowsern, sind das halt die Daten, die rein und raus gehen. Daten werden intern im Rechner in dem Speicher abgelegt, und zwar in Paketen von 8, 1 und 0. 1 und 0 sind Witz, das wissen wir wahrscheinlich alle, so ein Paket von 8 davon nennt man ein Byte. Jedes Byte hat eine Adresse im Speicher. Und der Witz ist, dass hier so, wie das 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 einem 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 ein Register-Satz 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ößtendordnungsmäßig sieben 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 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 Programm Speicher 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ächtnissen. Na ja, das stimmt nicht ganz, aber grob. Und der Stack ist wirklich, der Name ist Programm, das ist wie ein Stapel, ich lege unten was drauf, dann lege ich was obendrauf, lege was weiteres obendrauf und ich lege auch Dinge oder hohe Dinge genau und der umgekehrten Reihenfolge wieder runter. Also wie ein Stapelpapier 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 ein Ausschnitt aus einem Programmspeicher. Also ich habe da jetzt beidweise halt so mal irgendwelche Random Zahlen hingemalt. Na ja, das sind keine Random Zahlen, das ist tatsächlich ein Teil eines Shellcodes. Also von dem Ding, was ich gleich benutze, um meinen Rechner aufzumachen. Die sind jetzt von oben nach unten beidweise 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 Beid hat halt so eine Adresse. Das heißt der Prozessor genau, wenn er gesagt bekommt, mach mal hier an der Stelle im Programmcode weiter, dann springt er halt dahin und liest weiter. Und zwar sieht das so aus. Da werden halt Dinge, also der Obcode oder der Befehl, der gerade ausgeführt werden soll, wird eingelesen und wird ausgeführt. Und der könnte dann zum Beispiel so was machen, wie ein Datum außenregister Satz auf den Stack oben drauflegen. Im nächsten Schritt wird dann was anderes oben draufgelegt und dann wird da wieder was runtergeholt und wieder zurück in das Register geschrieben, möglicherweise noch irgendwo mitverrechnet 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 im 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, springen wir irgendwie zurück oder springen wir vor. Aber im Prinzip ist das alles, was ein Rechner macht. Okay, der Shell-Code, den ich gleich benutzen werde, sieht so aus, dass es 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. Das finden wir ganz gerne, wenn wir ein Programm geschrieben haben, dass nicht für jeden Prozessor dieser Welt neu schreiben müssen. Weil das da ist Code, der nur auf diesem Prozessor, in diesem Fall in X86-Prozessor läuft. Wenn ich versuche, den auf einem Arm-Prozessor laufen zu lassen, dann kenne ich nicht den Befehl, mache ich nicht. Beides erreicht man mit sogenannten höheren Programmiersprachen. Eine davon ist C. Das gleiche Programm sieht den C so aus. Das kann man einigermaßen erkennen. Hier ist irgendwie ein String drin offensichtlich, also eine Zeichenkette, die auf slash-bin-slash-sh zeigt. Das ist für Leute, die UNIX kennen, die Standard Shell. Also ein Kommando-Zeilen-Interpreter. Und darunter ist eine Zeile, die ist jetzt 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 was 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, öffnen wir mal eine Datei und liest den Inhalt. Das will ich ja nicht jedes Mal auf der Ebene neu programmieren. Deswegen leg ich mir dafür schöne Bibliotheken an. Mit Funktionen. Funktionen sind Teile, also wiederverwertbare Programmteile, die einen definierten Satz von Eingabeparametern haben. Also ich kann eine Datei öffnen Funktion beispielsweise mitgeben, wo die Datei sich befindet in der Teilsystem und ob ich sie lesend schreiben möchte, oder schreibend, oder beides. Dann haben die einen Funktionsrumpf. Das ist der Teil der Funktion, der was macht. Das tut, was die Funktion tun soll mit den Eingangsparametern. Und dann gibt es noch einen Return-Wert, das ist also ein Rückgabewert, den die Funktion zurückgeht, wenn sie fertig ist mit was immer sie getan hat. Und auf diese Weise baut man sich Bibliotheken und verwendet die immer wieder. Dieses X-Hack VE 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 Funktion aufgerufen wird, nehmen wir mal an, wir hätten eine Funktion, die drei Parameter hat, zwei Parameter hat, Entschuldigung, und drei lokale Variablen. Achso, Variablen habe ich gar nicht erklärt. Variablen ist auch ein Konzept von Programmiersprachen. Da kann ich halt Speicheradressen, entsprechende Namen geben, sodass ich halt als Programmierer sehen kann, wo ich was abgelegt habe. Und Funktionen können lokale Variablen haben. Davon beliebe ich viele. Und die werden eben über ein Stack abgebildet, genauso wie die Funktionsparameter über ein 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 ein Stack läuft. Bin ich noch gerade nicht sicher? Ne, ich glaube, läuft er nicht. Wenn ich jetzt mir eine Funktion vorstelle, die zwei Parameter hat, und drei lokale Variablen, davon soll einer ein Buffer sein, dann passiert, wenn die Funktion aufgerufen wird, folgendes. Zuerst werden die Funktionsargumente oder Parameter auf ein Stack gelegt, und zwar ein umgekehrter Reihenfolge, fragt nicht, ist so. Danach wird die momentane Adresse, oder die nächste Adresse auf ein Stack gelegt, damit das Programm weiß, wenn es auf die Funktion zurückkommt, wo es weitermachen muss. Weil es folgt ja ein Sprung. Dann kommt noch was, was uns nicht interessiert. Und dann kommen die lokale Variablen der Funktion selber. In dieser Fall eine Variable, dann kommt ein Buffer, und dann kommt noch eine Variable. An dieser Stelle ist es sehr schön zu erklären, was ein Buffer ist. Ein Buffer ist eigentlich nicht anderes als eine Variable, die aber mehr Speicher hat. Also es 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. Fünf Beiz groß. Die können beliebig groß sein, das ist sehr gut beliebig, ist nicht ganz richtig. Hängt von der Speichergröße ab, wie ich gestern gelernt habe bei der Demo. Ja, aber im Prinzip sind die nicht begrenzt. Und das Schlimme ist, wenn man so low-level programmiert, selber darum kümmern, dass man die Grenzen einhält und nicht zu viel da reinschreibt. Und dann sind wir schon beim Programm. Das Rechte ist das C-Programm, was ich exploite. Das ist relativ überschaubar. Da oben diese CS-Zeile in Main kann man ignorieren. Das braucht man halt, um zu sagen, hier beginnt das Programm, hier geht das 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 im Weg gelegt haben gestern. Und 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 einen Buffer der Größe 256. Das ist nicht mehr ganz aktuell. In der Demo, die ich gleich zeige, ist ein bisschen größer. Es spielt aber keine Rolle. Danach gibt es eine Bibliotheksfunktion Stringcopies. Die kopiert, was immer in AGV1 liegt. Da muss man jetzt dazu sagen, das ist ein Kommandezeilenparameter. Wenn ich ein Programmaufrufe auf der Kommandezeile 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, Komma, was immer in dem Buffer drinnen steht. Und dann folgt das Return, spricht die Funktion, 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 dahin gelegt hat. Und wenn ich jetzt zu viele Daten in diesen Buffer reinschreibe, der ist links Verkürzte dargestellt, das sind 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 sich interessiert und irgendwann überschreibe ich die Return-Adressen. 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-Pointe. 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 meine Shellcode reinzuladen, also das kleine Programmstück, was ihr eben gesehen habt, was das laufende Programm beendet und das Programm, nämlich eine Kommando-Zeit-Interpreter startet, wenn ich es jetzt schaffe, das da oben reinzuschreiben 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 diesen Buffer reingeschrieben habe. Und jetzt kommt der Punkt, an dem es interessant wird. Was habe ich mir alles anders vorgestellt? Da, sieht man das. Ja, da ist ein bisschen klein, vielleicht. Könnte das lesen nehmen? Da hinten, könnte 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. Ich gehe mir auf den linken Bildschirm ausgründen. Könnte 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 Wahlen ist das Programm, was verwundbar ist. Ich habe eben den Parameter Hackel mitgegeben, also sagt es Hallo Hackel, alles cool. Wenn ich jetzt aber Dinge da reinschreibe, die ein bisschen länger sind, beispielsweise so was, ich hoffe, das ist die richtige Sonntags- und mit preisend Dinge auf der Kommandozeile direkt auszuführen. Dann kriege ich einen Segmentation Fold. Ein Segmentation Fold ist der nette Hinweis vom Betriebssystem. Kollege, du hast gerade Speicher lesen und oder schreiben wollen, auf den du keinen Zugriff hast. Wenn Leute die Exploits schreiben, so was kriegen, das ist das, was normalerweise, das kennt wahrscheinlich jeder, oder irgendwas zu sagen, abstürzt, dann ist das oft ein Segmentation Fold. 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 Grail, weil Segmentation Folds sind oft ein Indiz dafür, dass es da gerade ein Buffer Overflow gegeben hat. Und ein Buffer Overflow, damit kann man eine Menge anfangen. Man kann beispielsweise einen Shellcode reinladen. X, X, D, minus 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 reininjecte, also anstatt jetzt diesen Python-Ding oder dem einfachen Springhuckle, dieses Teil jetzt Python-Python, 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. Aber interessant wird es natürlich, wenn so eine Verwundbarkeit irgendwo an der Stelle sitzt, die aus Netzwerk horcht, wenn man von außen irgendwie Pakete einschleusen kann, die sowas hervorrufen. Und ich dann irgendwie eine Shell oder ähnliches bekomme. Auch interessant wird es, wenn das ein Dienst ist, der aus Gründen mit Rudrechten laufen muss, dann habe ich nämlich auf einmal eine Rud-Shell. Und so weiter und sofort. Also man kann da eine Menge lustige Sachen mitmachen. Ich habe hier etwas anderes vorbereitet. Und zwar, wenn ich jetzt ein Debugger über das Ding laufen lasse. Ein Debugger ist ein Programm, mit dem ich zur Laufzeit reingucken kann in den Speicher und schauen kann, was denn der Prozess oder gerade macht und wo an welche Stelle im Speicher geschrieben wird. Hier sehe ich jetzt nochmal den Quellcode von dem Programm. Das ist gleiche wie eben bis auch, dass der Buffer ein bisschen größer ist. Das spielt aber keine große Rolle. Man hat die Breakpoints gesetzt und Breakpoint heißt, der Debugger viel mehr 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 Spring Copy, 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. Das ist alles mit den richtigen Argumenten. Das ist alles schon hoffentlich konfiguriert. Dann sehe ich hier, das ist ein Ausschnitt aus dem Buffer und zwar irgendwo in den letzten paar Hundert bytes. Da steht ziemlich zufälliger Quatsch drin. Keine Ahnung, was das ist. Das hat irgendeinen Prozess vorher oder vielleicht möglicherweise auch der In-It-Prozess. Also das, was vor meinem eigentlichen Main-Programm läuft, dahin gelegt, keine Ahnung, weiß ich nicht. Und die Return-Adresse steht im Moment auf dieser lustigen kryptischen Zahl. Das wird irgendwas sein, was nach dem Main beendet wurde, also die Funktion Main beendet wurde wahrscheinlich noch irgendwie Speicheraufräume oder sowas. So, jetzt sind da also an der Stelle Spring Copy. Also es ist noch nichts passiert, wenn ich jetzt zum nächsten Breakpoint weiter laufe. Dann sind ganz viele 0x90 Instruktionen. Das ist in die Anweisung für den Prozess, wo man macht mal nix, warte mal, einen Takt lang. Davon habe ich ganz viele da reingeschrieben und irgendwann kommt dann hier dieser Shellcode. 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 gibt es in den Shellcode in diese ganzen No-Operation Instruktionen. Das nennt man eine Nobslide. 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 von Programmausführung zu Programmausführung ändern. Wenn ich zum Beispiel eine neue Variable definiere oder whatever, jedenfalls habe ich die Sachen in der Adresse und deswegen macht man gerne so eine Nobslide. Also man nimmt seinen Shellcode, packt ihn irgendwo in die Mitte, packt davor ganz viele Nobs, wo der Rechler einfach so durchtingelt und nichts tut und dann irgendwann an dem Shellcode ankommt und dahinter packt man ganz viele Return-Adressen, die oben in diese Nobslide reinspringen. Das heißt, egal ob ich den Shellcode, ob ich einen Bereich vor dem Shellcode treffe oder einen Bereich hinter dem Shellcode treffe, es passiert immer, was passieren muss, nämlich er springt in ungünstigsten Fall von hinterm Shellcode, zuvor am Shellcode der dann das tut, was er tut. Und das ist der Buffer Overflow Exploit. Demo Time. Ja, vorbei. Wie ich schon sagte, das ist sehr akademisches Beispiel, weil ich das alles halt sehr, um es einfach zu halten, eben 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 reinschreibt, nicht zu checken, wie groß denn der Puffer ist und mal zu gucken, ob man überhaupt noch da reinschreiben darf. Das stimmt auch, meistens. 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 und noch gar nicht wissen kann, wie groß dieser Puffer sein muss, weil ich 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 Puffer wird oft berechnet. Und wenn ich jetzt bei der Berechnung die Größe, die dann dazu führt, dass der Rechner oder das Programm viel mehr denkt, der Puffer ist riesig groß, dann schreibt das Ding halt weiter. Das ist eine Sache, die oft ausgenutzt wird. Und Daten kommen halt eben ja, 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. Das sind 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 zusammenbauen, was irgendwie den Bildstandard nicht ganz erfüllt, oder wo irgendwo ein Byte falsch ist, so dass, wenn der Browser das Bild lädt, irgendwo sich irgendwo verhaskelt und irgendwo es zu eine Buffer Overflow kommt. Weil zum Beispiel ein Puffer zu klein ausgelegt wird. Oder ich kann versuchen, ü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, dass der so eine Eier liegende Wollmilchstau ist. Ich kann mit dem Teil beliebige Formate anzeigen lassen. Text mit eigenen Schriften, das wäre auch ein angreifes Vektor, wenn ich da eine eigene Schrift einbaue und die Schrift halt irgendwie so baue, dass beim Einlesen und beim Pausen und bauen dieser Schrift der Reader irgendwie ein Buffer Overflow kriegt. Bis hin zu 3D Elemente. Ich habe gehört, Sie haben Teile von Flash in den Adobe Reader eingebaut, damit 3D Modelle irgendwie anzeigen und auch schön drehen kann. Was man ja alles braucht. Alles so gleich ein Programm, gute Idee. Ja, jedenfalls auch da bieten sich verschiedenste Angriffsvektoren und was man machen würde, ist halt eine Datei bauen, die halt einen Feder ausnutzt und ein Buffer Overflow erzeugt. Der Angriff, den ich hier gezeigt habe, der ist heute aus verschiedenen Gründen nicht mehr möglich. Ich musste 3 Dinge abschalten, um den überhaupt laufen lassen zu können. Das eine ist, die ich wahrscheinlich jetzt schon gedacht habe. Na ja, 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 Prozess ausgeführt werden. Das gibt's. Das nennt sich Ride X or Execute und ist seit einigen Jahren im Kernel und sogar ein Hardware drin. Wenn es angeschaltet ist, das musste ich ausschalten. Dann gibt es noch die Möglichkeit, sogenannte Stack Canaries, also Magic Byte, was da reingeschrieben wird und ein bisschen Code, was vor dem Rückkehr in der Funktion nochmal testet, ob dieses Byte, was ich da reingeschrieben habe, also zwischen dem zwischen den lokalen Variablen und der Return Adresse. Moment, ich mach das mal eben grafisch, damit man das sehen kann, genau. Also an diese Stelle, die jetzt da jetzt schwarz markiert ist, vor der zwischen dem Return und dem Buffer würde man 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 es der 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 Shellcode, ans Ende vom Shellcode eine Adresse schreiben muss, die vorne in diese Knops-Lite reinführt und wenn ich absolut keine Ahnung hab, wo diese Adresse ist, also ich kann nicht mehr durch vergrößern des Buffers oder durch mehr Knops einfügen, kann ich das irgendwann nicht mehr schaffen, weil das einfach so zufällig ist, dass ich keine Chance mehr habe herauszufinden, wenn das Shellcode liegt und wenn ich nicht weiß, wo der liegt, kann ich da nicht reinspringen, kann ich also keinen Bufferflow-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, dass ich Fehler mache und das ist nicht gefixt und die Programmiersprachen, die man dafür verwendet, sind eigentlich einer Zeit entstammend, in denen 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 was hinzubekommen. 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 Betriebssystem-Hersteller bzw. Benutzer. An der Stelle danke ich und Fragen. Herzlichen Dank, Hagel, 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 dahin die Mikrofone und ich rufe dann auf. Keine Fragen. Eine ganz schön umfangreich. Warum keine Fragen? Keine Fragen, weil es zu viele gäbe oder keine Fragen, weil es gar keine mehr gibt. Ach so, eine Entweder-Oder-Frage kann man nicht mit Handzeichen beantworten. Das ist schwer. Laut steht jemand, bitte. Ich hätte mal eine Frage und zwar, ich habe die Stelle nicht gefunden, wo die Rationenadresse ausgerechnet wird und reingeschrieben wird. Die Rationenadresse? Ja. Die konntest du auch nicht finden, weil ich die gar nicht gezeigt habe. Moment. Ich habe dazu noch ein kleines Python-Skript, was das alles macht. Das ist diese hier. Also der Shell-Code, den ich vorher gezeigt habe, das ist wirklich nur der Part, der halt EXEC-VE aufruft mit bin SH und da wird halt hier eingelesen in diesem Python-Skript und dann baue ich mit dem Python-Skript ganz viele Knops davor, also hier berechne ich erst mal, wie lang diese Knopslight sein soll und packe halt die Knopslight davor. Dann kommt noch ein bisschen Padding und ein bisschen Alignment, damit das alles richtig hinten rauskommt, dass die Rücksprung-Adresse auch an der richtigen Stelle liegt im Stack. Genau. Und hier oben habe ich halt diese Target-Adresse das Programm einmal laufen lässt und bin lieber geschaut, wo ist denn der Buffer? Und dann kannst du halt hier irgendwie eine Adresse in der Mitte von dem Buffer aussuchen, den Shell-Code dahinter packen, ein paar Knops davor und dann tut's das schon. Soweit die Theorie, die Praxis habe ich schmerzhaft gelernt, ist ein bisschen komplizierter. Genau. Also mehr macht dieses Skript auch nicht. Nimmt man den Shell-Code, packt Knops davor, die Return-Adresse dahinter, fertig. Ist das eine Adresse, eine Relative oder eine absolute Adresse? Das ist eine absolute Adresse, ja. 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-Befil macht weil wenn ich dieses Make-Exploit mache, was ich euch irgendwie gezeigt habe, dann wird vorher noch mit diesem Befehl hier für den nächsten Prozess dieses Address, Space, Layout, Randomization ausgeschaltet. Standartmäßig ist das an, Linuxkörnel. Und wenn ich das nicht mache, sondern einfach nur das Programm aufrufe und da meine Payload rein schiebe, dann gibt's zwar auch ein Second-Fold 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ücksprung-Adresse irgendwo in Speicher zeigt, da springt er dann hin und bupp, es gibt's ein Second-Fold. Die beiden anderen Dinge könnte ich auch noch ihm zeigen, ich weiß nicht, wir haben noch Zeit, oder? Die beiden anderen Dinge könnte ich eben auch noch zeigen, ich muss um die beiden anderen Sicherheitsmechanismen abzuschalten, da muss man das Programm nämlich mit diesen beiden ZXX-Stack das ist dieses, was den Software-Shake ausschaltet, ob ich gerade im Stack-Versuche-Programme, also generell im Datenspeicher, nee, im Stack-Versuche-Programme auszuführen. Das muss man ausschalten und dann gibt's noch diesen Stack-Protector, das ist ein Stack-Canary. Das 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-Flex konfiliert wurden, sollten Trademark eigentlich nicht so leicht verwundbar sein. Und wenn dann noch Address-Based-Layout-Analyse dazu kommt, dann ist das schon relativ gut, da muss man schon echt ein Menge Zeug machen, um dann auch irgendwie reinzukommen. Aber es geht halt trotzdem. Gut, ich sehe da drüben an dem Mikrofon der wartet, bitte, kann man nicht einfach relativ Adressen verwenden, um ASLR auszuhebeln? Und wenn nein, warum nicht? Was meinst du mit relativ, relativ vorzu? Nein, naja, also ich meine ah, nein, ich hab's gerafft, okay. Also aber im Prinzip ist bis auf den richtigen Weg, also was man halt machen kann, ist, man kann herausfinden, wo LPC zum Beispiel anfängt. Und wenn ich weiß, wo die LPC anfängt, dann weiß ich auch, wenn ich zudem noch die Version von der LPC kenne, die da reingelegt wurde, weiß ich dann auch, wo bestimmte Dinge aus der LPC eben Speicher liegen. Und sobald ich das hab, kann ich halt dann anfangen, mir wieder Dinge zusammenzubauen. Das geht aber dann eher in Richtung Return-Oriented-Programming. Gut, sieht aus, als sei die Frage beantwortet, gibt es noch weitere Fragen. Das scheint nicht der Fall zu sein, im Internet scheint auch keine Frage zu kommen. Dann würde ich hier mit den Vortrag schließen. Herzlichen Dank, Hagel.