Lokalisierung und Vermeidung potentieller ...rgraf/downloads/Arbeiten/Lokalisierung... ·...

154
DIPLOMARBEIT Lokalisierung und Vermeidung potentieller Sicherheitsschwachstellen in komplexen Softwaresystemen durchgef¨ uhrt am Studiengang Informationstechnik und System–Management an der Fachhochschule Salzburg vorgelegt von: Roland J. Graf Studiengangsleiter: FH-Prof. DI Dr. Thomas Heistracher Betreuer: DI(FH) Thomas Kurz Salzburg, September 2007

Transcript of Lokalisierung und Vermeidung potentieller ...rgraf/downloads/Arbeiten/Lokalisierung... ·...

  • DIPLOMARBEIT

    Lokalisierung und Vermeidung potentiellerSicherheitsschwachstellen in komplexen

    Softwaresystemen

    durchgefuhrt amStudiengang Informationstechnik und SystemManagement

    an derFachhochschule Salzburg

    vorgelegt von:Roland J. Graf

    Studiengangsleiter: FH-Prof. DI Dr. Thomas HeistracherBetreuer: DI(FH) Thomas Kurz

    Salzburg, September 2007

  • Eidesstattliche Erklarung

    Hiermit versichere ich, Roland J. Graf, geboren am 6. Mai 1964, dass die vorliegendeDiplomarbeit von mir selbstandig verfasst wurde. Zur Erstellung wurden von mir keineanderen als die angegebenen Hilfsmittel verwendet.

    0310032093Roland Graf Matrikelnummer

    ii

  • Danksagung

    Alles Wissen und alle Vermehrung unseres Wissens endetnicht mit einem Schlusspunkt, sondern mit Fragezeichen.

    Hermann Hesse (1877-1962)

    Meinen Eltern gebuhrt an dieser Stelle mein besonderer Dank. Gerade die erstenJahre meiner schulischen Laufbahn waren alles andere als Erfolg versprechend undtrotzdem haben sie mir bei der Wahl meiner Ausbildung stets alle Freiheiten gelassen,mir ihr Vertrauen entgegengebracht und an mich geglaubt.

    Um die Lust am Lernen nicht zu verlieren, braucht es Lehrer, die neben den fachli-chen auch menschliche Werte vermitteln. Ich hatte das Gluck, einige dieser ganz weni-gen Lehrer zu treffen. Stellvertretend mochte ich hier besonders Hr. Prof. Dr. GeroldKerer hervorheben, der nicht nur bereits vor uber 20 Jahren als mein HTL-Lehrer mitseinen fachlichen und padagogischen Fahigkeiten glanzte, sondern mich auch in derFH-Salzburg wieder durch seine auergewohnliche Menschlichkeit und Qualifikationbeeindruckt hat.

    Dank gilt auch meinen Kollegen am Studiengang ITS, die mich in so manchen Ge-sprachen und Diskussionen mit ihren Eingaben, Ideen, Fragen und Hinweisen geleitethaben. Besonders hervorheben mochte ich meinen Diplomarbeitsbetreuer DI(FH) Tho-mas Kurz und allen voran FH-Prof. DI Dr. Thomas Heistracher, welche mich auch alsReviewer mit konstruktiver Kritik sehr unterstutzt haben.

    Von meinen Kommilitonen verdient Dietmar eine Erwahnung. Durch seine kriti-schen Verbesserungsvorschlage hat er mich oft zu einer Mehrleistung getrieben.

    Zuletzt mochte ich noch Sabine danken. Ohne sie ware mein Studium neben demBeruf so gar nicht moglich gewesen. Sie hat mich uber all die Jahre tatkraftig un-terstutzt und mir auch in schweren Zeiten den notwendigen Halt, die Kraft und dieStabilitat gegeben, die ich gebraucht habe. Niemand kann so positiv formulieren, wiesie es tut und so war ich bevorteilt, indem sie als Germanistin all meine Arbeitensprachlich redigiert hat. Ihr gebuhrt jedenfalls mein groter Dank!

    Und wenn Hesse folgend nun all meine Anstrengung zur Vermehrung des Wissensmit einem Fragezeichen endet, dann bleibt noch eine Frage zu stellen: Was kommtjetzt?

    iii

  • Informationen

    Vor- und Zuname: Roland J. GrafInstitution: Fachhochschule Salzburg GmbHStudiengang: Informationstechnik & System-ManagementTitel der Diplomarbeit: Lokalisierung und Vermeidung potentieller

    Sicherheitsschwachstellen in komplexen Soft-waresystemen

    Betreuer an der FH: DI(FH) Thomas Kurz

    Schlagworter

    1. Schlagwort: Software Security2. Schlagwort: Software Vulnerability3. Schlagwort: Code Injection

    Abstract

    This diploma thesis documents the usability of tools to localize potential security vulne-rabilities and evaluates the effectiveness of development methods to avoid them. Mostly,vulnerabilities are based on software bugs and design flaws. This paper provides thebasics of memory segmentation, processor registers and stack frames, before it explainssoftware bugs as the cause of potential software vulnerabilities and their risk potenti-al. A variety of software tools are available to implement Static White Box Tests andDynamic Black Box Tests. Source Code Analysis Tools support the developers to parsefor potential bugs in the source code, Debugging, Tracing and Monitoring Tools helpthe software and security testers to spy on data flows, function calls and flaws in exe-cutable binaries. This document reports the strengths and weaknesses of tested toolsand methods and discusses their expected effectiveness in production environments.Adapted development methods can increase the resistance of software to attacks andunauthorized data manipulations. Finally, an introduction to Defensive Programmingwith helpful programming hints, additional tables and references, code examples, andBest Practices for programmers will be given, which aims at helping developers to writesecure software.

    iv

  • Inhaltsverzeichnis

    Eidesstattliche Erklarung ii

    Danksagung iii

    Informationen iv

    Schlagworter iv

    Abstract iv

    Abbildungsverzeichnis x

    Tabellenverzeichnis xi

    Listingverzeichnis xii

    1 Einfuhrung 1

    1.1 Global vernetzte Sicherheitsschwachen . . . . . . . . . . . . . . . . . . 2

    1.2 Stabile Software(un-)sicherheit . . . . . . . . . . . . . . . . . . . . . . . 3

    1.3 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

    1.4 Uberblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

    2 Grundlagen 6

    2.1 Komplexe Softwaresysteme . . . . . . . . . . . . . . . . . . . . . . . . . 6

    2.2 Speicherorganisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

    2.2.1 Prozessspeicher . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

    2.2.2 Text-Segment . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

    2.2.3 Data-Segment . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

    2.2.4 Heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

    v

  • 2.2.5 Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

    2.3 Register . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

    2.4 Daten und Funktionszeiger . . . . . . . . . . . . . . . . . . . . . . . . . 14

    3 Potentielle Schwachstellen 16

    3.1 Designfehler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

    3.2 Overflow Fehler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

    3.2.1 Stack Overflow . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

    3.2.1.1 Der klassische Stack Overflow . . . . . . . . . . . . . . 19

    3.2.1.2 Frame Pointer Overwrite . . . . . . . . . . . . . . . . . 22

    3.2.2 Heap Overflow . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

    3.2.3 Array Indexing Overflows . . . . . . . . . . . . . . . . . . . . . 26

    3.2.4 BSS Overflow . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

    3.3 Format-String Fehler . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

    4 Lokalisierung potentieller Schwachstellen 32

    4.1 Allgemeines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

    4.1.1 Informationsgewinnung . . . . . . . . . . . . . . . . . . . . . . . 33

    4.1.2 Vollstandige Sicherheitsanalyse . . . . . . . . . . . . . . . . . . 34

    4.1.3 Statische und dynamische Analyseverfahren . . . . . . . . . . . 35

    4.2 Quelltextbasierte Analyse . . . . . . . . . . . . . . . . . . . . . . . . . 35

    4.2.1 Lexikalische Analyse . . . . . . . . . . . . . . . . . . . . . . . . 37

    4.2.1.1 Grep . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

    4.2.1.2 RATS . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

    4.2.1.3 Flawfinder . . . . . . . . . . . . . . . . . . . . . . . . . 40

    4.2.1.4 ITS4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

    4.2.2 Semantische Analyse . . . . . . . . . . . . . . . . . . . . . . . . 42

    4.2.2.1 C++ Compiler . . . . . . . . . . . . . . . . . . . . . . 42

    4.2.2.2 Splint . . . . . . . . . . . . . . . . . . . . . . . . . . . 44

    4.2.2.3 CQUAL . . . . . . . . . . . . . . . . . . . . . . . . . . 45

    4.2.2.4 PREfast und PREfix . . . . . . . . . . . . . . . . . . . 46

    4.2.3 Bewertung der Methoden und Werkzeuge . . . . . . . . . . . . . 47

    4.3 Binarcodebasierte Analyse . . . . . . . . . . . . . . . . . . . . . . . . . 48

    vi

  • 4.3.1 Disassembling . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

    4.3.2 Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

    4.3.3 Tracing und Monitoring . . . . . . . . . . . . . . . . . . . . . . 53

    4.3.3.1 API-Schnittstellenanalyse . . . . . . . . . . . . . . . . 53

    4.3.3.2 Datenflussanalyse . . . . . . . . . . . . . . . . . . . . . 55

    4.3.3.3 Speichermanagementanalyse . . . . . . . . . . . . . . . 57

    4.3.3.4 Speicherabbilder . . . . . . . . . . . . . . . . . . . . . 59

    4.3.3.5 Status- und Fehlerinformationen . . . . . . . . . . . . 59

    4.3.4 Fault Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . 60

    4.3.5 Bewertung der Methoden und Werkzeuge . . . . . . . . . . . . . 62

    4.4 Integrierte Analyse und Uberwachung . . . . . . . . . . . . . . . . . . . 63

    4.4.1 Bounds Checking . . . . . . . . . . . . . . . . . . . . . . . . . . 63

    4.4.2 Uberwachung des Stacks . . . . . . . . . . . . . . . . . . . . . . 64

    4.4.3 Uberwachung von Funktionen . . . . . . . . . . . . . . . . . . . 65

    4.4.4 Uberwachung des Heaps . . . . . . . . . . . . . . . . . . . . . . 66

    4.4.5 Bewertung der integrierten Methoden . . . . . . . . . . . . . . . 68

    5 Vermeidung potentieller Schwachstellen 70

    5.1 Allgemeines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

    5.2 Sicheres Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71

    5.2.1 Threat Modeling . . . . . . . . . . . . . . . . . . . . . . . . . . 72

    5.3 Defensive Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . 73

    5.3.1 Uberprufung der Ein- und Ausgabedaten . . . . . . . . . . . . . 74

    5.3.2 Sichere Zeiger- und Speicherverwaltung . . . . . . . . . . . . . . 74

    5.3.3 Fehlerbehandlung . . . . . . . . . . . . . . . . . . . . . . . . . . 75

    5.3.4 Hilfen zur Fehlersuche . . . . . . . . . . . . . . . . . . . . . . . 76

    5.3.5 Sichere Bibliotheksfunktionen . . . . . . . . . . . . . . . . . . . 78

    5.3.5.1 Fehlerfreie Bibliotheksfunktionen . . . . . . . . . . . . 79

    5.3.5.2 Bibliothekserweiterungen . . . . . . . . . . . . . . . . 80

    5.3.5.3 Wrapper . . . . . . . . . . . . . . . . . . . . . . . . . . 81

    5.4 Sicherere Programmiersprachen . . . . . . . . . . . . . . . . . . . . . . 82

    5.4.1 Sichereres C und C++ . . . . . . . . . . . . . . . . . . . . . . . 82

    5.4.2 Managed Code und Managed Memory . . . . . . . . . . . . . . 83

    5.5 Zusatzliche Techniken . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85

    5.6 Bewertung der Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . 86

    vii

  • 6 Zusammenfassung und Ausblick 88

    6.1 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88

    6.2 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90

    6.3 Trends . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90

    Literaturverzeichnis 93

    Abkurzungsverzeichnis 101

    Anhang 103

    A APIs und Bibliothekserweiterungen 104

    A.1 Die Standardbibliotheken . . . . . . . . . . . . . . . . . . . . . . . . . . 104

    A.1.1 Unsichere POSIX C-Funktionen . . . . . . . . . . . . . . . . . . 104

    A.1.2 Unsichere Windows CRT-Funktionen . . . . . . . . . . . . . . . 105

    B Protokolle 108

    B.1 Lexikalische Quelltextanalysen . . . . . . . . . . . . . . . . . . . . . . . 108

    B.1.1 grep Protokoll . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108

    B.1.2 ITS4 Analyseprotokoll . . . . . . . . . . . . . . . . . . . . . . . 109

    B.1.3 RATS Analyseprotokoll . . . . . . . . . . . . . . . . . . . . . . 111

    B.1.4 Flawfinder Analyseprotokoll . . . . . . . . . . . . . . . . . . . . 113

    B.2 Semantische Quelltextanalysen . . . . . . . . . . . . . . . . . . . . . . . 115

    B.2.1 Microsoft C/C++ Compiler Analyseprotokoll . . . . . . . . . . 115

    B.2.2 GCC Compiler Analyseprotokoll . . . . . . . . . . . . . . . . . . 117

    B.2.3 Splint Analyseprotokoll . . . . . . . . . . . . . . . . . . . . . . . 118

    C Listings 120

    C.1 Absicherung des Stacks uber Security Cookies . . . . . . . . . . . . . . 120

    C.2 Einfache Speicheruberwachung in C++ . . . . . . . . . . . . . . . . . . 122

    C.3 Defensive Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . 123

    C.3.1 Uberprufung der Eingabedaten . . . . . . . . . . . . . . . . . . 123

    C.3.2 Zeiger und Speicherbehandlung . . . . . . . . . . . . . . . . . . 124

    C.4 Sichere Programmiersprachen . . . . . . . . . . . . . . . . . . . . . . . 125

    C.4.1 Sicheres C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125

    viii

  • C.4.2 Automatisches Bounds Checking in C# . . . . . . . . . . . . . . 126

    C.5 Sichere Bibliotheksfunktionen . . . . . . . . . . . . . . . . . . . . . . . 127

    C.5.1 Sicherung der Funktionen uber Return Codes . . . . . . . . . . 127

    C.5.2 Sicherung von Funktionen uber Exceptions . . . . . . . . . . . . 128

    D Sicherheits-Tools und Bibliotheken 129

    D.1 Statische Analysewerkzeuge . . . . . . . . . . . . . . . . . . . . . . . . 129

    D.2 Dynamische Analysewerkzeuge . . . . . . . . . . . . . . . . . . . . . . . 132

    D.3 Sonstige Werkzeuge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136

    E Good Practices fur sichere Software 137

    E.1 Ein- und Ausgabedaten . . . . . . . . . . . . . . . . . . . . . . . . . . . 137

    E.2 Zeiger- und Speicherbehandlung . . . . . . . . . . . . . . . . . . . . . . 138

    E.3 Fehlerbehandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138

    E.4 Hilfe zur Fehlersuche . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140

    F Weiterfuhrende Online-Quellen 141

    F.1 Dokumente und Links zu Codesicherheit . . . . . . . . . . . . . . . . . 141

    F.2 News, Newsletter und Mailing-Listen . . . . . . . . . . . . . . . . . . . 141

    ix

  • Abbildungsverzeichnis

    2.1 Typisches Speicherabbild einer laufenden Applikation . . . . . . . . . . 10

    2.2 Daten- und Funktionszeiger . . . . . . . . . . . . . . . . . . . . . . . . 14

    3.1 Manipulationen durch einen Stack Overflow . . . . . . . . . . . . . . . 21

    3.2 Manipulationen durch einen Heap Overflow . . . . . . . . . . . . . . . . 25

    3.3 Manipulationen durch einen Off-By-One Overrun . . . . . . . . . . . . 26

    4.1 Debugging Session innerhalb einer Applikation . . . . . . . . . . . . . . 52

    4.2 APISPY beim Aufspuren sicherheitskritischer Funktionen . . . . . . . . 54

    4.3 RegMon beim Protokollieren von Zugriffen auf die Windows Registry . 57

    4.4 Stackschutzmechanismen mit Security Cookies . . . . . . . . . . . . . . 65

    4.5 Fehlermeldung nach einem Heap Overflow . . . . . . . . . . . . . . . . 67

    x

  • Tabellenverzeichnis

    3.1 Ausgewahlte Format-String Platzhalter der printf-Familie . . . . . . . . 30

    4.1 Zusammenstellung einiger Fuzzing Werkzeuge . . . . . . . . . . . . . . 61

    A.1 Unsichere POSIX-Funktionen . . . . . . . . . . . . . . . . . . . . . . . 105

    A.2 Unsichere Windows CRT-Funktionen . . . . . . . . . . . . . . . . . . . 106

    A.3 Template Overloads fur unsichere Windows CRT-Funktionen . . . . . . 107

    D.1 Auswahl einiger statischer Analysewerkzeuge . . . . . . . . . . . . . . . 131

    D.2 Auswahl einiger dynamischer Analysewerkzeuge . . . . . . . . . . . . . 135

    D.3 Auswahl einiger Sicherheitswerkzeuge . . . . . . . . . . . . . . . . . . . 136

    xi

  • Listingverzeichnis

    2.1 Programm mit Variablen verschiedener Speicherklassen . . . . . . . . . 7

    3.1 Beispielprogramm StackOverflow.c . . . . . . . . . . . . . . . . . . . . 20

    3.2 Stackdump innerhalb des Programms StackOverflow.c . . . . . . . . . . 20

    3.3 Beispielprogramm HeapOverflow.c . . . . . . . . . . . . . . . . . . . . . 23

    3.4 Beispiel eines Off-By-One Fehlers im Programm StackOverflow.c . . . . 26

    3.5 Frame Pointer Manipulation durch Off-By-One Fehler . . . . . . . . . . 27

    3.6 Beispielprogramm PrintDemo.c . . . . . . . . . . . . . . . . . . . . . . 29

    3.7 Stackdump innerhalb des Programms PrintDemo.c . . . . . . . . . . . 30

    4.1 Testprogramm zur Codeanalyse mit einem C++ Compiler . . . . . . . 42

    4.2 Disassembliertes Programm PrintfDemo.c . . . . . . . . . . . . . . . . . 50

    5.1 Erweiterung zur Fehlersuche in einer DEBUG-Version . . . . . . . . . . 77

    C.1 Prolog- und Epilog-Erweiterungen zur Behandlung von Security Cookies 120

    C.2 Include-Datei zum Uberladen des new-Operators . . . . . . . . . . . . . 122

    C.3 Quelltext mit Buffer Overflow und Memory Leak Fehler . . . . . . . . . 122

    C.4 Defensive Programmierung innerhalb der .NET Standard Library . . . 123

    C.5 Zeiger- und Speicherbehandlung bei defensiver Programmierung . . . . 124

    C.6 Auszug aus einem STL Programm mit Smart-Pointers und Strings . . . 125

    C.7 C# Programm mit einem Array Indexing Fehler . . . . . . . . . . . . . 126

    C.8 Fehlerauswertung mittels GetLastError() . . . . . . . . . . . . . . . . . 127

    C.9 Fehlerbehandlung uber Exceptions in einem korrekten C# Code . . . . 128

    xii

  • 1

    Einfuhrung

    An application should be considered unsafe until demonstrated to be otherwise.

    (Swiderski, 2004)

    Die Haufung von Veroffentlichungen kritischer Sicherheitslucken in Applikationen, Netz-

    werkdiensten und Betriebssystemen macht deutlich, dass ein Groteil der Applikationen

    und Computersysteme noch immer nicht sicher genug und ausreichend geschutzt ist.

    Technische Detailinformationen uber bestimmte Einbruchsmoglichkeiten in Computer-

    systeme und die ausgenutzten Schwachstellen werden grotenteils online publiziert1.

    Umfangreiche Beschreibungen der oftmals kreativen Methoden der Angreifer und der

    moglichen Abwehrmethoden sind jedem Interessierten frei zuganglich. Diese machen

    auch immer wieder deutlich, dass ein Groteil der Sicherheitsschwachstellen die Fol-

    ge von Design- und Codierungsfehlern in einzelnen Teilen der Software ist. Ebenso

    fallt dabei auf, dass bestimmte Fehler besonders haufig als Grund fur eine Sicherheits-

    schwachstelle genannt werden.

    Die Softwareindustrie wird vermehrt angehalten, fehlerfreie und sichere Software zu

    entwickeln. Der Druck auf die Softwareentwickler steigt. Trotz der bekannten Mangel

    1Im Internet werden fast taglich Nachrichten von kritischen Softwarefehlern und Sicherheitsluckenveroffentlicht. Security Online Archive (z.B. SANS, CERT), einschlagige Mailinglisten, namhafte Un-ternehmen im Bereich Computer- und Netzwerksicherheit und News-Dienste (z.B. Heise Security,SecurityFocus, Computer Crime & Intellectual Property Section), selbst Hacker und Cracker liefernInformationen dazu grotenteils frei Haus. Eine Reihe weiterer Quellen vervollstandigen diese Berich-te und stellen Auswertungen uber Trends, Statistiken und Top-10 Listen uber mehr oder wenigererfolgreiche Attacken auf verwundbare Computersysteme zur Verfugung. Die Gesamtheit dieser Infor-mationen ergibt einen aktuellen Lagebericht uber IT-Sicherheit, sicherheitskritische Softwaremangelund deren Ursachen. Eine Liste ausgewahlter Quellen findet sich im Anhang F am Ende dieses Doku-ments.

    1

  • 1. Einfuhrung 2

    und deren technischer Ursachen scheinen sie derzeit aber kaum in der Lage, diesen

    Forderungen nachzukommen und Software herzustellen, die im Umfeld der globalen

    Vernetzung und Bedrohungen bestehen kann.

    1.1 Global vernetzte Sicherheitsschwachen

    Die Firma Sun Microsystems2 hat vor einigen Jahren schon in der VisionThe Net-

    work is the Computer die hochgradige Vernetzung der Informationstechnologie und

    die damit einhergehenden technologischen Veranderungen vorhergesehen. Mittlerweile

    sind fast alle Computer uber ein Netzwerk oder das weltumspannende Internet mit-

    einander verbunden. Desktop-Computer, Server und Router, Pocket-Computer, mobile

    Telefone, Embedded Systems, Fernseher, Multimedia Systeme, jede Menge mikropro-

    zessorgesteuerte Gerate und eine Unzahl von Peripheriegeraten sind Teile eines oder

    des globalen Netzwerks geworden. Dass sich durch diese Vernetzung auch das Bedro-

    hungspotential durch mogliche Angriffe aus dem Netz (Remote Exploits) vervielfacht

    hat, wird dem Anwender und der Softwareindustrie aber erst heute immer mehr und

    oftmals schmerzlich bewusst. Mit der globalen Vernetzung verschiedenster Gerate und

    Systeme untereinander wachst auch der Druck, die Sicherheitsvorkehrungen bei Com-

    putersystemen und allen Systemteilen entsprechend anzupassen.

    Konnte fruher noch von lokalen Bedrohungen und Angriffen (Local Exploits), von lo-

    kalen Sicherheitsschwachstellen und einem lokalen Risiko ausgegangen werden, so sind

    sowohl die Gefahrenpotentiale als auch die Angriffsziele mittlerweile im globalen Netz

    verteilt und somit auch die Auswirkungen globaler Natur. Einzelne Applikationen sind

    Teile eines vernetzten und komplexen Systems. Mit dem Internet verbundene Syste-

    me stellen dabei ein besonders groes Risiko und manchmal auch einen besonderen

    Reiz fur Angreifer dar. Das Internet ist eine feindselige Umgebung, deshalb muss der

    Programmcode so entworfen sein, dass er einem Angriff widerstehen kann. Vernetzte

    Systeme bedurfen also einer expliziten Sicherung gegen mogliche Angriffe. Jede poten-

    tielle Schwachstelle eines Systemglieds mindert die Sicherheit des gesamten Systems

    oder stellt sie gar in Frage. Ein einzelner Sicherheitsmangel einer Applikation kann

    2http://www.sun.com

    http://www.sun.com

  • 1. Einfuhrung 3

    schon fur den Angriff des Gesamtsystems missbraucht werden. Ziel und Voraussetzung

    fur ein sicheres Computersystem ist demnach die Sicherheit jeder einzelnen Kompo-

    nente. Nur so kann die Sicherheit des Gesamtsystems gewahrleistet werden und ein

    System den Attacken und Gefahren desWild Wild Web[26, S. 5] standhalten. Die

    Entwicklung fehlerfreien Quellcodes ist langst nicht mehr genug, wenngleich eine der

    unabdingbaren Voraussetzung fur sichere Software.

    1.2 Stabile Software(un-)sicherheit

    Wenn von Sicherheitslucken in Computersystemen berichtet wird, handelt es sich fast

    immer um Fehler im Bereich der Softwareentwicklung, also Fehler im Programmcode.

    Fehler in der Hardware oder den angewandten Richtlinien, auch wenn daruber seltener

    berichtet wird, sind ebenfalls moglich und nicht minder gefahrlich. Viele dieser oft-

    mals lange unentdeckten Fehler stellen massive Sicherheitslucken dar. Sie bieten eine

    Angriffsflache fur mogliche Attacken gegen einzelne Applikationen, Computersysteme

    oder gesamte Netzwerke.

    Vor einigen Jahren galt es noch als ausreichend stabile Software zu entwickeln. Es

    genugte, wenn eine Applikation die Anforderungen der Endbenutzer erfullte. Zusatzliche

    sicherheitsrelevante Forderungen wurden kaum erhoben. Softwareentwickler wurden an-

    gehalten soliden, stabilen, wartbaren und erweiterbaren Code zu schreiben. In dieser

    Zeit spielte Codesicherheit selbst in klassischen Standardwerken der Softwareentwick-

    lung wie [35] und [36] kaum eine Rolle. Gute Software stellte sich dem Benutzer aus-

    schlielich als stabil laufende Software dar. Dem Schutz vor mutwilligen Manipulationen

    wurde kaum Beachtung geschenkt.

    Mittlerweile wird vermehrt und ausdrucklich die Entwicklung sicherer Software ge-

    fordert. Langsam beginnen auch Softwareproduzenten und Benutzer ein allgemeines

    Sicherheitsbewusstsein zu entwickeln. Fur eine sichere Software sind ein sicherer Code

    und ein sicheres Design die unabdingbaren Voraussetzungen. Sichere Software meint in

    diesem Zusammenhang, dass sowohl das Design als auch die Implementierung im Hin-

    blick auf die Abwehr potentieller Gefahren und Attacken entworfen wurden. Auch wenn

    fehlerfreier Code nicht automatisch eine sichere Software garantiert, so gilt ein Gutteil

  • 1. Einfuhrung 4

    der Aufmerksamkeit dem Ziel, fehlerfreien Code zu entwickeln. Mit welchen Methoden

    dieses Ziel letztlich erreicht werden kann, ist eine der zentralen Fragestellungen dieser

    Arbeit.

    1.3 Motivation

    Software- bzw. Codesicherheit kann nicht ohne entsprechenden Einsatz und ohne spe-

    zielle Methoden schon wahrend der Entwicklung und wahrend des gesamten Lebens-

    zyklus einer Applikation erreicht werden. Die Bedrohungsmodellierung (Threat Mo-

    deling) hilft Gefahrdungspotentiale fruhzeitig zu identifizieren, zu evaluieren, sie zu

    dokumentieren und Gegenmanahmen schon in der Designphase zu entwickeln. Der

    Entwicklungszyklus (Development Life Cycle) und der Sicherheitszyklus (Security Life

    Cycle) sind untrennbare Teile der Entwicklung einer Applikation. Sicherheitsprozesse

    umfassen die Spezifikationen, den Quelltext, die Dokumentation und den Test und sind

    Teil eines sicheren Software-Entwicklungszyklus. Software muss aktiv und explizit nach

    bestimmten Sicherheitskriterien entwickelt werden, um moglichen gezielten Angriffen

    standzuhalten. Die verwendeten Modelle, die Methoden und die Implementierungen

    werden stetig angepasst und notigenfalls erweitert, um im veranderten Risikoumfeld zu

    bestehen und den standig neuen Anforderungen zu entsprechen. Begleitende Manah-

    men wahrend aller Entwicklungsphasen einer Applikation oder eines Softwaremoduls

    schaffen erst die Voraussetzungen fur die Schaffung sicherer Softwaresysteme.

    Basierend auf den oben genannten Forderungen sind das Ziel und die Motivation dieser

    Arbeit die Untersuchung und die Diskussion potentieller Sicherheitsschwachstellen. Der

    Fokus richtet sich vorwiegend auf die Implementierung, die Methoden zur Lokalisierung

    von Codefehlern und die systematische Vermeidung von Schwachstellen in komplexen

    Softwaresystemen. Der folgende kurze Uberblick beschreibt die einzelnen Kapitel der

    vorliegenden Arbeit.

  • 1. Einfuhrung 5

    1.4 Uberblick

    Das folgende Kapitel 2 fuhrt in ausgewahlte technische Grundlagen der Software-

    entwicklung ein und erklart die zum Verstandnis notwendigen Begriffe sowie das Spei-

    chermanagement einer Applikation. Nachdem ein Groteil der Systemsoftware nach

    wie vor in C/C++ programmiert ist, werden diese beiden Programmiersprachen auch

    bevorzugt in die Erklarungen einflieen.

    In Kapitel 3 werden potentielle Sicherheitsschwachstellen einer Software und dar-

    auf basierende Angriffsmethoden vorgestellt. Einige einfache Beispiele zeigen die prak-

    tische Umsetzung und Einfachheit eines Buffer Overflow Angriffs auf ungesicherte Soft-

    ware.

    Das Kapitel 4 untersucht ausgewahlte Methoden der systematischen Lokalisierung

    potentieller Code- und Sicherheitsschwachstellen. Zur Anwendung kommen dabei quell-

    codebasierte und binarcodebasierte Analysemethoden. Nachdem die manuelle Prufung

    von Code oft nicht effizient genug, sehr aufwandig und teuer ist, werden ebenso Werk-

    zeuge zur automatischen Softwareanalyse gepruft.

    In Kapitel 5 steht die Vermeidung potentieller Schwachstellen im Vordergrund. Schwer-

    punkt ist dabei die Diskussion von Methoden zur Erstellung von sicheren Designs und

    zur Entwicklung sicherer Implementierungen. Das schon fur die Designphase empfoh-

    lene Threat Modeling bleibt hier ebenso wenig unbehandelt wie die Anwendung der

    Prinzipien des defensiven Programmierens.

    In Kapitel 6 schliet ein kurzer Ausblick in die Zukunft die Arbeit ab. Darin wird

    erlautert, wie aus der Sicht von Experten die Sicherheit komplexer Softwaresysteme in

    Zukunft gewahrleistet werden konnte. Die kurze Zusammenfassung schliet mit der

    Beantwortung der Frage ab, ob die aktuellen Methoden der Softwareentwicklung schon

    heute ausreichen wurden, um die Sicherheit komplexer Softwaresysteme sicherzustellen

    oder ob erst in Zukunft eine echte Softwaresicherheit moglich sein wird.

  • 2

    Grundlagen

    Fast alle Sicherheitslucken basieren, wie aus den im Kapitel 1 angefuhrten Quellen her-

    vorgeht, auf Programmfehlern und ein Groteil aller Angriffe basiert auf dem Prinzip

    der Speichermanipulation. Selbst wenn ein Programm im Normalbetrieb uber langere

    Zeit stabil lauft, so bedeutet dies nicht zwingend, dass sich keine Fehler im zugrunde-

    liegenden Programmcode befinden. Erst die Konfrontation eines Programms bzw. einer

    Funktion mit fur den Regelbetrieb nicht vorhergesehenen Daten oder Situationen kann

    Fehler hervorrufen. Jeder einzelne Fehler kann sowohl die Applikation selbst als auch

    das Gesamtsystem in einen verwundbaren oder nicht geplanten Zustand versetzen.

    Sowohl Design- als auch Implementierungsfehler entstehen nicht zwingend, aber oft

    als Folge der Komplexitat eines Quelltextes oder einer Softwarearchitektur. Bevor die

    technischen Grundlagen des Speichermanagements einer Applikation erklart werden,

    wird der Begriff komplexe Softwaresysteme eingefuhrt.

    2.1 Komplexe Softwaresysteme

    Die Definition eines komplexen Softwaresystems kann gerade aus der Sicht der Soft-

    wareentwicklung eindeutig festgelegt werden. Unter komplex kann jede Software be-

    zeichnet werden, welche aufgrund ihres Umfangs nicht mehr ohne weiteres mit allen

    Funktionen, deren Wechselwirkungen und deren Auswirkungen auf das Gesamtsystem

    erfasst werden kann.

    6

  • 2. Grundlagen 7

    Komplexe Applikationen neigen zu mehr und schwer zu entdeckenden Fehlern. Zeit-

    gemae und fortgeschrittene Entwicklungsmethoden versuchen der Komplexitat durch

    Modularisierung und Aufteilung in uberschaubare Funktionseinheiten entgegenzuwir-

    ken. Dass dieses Vorhaben nicht zwingend zum Erfolg fuhren muss, zeigt die Zahl der

    Veroffentlichungen (z.B. Bug Reports) und Fehlerkorrekturen (Bug Fixes) komplexer

    Software der letzten Jahre1.

    2.2 Speicherorganisation

    Dieses Kapitel fuhrt einige Begriffe der Speicherverwaltung (Memory Management) ein.

    Es erklart die Segmentierung des Speichers (Memory Segmentation) und deren Zweck.

    Diese Beschreibung zieht als Beispiel die Segmentierung und Speicherverwaltung einer

    32 Bit Intel x86-Architektur (IA-32)[28] heran. Das beschriebene Prinzip gilt jedoch

    ebenfalls fur fast alle anderen gangigen Prozessorarchitekturen.

    Im folgenden Listing 2.1 wird ein kurzes C-Programm in Auszugen gezeigt. Es ver-

    wendet Variablen verschiedenster Typen und Speicherklassen. Dieses Programm weist

    eine Reihe von Fehlern auf, welche - wie sich im Laufe dieses Dokuments noch zeigen

    wird - ernste Sicherheitsschwachstellen darstellen. In den folgenden Erlauterungen wird

    wiederholt auf diesen Quellcode oder Teile davon Bezug genommen.

    1 int _iGlobalValue; // Var im BSS Segment

    2 static char _szGlobalMsg[] = "text"; // Var im Data Segment

    3

    4 char* foo(const char *str1, const char* str2)

    5 {

    6 static int iLocal = 100; // Var im Data Segment

    7 char szBuffer[20]; // Puffer auf Stack

    8 strcpy( szBuffer, str1 ); // => Stack Overflow Schwachstelle

    9 ...

    10 return szBuffer; // Pointer auf Stackpuffer

    11 }

    1Haufig wird z.B. bei groeren Service Packs von Betriebssystemen und Office Paketen die An-zahl der behobenen Fehler mit einigen Hundert angegeben. Microsoft veroffentlicht in einem monat-lichen Updatezyklus Service Packs, Updates und Patches und beziffert die Anzahl der kritischen undzusatzlich geschlossenen Sicherheitslucken im Schnitt mit etwa 20 Fehlern pro Monat. Siehe dazu auchhttp://www.microsoft.com/germany/technet/sicherheit/bulletins/aktuell/default.mspx

    http://www.microsoft.com/germany/technet/sicherheit/bulletins/aktuell/default.mspx

  • 2. Grundlagen 8

    12

    13 int main(int argc, char *argv[])

    14 {

    15 static short iLen; // Var im BSS Segment

    16 char* pBuff1, pBuff2; // Vars auf Stack

    17

    18 iLen = strlen( argv[1] ); // => Integer Overflow Schwachst.

    19 pBuff1 = (char*)malloc( iLen ); // Allokiert Puffer auf Heap

    20 for( int i=0; i unsichere Funktion strcpy()

    23 // => uninitial. Zeiger pBuff2

    24 printf( pBuff1 ); // Format String Schwachstelle

    25 // => unsichere Funktion printf()

    26 free( pBuff1 ); // Freigabe des Pufferspeichers

    27 pBuff2 = foo(argv[1]); // => Illegaler Zeiger in pBuff2

    28 free( pBuff1 ); // => Double Free Fehler

    29 return 0;

    30 }

    Listing 2.1: Programm mit Variablen verschiedener Speicherklassen

    2.2.1 Prozessspeicher

    Ein Computerprogramm ist im klassischen Fall eine ausfuhrbare Datei2 (Executable),

    welche auf einem Datentrager gespeichert ist. Dabei kann es sich zum Beispiel, wie un-

    ter Linux und nahezu allen Unix-Derivaten verwendet, um Dateien im Executeable and

    Linking Format (ELF) handeln [14]. Microsoft Windows Plattformen verwenden dazu

    Dateien im sogenannten Portable Executable Format (PE Format) [16]. Diese Dateien

    beinhalten nicht nur den ausfuhrbaren Programmcode und dessen statische Daten, son-

    dern beschreiben die Objektdatei. Sie speichern ebenso zusatzliche Informationen zum

    Starten der Applikation und zum Verwalten des Speichers. Wird nun ein Programm

    gestartet, so werden, entsprechend der codierten Informationen im Optional Header 3,

    2Ausfuhrbare Scriptdateien, wie sie z.B. unter Unix-basierten Systemen haufig vorkommen, sindvon diesen Betrachtungen ausgenommen. Diese konnen nicht direkt ausgefuhrt werden, sondernbenotigen ein zusatzliches Programm (Interpreter), welches die einzelnen Script Statements inter-pretiert und zur Ausfuhrung bringt.

    3Diese Bezeichnung ist eigentlich irrefuhrend, da dieser Header nicht optional ist und unbedingtnotwendige Informationen zur Groe des beim Start benotigten Speichers beinhaltet.

  • 2. Grundlagen 9

    Teile dieser Objektdatei vom Program Loader in den Hauptspeicher geladen, der Spei-

    cher entsprechend konfiguriert und der Programmcode zur Ausfuhrung gebracht. Der

    Start im Speicher erfolgt durch den Aufruf einer speziellen Funktion (Startup-Routine)

    an einer bestimmten Adresse (Einsprungadresse). Im Listing 2.1 ist dieser Einsprung-

    punkt (Entry Point) die Funktion main. Ein laufendes Programm wird als Prozess

    bezeichnet [23].

    In modernen Betriebssystemen wird jedem laufenden Prozess ein virtueller Adressraum

    zur Verfugung gestellt, welcher von der Memory Management Unit (MMU) in physische

    Speicheradressen umgesetzt wird. Einem Prozess stehen nun separat organisierte Spei-

    cherregionen bzw. Speichersegmente (Memory Segments) innerhalb seines Adressbe-

    reichs zur Verfugung, in denen sich sein Programmcode und statische Daten befinden

    und auch temporare Daten abgelegt werden konnen. Typische Segmente innerhalb des

    Prozessspeichers sind das Text-, Data- und BSS-Segment sowie der Stack und der

    Heap einer Applikation (siehe Abbildung 2.1). In den folgenden Abschnitten werden

    diese Begriffe bzw. Speicherbereiche detailliert beschrieben [31].

    2.2.2 Text-Segment

    Im Text-Segment bzw. Code-Segment werden die maschinenlesbaren Instruktionen, also

    jener Programmcode, welchen die Central Processing Unit (CPU) ausfuhrt, abgelegt.

    Dieses Segment ist als read-only markiert, das heit, es kann nur lesend darauf zuge-

    griffen werden. Damit kann der ausfuhrbare Code des Prozesses weder versehentlich

    noch mutwillig modifiziert werden. Jeder Versuch, den Speicher in diesem Segment zu

    manipulieren, wurde sofort zu einer entsprechenden Ausnahmebehandlung (Exception)

    und zu einem Programmabbruch fuhren.

    2.2.3 Data-Segment

    Im Data-Segment werden nur bestimmte Daten des Prozesses, jedoch kein ausfuhrbarer

    Code abgelegt. Das Data-Segment hat eine feste, beim Programmstart zugewiesene

    Groe und nimmt alle vor dem eigentlichen Programmstart initialisierten globalen Va-

    riablen (z.B. primitive Variablen, Arrays, Puffer, Strukturen, Zeiger, Objektdaten) auf.

  • 2. Grundlagen 10

    Das Data-Segment kann wahrend der Programmausfuhrung gelesen und beschrieben

    werden, um den Inhalt der dort gespeicherten Variablen wahrend der Laufzeit andern

    zu konnen.

    DataBSS

    Heap

    Stack

    Verfgbarer Speicher

    Text0x08000000

    0xC0000000hoheAdresswerte

    niedrigeAdresswerte

    dynamisches Wachstum

    dynamischesWachstum

    Funktionsparameter

    vorhergehendeStack Frames

    Funktion Return Address.gesicherter Frame Pointer

    Lokal deklarierte Variablen und Puffer

    Func

    tion

    Sta

    ck F

    ram

    e

    optionale Prozessdaten

    Abbildung 2.1: Typischer Prozessspeicher- und Stackaufbau einer C/C++ Applikation

    Der BSS-Bereich4 ist ein Unterbereich des Data-Segments. Er nimmt nicht-initialisierte

    globale und nicht-initialisierte statische Variablen auf (siehe Listing 2.1 die Variable

    _iGlobalValue in Zeile 1 und die lokale statische Variable iLen in Zeile 15), wohin-

    gegen alle initialisierten globalen und statischen Variablen auerhalb des BSS-Bereichs

    abgelegt werden (siehe Listing 2.1, Zeile 2 und 6 ). Wird das Programm gestartet, wird

    der BSS-Bereich in der Regel noch vor dem eigentlichen Programmstart durch das Be-

    triebssystem mit Nullen gefullt. Numerische Werte erhalten dadurch also alle den Wert

    0, Strings sind den Konventionen der Programmiersprache C und C++5 entsprechend

    immer mit dem Zeichen \0 (ASCII 0) abgeschlossen und haben damit auch die Lange

    4BSS steht als Abkurzung fur Block Started by Symbol5Ein Groteil der Betriebssysteme und Systemprogramme ist in der Programmiersprache C/C++

    implementiert. In diesen Sprachen ist eine Zeichenkette (String) per Definition eine Folge von Zeichen(ASCII-Zeichen) in einem char -Array, welche immer mit einem ASCII 0 (0 Byte, welches nur 0-Bitsenthalt) abgeschlossen sein muss. A string is a contiguous sequence of characters terminated by andincluding the first null character. [...] A pointer to a string is a pointer to its initial (lowest addressed)character. The length of a string is the number of bytes preceding the null character and the valueof a string is the sequence of the values of the contained characters, in order. [11, S. 164] ExpliziteLangenangaben werden also nicht gespeichert. Wurde das 0-Byte als Ende-Zeichen fehlen, wurdenString-verarbeitende Funktionen diese Zeichenkette als so lange interpretieren, bis zufallig ein 0-Byteim Speicher vorkommt.

  • 2. Grundlagen 11

    0 [11]. So wird sichergestellt, dass sich keine unerwunschten Werte in den uninitialisier-

    ten Variablen befinden, vor allem aber auch keine Daten eines vorangegangenen und

    wieder terminierten Prozesses, der diesen Speicherbereich zuvor verwendet bzw. mit

    eigenen Daten beschrieben hat.

    2.2.4 Heap

    Jeder Prozess hat die Moglichkeit, erst wahrend der Programmausfuhrung Speicher

    vom Betriebssystem anzufordern. Dafur werden eigene Bibliotheksfunktionen (Memo-

    ry Management Functions) zur Verfugung gestellt, die den verfugbaren und belegten

    Speicher verwalten. Ein Prozess kann einen Speicher anfordern und erhalt dabei einen

    Zeiger auf diesen Speicher (z.B. uber malloc(), siehe Listing 2.1, Zeile 19). Benotigt

    er den Speicher nicht mehr, kann er diesen Speicher jederzeit freigeben (zum Beispiel

    mit free(), siehe Listing 2.1, Zeile 26). Nachdem weder der Zeitpunkt der Speicher-

    allokation noch die Groe des Speichers vorgegeben ist, wird von einer dynamischen

    Speicherverwaltung bzw. einer Dynamic Memory Allocation gesprochen.

    Alle dynamisch angeforderten Speicherblocke befinden sich innerhalb eines speziell

    dafur vorgesehenen, dynamisch wachsenden Speicherbereichs, dem sogenannten Heap

    des Programms. Die Groe des Heaps wird durch den verfugbaren Speicher abzuglich

    der Groe des Stacks limitiert (siehe Abbildung 2.1). Ein Prozess kann maximal soviel

    Speicher benutzen, wie diesem von der Speicherverwaltung auf Abruf zur Verfugung

    gestellt wird. Die Lage der Speicherblocke ist vom laufenden Prozess nicht beeinflussbar

    und wird von der Speicherverwaltung des Betriebssystems bestimmt. Mehrmalige Spei-

    cheranforderungen und Freigaben fuhren aufgrund der internen Organisation der be-

    legten und freien Speicherbereiche zu einer Fragmentierung (Memory Fragmentation)

    des Speichers. Auf dem Heap allokierter Speicher ist solange gultig, bis er wieder frei-

    gegeben wird oder der Prozess beendet wird. Moderne Betriebssysteme geben den

    gesamten Heap einer Applikation nach dessen Terminierung automatisch wieder frei,

    um den Speicher anderen Applikationen zur Verfugung stellen zu konnen.

  • 2. Grundlagen 12

    2.2.5 Stack

    Der Stack wachst dynamisch und teilt sich gemeinsam mit dem Heap den einer Ap-

    plikation zur Verfugung stehenden freien Speicher. Der Stack wachst, im Gegensatz

    zum Heap, von hohen Speicheradressen in Richtung niedrigere Speicheradressen (siehe

    Abbildung 2.1). Jede aufgerufene Funktion erzeugt im Stack-Bereich einen eigenen

    Speicherblock, genannt Stack Frame, welcher von der hochsten Adresse beginnend nach

    und nach den Stack befullt. Auf dem Stack eines Prozessors einer Intel x86 Architektur

    konnen folgende Daten innerhalb eines einzigen Stackframes abgelegt werden [8]:

    Funktionsparameter - Alle beim Aufruf einer Funktion ubergebenen Para-

    meter liegen auf dem Stack. Innerhalb der Funktion entspricht ein ubergebener

    Parameter einer lokalen Variablen.

    Funktionsrucksprungadresse - Unmittelbar vor Beendigung einer Funktion

    wird der Ruckgabewert der Funktion in einem Register des Prozessors oder auf

    dem Stack abgelegt und zur aufrufenden Funktion bzw. zur Funktionsrucksprung-

    adresse (Function Return Address) zuruckgekehrt. Auf dieser Adresse liegt eines

    der Hauptaugenmerke beim Versuch einer Attacke. Gelingt es einem Angreifer

    diese Adresse zu manipulieren, kann er den Programmfluss gezielt beeinflussen.

    Frame Pointer - Der aus Effizienzgrunden auf dem Stack gesicherte Frame Poin-

    ter enthalt die Basisadresse des aktuellen Stack Frames. Er dient dem effizienten

    Zugriff auf die auf dem Stack gesicherten Variablen, indem jede Variable mit

    diesem Pointer und einem bestimmten Offset adressiert werden kann.

    Lokale Variablen - Alle lokal deklarierten auto-Variablen werden auf dem Stack

    abgelegt. Im Gegensatz dazu werden lokale statische Variablen, welche ihren Wert

    auch nach dem Verlassen der Funktion behalten mussen, entweder im allgemeinen

    Datensegment oder im BSS-Bereich abgelegt.

    Optionale Prozessdaten und Zeiger - Je nach Architektur und Compiler

    konnen noch weitere Daten auf dem Stack abgelegt werden, z.B. eine Adres-

    se zur Ausnahmebehandlung (Exception Handler Frame), zwischengespeicherte

    Register des Prozessors (Callee Save Registers).

  • 2. Grundlagen 13

    Im Gegensatz zu Speicherblocken auf dem Heap hat ein Stack Frame immer eine be-

    grenzte Lebensdauer und limitierte Groe. Im Beispiel aus Listing 2.1 werden alle loka-

    len auto-Variablen (siehe Listing 2.1, Zeilen 16 und 7) und Verwaltungsdaten (Funkti-

    onsrucksprungadresse) auf dem Stack gespeichert. Wird die Funktion verlassen, werden

    auch die aktuellen Stack Frames wieder vom Stack entfernt. Die Zugriffsorganisation

    erfolgt ahnlich einem Stapel, denn die Daten werden immer in umgekehrter Reihenfolge

    gelesen, als sie zuvor auf dem Stack geschrieben wurden (LIFO-Prinzip - Last In/First

    Out). Der letzte auf dem Stack abgelegte Stack Frame bestimmt immer die aktuelle

    Groe des Stacks. Eine Fragmentierung des Stacks aufgrund der LIFO-Organisation ist

    nicht zu befurchten.

    2.3 Register

    Ein Prozessor besitzt nur einen sehr kleinen, jedoch sehr schnellen internen Speicher

    zur Abarbeitung eines Programms. Ein Teil dieses Speichers wird fur interne Zwecke

    verwendet und ist von Auen bzw. fur Programme nicht zuganglich. Ein anderer kleiner

    Teil dieser Speicherplatze wird fur Ein- und Ausgabeoperationen, das Verschieben und

    Manipulieren von Speicher, zur Ubergabe bestimmter Parameter, zur Adressierung und

    Indizierung von Speicher, zur Ruckgabe von Ergebnissen und fur Zahler verwendet.

    Dieser Teil der Speicherplatze wird im Allgemeinen als Register bezeichnet. Die fur

    eine Applikation verfugbaren Register werden als General Purpose Registers (GPR)

    bezeichnet, welche in allen Architekturen in ahnlicher Form zur Verfugung stehen.

    Intel unterteilt in Intels 32-bit Architecture (IA-32) die Register je nach Verwendung

    in die Gruppen General Data Registers, General Address Registers, Floating Point Stack

    Registers, in Register fur spezielle Verwendungen (z.B. Multimedia) und Flags, Counter

    und Pointer zur Kontrolle des Programmflusses (Instruction Pointer, Interrupt Control,

    Paging, Mode Switching, uvm.). Weitere Details zur hier als Beispiel angefuhrten IA-32

    Architektur sind [28] zu entnehmen.

    Jeder Prozessor und jede Prozessorarchitektur hat spezielle Register, um mit dem

    Programm zu kommunizieren oder den Programmfluss zu steuern. Moderne Prozes-

    soren haben in der Regel einen groeren und schnelleren internen Speicher und konnen

  • 2. Grundlagen 14

    oft mehrere Speicherplatze in kurzester Zeit oder parallel bearbeiten. Sie bieten eine

    groere Registerbreite (Bits pro Register) und konnen dadurch mehr Speicher direkt

    adressieren. Die Anzahl und Art der Register ist hochst unterschiedlich, alle moder-

    nen Prozessoren bieten mittlerweile aber Segment, Control, Debug und Test Register

    zur Steuerung des Prozessors. Gemeinsam haben fast alle Prozessoren auch, dass jede

    Manipulation eines Registers eine unerwartete und unbeabsichtigte, fur Angreifer viel-

    leicht beabsichtigte, Auswirkung auf das Programm oder den Programmfluss haben

    kann. Die fur einen Angriff wohl wichtigsten Register sind das ESP (Stack Pointer),

    das EBP (Extended Base Pointer) und das EIP (Instruction Pointer) Register.

    2.4 Daten und Funktionszeiger

    Ein Pointer ist die Bezeichnung fur einen Zeiger auf eine Speicheradresse. Man unter-

    scheidet dabei zwischen Zeigern auf Daten (Data Pointer) und Zeigern auf Funktionen

    (Function Pointer). Ein Zeiger zeigt immer an den Beginn eines Speicherbereichs. Uber

    den Zeiger selbst, welcher ausschlielich nur die Adresse darstellt, kann keinerlei Aussa-

    ge uber die Groe des Speicherblocks, die an dieser Adresse abgelegten Daten und deren

    Datentypen getroffen werden. Uber die Lage des Speicherblocks kann maximal auf die

    grundsatzliche Verwendung - Daten oder Programmcode - geschlossen werden.[47]

    Normale Variable Wert

    Adresse

    Wert

    Pointer Variable

    Pointer Variable Adresseint iValue;

    int* pValue;

    int (*pfFoo)();

    int foo(){ ...}

    pfFoo();

    *pValue = iValue;

    Datenzeiger (Data Pointer) Funktionszeiger (Function Pointer)

    Abbildung 2.2: Daten- und Funktionszeiger

    Innerhalb eines Programms wird haufig uber Zeigervariablen auf Daten oder Funktio-

    nen zugegriffen (siehe Abbildung 2.2). Programmiersprachen wie C und C++ stellen

  • 2. Grundlagen 15

    dafur eigene Pointertypen zur Verfugung, die einen einfachen Zugriff auf Speicher und

    eine einfache Zeigermanipulation (Zeigerarithmetik) ermoglichen.

    In den Programmiersprachen C und C++ reprasentiert schon ein Funktionsname den

    Zeiger auf die Funktion, also jene Speicheradresse, an der der Funktionscode beginnt.

    Erst die Klammerung nach dem Funktionsnamen lasst den Compiler erkennen, dass es

    sich um einen Aufruf der Funktion an dieser Adresse mit den angegebenen Parametern

    handelt. Zeigervariablen konnen sich prinzipiell in jedem Speicherbereich befinden, je

    nachdem welcher Speicherklasse sie zugeordnet werden. Typische auf dem Stack abge-

    legte Pointer sind beispielsweise die als Argumente einer Funktion ubergebenen Zeiger,

    lokale Zeigervariablen der Speicherklasse auto innerhalb einer Funktion, Rucksprung-

    adressen, Adressen auf Exception Handler und gesicherte Frame Pointer. In C++ ge-

    schriebene Programme speichern fur deren Objektinstanzen wahrend der Laufzeit auch

    Tabellen mit Zeigern auf Funktionen (Vector Tables oder VTables), um virtuelle Me-

    thoden abzubilden.

    Zeiger innerhalb des Prozesspeichers stellen bei allen Attacken das Hauptangriffsziel

    dar. Konnen Zeiger oder ganze Zeigertabellen von Auen manipuliert werden, konnen

    damit andere Daten, als ursprunglich vorgesehen, gelesen oder gespeichert werden. Bei

    manipulierten Funktionszeigern werden nicht vorgesehene Funktionen aufgerufen und

    damit der Programmfluss gezielt verandert. Ebenso ist es denkbar, Zeiger auf Dateien

    zu manipulieren und damit externe Dateien in einen laufenden Prozess einzuschleusen.

    Auch Handles6 auf Dateien sind letztendlich nur Zeiger.

    6Als Handle wird in der Softwareentwicklung ein Identifikator, Nickname oder Alias auf digitaleObjekte wie z.B. Dateien, Prozesse, Ressourcen, angeschlossene Gerate, usw. bezeichnet. Das Betriebs-system vergibt ein systemweit eindeutiges Handle beim Erzeugen eines Objekts oder dem Aufbau einerVerbindung mit einem Objekt. Im Programm werden diese Objekte dann nur noch uber dieses Handleangesprochen.

  • 3

    Potentielle

    Sicherheitsschwachstellen

    Unter einer potentiellen Sicherheitsschwachstelle (Security Vulnerability) versteht man

    eine Systemschwache, welche einen Einbruch in das System zumindest theoretisch

    moglich macht. Eine Schwachstelle ist immer die Folge eines Fehlers im Code (Co-

    ding Bug) oder eines Fehlers im Design (Design Flaw). Ein Bug ist ein Fehler in der

    Implementierung, z.B. eine fehlerhafte Stringbehandlung oder ein fehlerhafter Zeiger

    innerhalb einer Funktion. Das Design einer Software kann keine Bugs haben, weil es

    sich dabei nicht um Codierungsfehler handelt. Ein Flaw ist auf der Ebene des Designs,

    der Planung und der Architektur einer Software zu suchen.

    Die folgenden Abschnitte dieses Kapitels beschreiben einige der typischen Fehler, wel-

    che fur einen Groteil der Sicherheitsschwachstellen verantwortlich sind. Allen voran

    Designfehler und die sogenannten Pufferuberlaufe.

    3.1 Designfehler

    A flaw is instantiated in software code but is also present (or absent!) at the de-

    sign level. schreiben Hoglund und McGraw in [24, S. 39]. Ein Designfehler kann al-

    so im Quelltext einer Software zu finden sein. Oft aber sind designbasierte Sicher-

    heitsschwachstellen die Folge von fehlenden Codeteilen oder einer unzureichenden Im-

    plementierung notwendiger Sicherungs- und Verteidigungsmechanismen. Die folgende

    16

  • 3. Potentielle Schwachstellen 17

    Auflistung sicherheitsrelevanter Designfehler lasst schnell erkennen, welche fehlerhaften

    oder fehlenden Funktionen die typischen Designfehler heutiger Software sein konnen:

    Eingabe- und Parameterprufung: Buffer Overflows, Parametermanipulation,

    SQL Injection und Cross-Site Scripting (XSS) basieren auf ungepruften Benut-

    zereingaben oder Funktionsparametern. Nur die strikte Uberprufung aller Ein-

    gangsdaten kann Manipulationen verhindern.

    Authentisierung und Authentifizierung: Das erste zweier Subjekte (z.B. Be-

    nutzer, Prozesse, Services, Clients) muss einen Nachweis seiner Identitat erbrin-

    gen, sich authentisieren. Das zweite Subjekt als sein Gegenuber muss die Identitat

    seines Gegenubers uberprufen, die Identitat seines Partners authentifizieren.

    Verschlusselung: Unverschlusselte Daten sind fur jedermann lesbar. Eine Ver-

    schlusselung der Daten lasst nur jenen die Informationen zukommen, fur die sie

    auch gedacht sind.

    Sicherung: Ungesicherte Daten sind manipulierbar. Codierungsverfahren konnen

    Daten vor Manipulationen sichern oder jede Manipulation aufdecken (z.B. Check-

    summe, Signatur).

    Zugriffs- und Ausfuhrungsberechtigungen: Berechtigungsstrategien legen

    fest, was ein Benutzer oder Prozess darf oder nicht darf (z.B. Zugriff auf Dateien,

    Ressourcen, Starten von Prozessen).

    Anwendungs- und Systemkonfiguration: Konfigurationsdateien unterliegen

    einer strengen Kontrolle (Zugriffs- und Manipulationsschutz).

    Fehlerbehandlung und Logging: Falsche Fehlerbehandlungen verraten oft in-

    terne Systeminformationen. Logdateien, Speicherauszuge und Stacktraces sollten

    nicht sichtbar sein oder mit einer entsprechenden Zugriffsberechtigung versehen

    werden.

    Designfehler lassen sich im Allgemeinen nicht durch die Prufung einzelner Codezei-

    len erkennen. Was fur eine einfache Applikation an Daten- und Codesicherung noch

    ausreichend sein mag, ist fur eine sicherheitskritische Anwendung bei weitem nicht

    genug. Erst eine Klassifizierung der Sicherheitsanforderungen, das Erkennen der Be-

    drohungsszenarien, das Zusammenwirken einzelner Module und die Identifikation der

  • 3. Potentielle Schwachstellen 18

    Datenstrome lasst mogliche Designfehler und darauf basierende Sicherheitsschwachstel-

    len sichtbar werden.

    3.2 Overflow Fehler

    Die in den letzten Jahrzehnten weitaus am haufigsten zum Einbruch in ein Computer-

    system genutzten Programmfehler stellen so genannte Pufferuberlaufe (Buffer Over-

    flows oder Buffer Overruns) dar. Sie treten immer dann auf, wenn ein Programm

    bzw. eine Funktion Daten in einem Speicher bestimmter Lange verarbeitet und dabei

    die Grenzen eines Puffers (Memory Buffer) schreibend uber- oder unterschreitet. Dabei

    werden angrenzende, sich ebenfalls im Speicher befindliche Daten oder Zeiger auf Daten

    und Funktionen uberschrieben, welche funktions- und ablaufrelevant sind. Besonders

    haufig treten diese Fehler bei C- und C++-Programmen auf, da hier seitens der Sprach-

    konzepte und Compiler keine Uberprufungen der Speicher- und Arraygrenzen erfolgen.

    Fur die Vermeidung eines Buffer Overflows ist letztendlich immer der Programmie-

    rer zustandig. Moderne Programmiersprachen unterstutzen die Entwickler, indem sie

    Array- und Speichergrenzen verwalten und Zugriffe auf illegale Speicherbereiche un-

    terbinden. Die Uberprufung aller Speicherzugriffe und Speichergrenzen hat naturlich

    Performanceeinbuen zur Folge.

    Overflows konnen durch einen Fehler scheinbar zufallig auftreten oder - bei Attacken -

    absichtlich provoziert werden. In allen Fallen ist ein Programmfehler die Voraussetzung

    fur einen Uberlauf. Das Problem kann uber langere Zeit unentdeckt bleiben, wenn das

    Programm keine sichtbaren Veranderungen im weiteren Ablauf zeigt. Ein Angreifer,

    der derartige Sicherheitsschwachstellen (Security Vulnerabilities) ausnutzen mochte,

    provoziert einen Overflow, um Daten bzw. Code in das System zu injizieren (Data

    Injection oder Code Injection). Dabei versorgt er das Programm gezielt mit Daten,

    die auerhalb der Spezifikationen liegen. Werden diese Eingangsdaten keiner expliziten

    Prufung unterzogen, kann es zu einem Pufferuberlauf kommen und im Speicher be-

    finden sich gezielt injizierte Daten. Mitunter ist der weitere Prozessablauf durch diese

    Daten gesteuert und im schlechtesten Fall durch einen Angreifer gezielt beeinflusst.

  • 3. Potentielle Schwachstellen 19

    Wie Hoglund et al. in [24] schreiben, erlauben unterschiedliche Programmfehler auch

    unterschiedliche Methoden um ein System anzugreifen.Related programming errors

    give rise to simular exploit techniques.[24, S. 38] Im Folgenden werden einige aus-

    gewahlte Uberlauffehler und die darauf basierenden Angriffsmethoden vorgestellt.

    3.2.1 Stack Overflow

    Der Stack ist, wie im Abschnitt 2.2.5 beschrieben, ein Speicherbereich, in dem lo-

    kale Variablen, Sprungadressen und Funktionsparameter kurzfristig abgelegt werden.

    In IA-32 Architekturen wachst, wie bei vielen anderen Prozessorarchitekturen auch,

    der Stack von hoheren zu niedrigeren Speicheradressen. Wird nun ein Puffer auf dem

    Stack angelegt und kommt es bei einem Schreibvorgang zu einem Uberschreiten der

    Puffergrenzen, so werden an den Puffer angrenzende Speicherstellen auf dem Stack

    uberschrieben.

    3.2.1.1 Der klassische Stack Overflow

    Der klassische stack-basierte Buffer Overflow oder, in Form einer Attacke provoziert,

    auch Stack Smashing Attack genannt [1], wird als Overflow der 1.Generation1 bezeich-

    net [21], weil dieser Overflow wohl zu den am langsten bekannten Schwachstellen gehort.

    Bei einem klassischen Stack Overflow Exploit ist das Ziel meist die Manipulation der

    Function Return Address, also der Rucksprungadresse zur aufrufenden Funktion. Kann

    dieser Zeiger gezielt manipuliert werden, kann eine eigene eingeschleuste Funktion oder

    eine Bibliotheksfunktion aufgerufen werden.

    Ein kurzes Beispiel soll die Vorgange auf dem Stack bei einem Funktionsaufruf und ei-

    nem Stack Overflow verdeutlichen. In dem im Listing 3.1 gezeigten Beispielprogramm

    wird aus der Funktion main() die Funktion foo() aufgerufen. Die Applikation ist syn-

    taktisch korrekt und ausfuhrbar, obwohl die Funktion foo einige Fehler bzw. Schwach-

    stellen aufweist, welche fur einen Angriff missbraucht werden konnten. Die unsichere

    1Halvar teilt in [21] die verschiedenen Exploit-Techniken erstmals in Generationen ein. Er klas-sifiziert damit die Arten der Buffer Overflow Schwachstellen anhand der zeitlichen Abfolge, in derdiese veroffentlicht wurden. Darauf basierend erweitert Klein in [31] diese Einteilung und weist dieOverflows jeweils einer bestimmten Generation zu.

  • 3. Potentielle Schwachstellen 20

    Funktion strcpy (siehe Anhang A.1) pruft nicht, ob die Lange des zu kopierenden

    Strings die des Puffers auf dem Stack uberschreitet.

    1 #include

    2 #include

    3

    4 void foo(const char* pStr)

    5 {

    6 char szBuffer[20];

    7 strcpy(szBuffer, pStr); // Stack Overflow Schwachstelle

    8 printf(szBuffer); // Format-String Schwachstelle

    9 }

    10

    11 int main(int argc, char* argv[])

    12 {

    13 foo(argv[1]); // Illegal Pointer Schwachstelle

    14 return 0;

    15 }

    Listing 3.1: Beispielprogramm StackOverflow.c

    Ebenso bleibt das Argument der Funktion printf ungepruft, was eine im nachsten

    Abschnitt besprochene Format-String Schwachstelle zur Folge hat (siehe Kapitel 3.3).

    0x0012FF5C 4f 1d 13 78 O..x // Beginn des Puffers szBuffer

    0x0012FF60 c0 ff 12 00 Ay..

    0x0012FF64 3c 10 40 00

  • 3. Potentielle Schwachstellen 21

    dem Stack gesicherten Register EIP (die Rucksprungadresse) und EPB (den Frame

    Pointer). Der lokale Puffer szBuffer der Funktion foo und auch die lokalen Variablen

    von main() liegen ebenfalls auf dem Stack.

    hoheAdresswerte

    niedrigeAdresswerte

    vorhergehendeStack Frames

    Function Return Addressgesicherter Frame Pointer

    Lokal deklarierter Puffer szBuffer[20]

    Sta

    ck F

    ram

    e fo

    o()

    Function Return Address

    Sta

    ck F

    ram

    e m

    ain(

    )

    Funktionsparameterargc

    argv[ ]

    gesicherter Frame PointerFunktionsparameter

    pStr

    vorhergehendeStack Frames

    Sta

    ck F

    ram

    e fo

    o()

    Function Return Address

    Sta

    ck F

    ram

    e m

    ain(

    )

    Funktionsparameterargc

    argv[ ]

    gesicherter Frame Pointer

    Lokal deklarierter Puffer szBuffer[20] mit eingeschleustem Code

    Function Return Address

    Sta

    ckw

    achs

    tum

    (a) Stack vor dem Buffer Overflow (b) Stack nach dem Buffer Overflow

    Ove

    rflow

    Puf

    fer

    FunktionsparameterpStr

    Abbildung 3.1: Einschleusen und Aufrufen von Code uber einen Stack Overflow

    Im folgenden Beispiel wird von der Annahme ausgegangen, dass der Angreifer das Pro-

    gramm aus Listing 3.1 mit einem wahlfreien Parameter aufrufen kann. Die Abbildung

    3.1.a zeigt den Stack vor dem Stack Overflow. Schleust der Angreifer nun einen mit

    Code und Adressen praparierten String2 in die Funktion foo ein, welcher langer ist als

    der lokal angelegte Puffer szBuffer, kommt es wegen der fehlende Langenuberprufung

    in foo() und auch in der Funktion strcpy zu einem Overflow des Puffers szBuffer. Der

    Angreifer uberschreibt durch den Overflow, wie aus dem Dump in Listing 3.2 und der

    Abbildung 3.1 ersichtlich ist, nicht nur die auf dem Stack gesicherte Rucksprungadresse

    (Function Return Address) und den gesicherten Frame Pointer, sondern schleust damit

    mitunter gleichzeitig Code bzw. Adressen in den Puffer szBuffer bzw. auf den Stack

    2Um Code oder Adressen uber einen String in ein Programm einzuschleusen, muss eine Zeichenkettemit einer Reihe nicht-druckbarer Zeichen erzeugt werden. Perl erlaubt die Zusammenstellung vonStrings uber Escape-Sequenzen (siehe dazu [56]), ahnlich der Programmiersprache C, und die Ubergabeder Parameter und den Aufruf des Programms uber ein Script von der Console heraus [26]. Linux/Unixbieten mit der Bash-Shell ahnliche Moglichkeiten [30].

  • 3. Potentielle Schwachstellen 22

    ein (siehe Abbildung 3.1.b). Die Rucksprungadresse lasst er auf den eingeschleusten

    Code zeigen. Beim Beenden der Funktion wird der EIP vom Stack geholt und auf diese

    Adresse gesprungen. Anstatt in main() fahrt der Programmfluss (Program Flow) im

    eingeschleusten Code fort.

    Stack Overflows gehoren zu den einfachsten Overflows, weil sie am leichtesten auszunut-

    zen sind. Ebenso gibt es mittlerweile mehrere Methoden, wie Stack Overflows verhin-

    dert oder zumindest erschwert werden konnen. Welche das sind und wie wirkungsvoll

    derartige Methoden sind, wird in spateren Kapiteln noch gepruft und diskutiert.

    3.2.1.2 Frame Pointer Overwrite

    Im Unterschied zum klassischen Stack Overflow, bei dem Daten und Zeiger und in erster

    Linie die Rucksprungadresse manipuliert werden, kommt es bei einem Frame Pointer

    Overwrite zu einem Uberschreiben des auf dem Stack gesicherten Frame Pointers.

    Kann dieser Pointer durch einen Angreifer gezielt manipuliert werden, zum Beispiel

    wie in der Abbildung 3.3 gezeigt durch einen Off-By-One Overrun, so kann dadurch

    der weitere Programmfluss geandert werden. Mit dieser Exploit-Technik kann ebenfalls

    zuvor eingeschleuster Programmcode zur Ausfuhrung gebracht werden. Der Artikel [32]

    zeigt mit einem primitiven Beispiel in beeindruckender Einfachheit die Funktionsweise

    eines Frame Pointer Overwrites und dessen Ausnutzung fur einen Exploit.

    3.2.2 Heap Overflow

    Heap Overflows gehoren zu den Overflows der 4. Generation [21]. Sie funktionieren nach

    einem ahnlichen Prinzip wie Stack Overflows, sind deutlich aufwandiger auszunutzen

    und in Quellen wie [15] detailliert beschrieben. Nach [15] und WSEC stellen sie aus

    folgenden Grunden eine zusatzliche Gefahr dar,

    weil viele Compilererweiterungen und softwarebasierte Schutzmethoden (spezielle

    Bibliotheken) nur den Stack nicht aber den Heap schutzen konnen,

    weil viele hardwarebasierte Schutzmethoden moderner Prozessoren nur das Aus-

    fuhren von Code auf dem Stack verhindern konnen

  • 3. Potentielle Schwachstellen 23

    und vor allem, weil vielfach noch von der falschen Annahme ausgegangen wird,

    dass Heap Overflows nicht fur Angriffe nutzbar seien und damit eine Codeande-

    rung von local buffer auf static buffer ausreichend fur einen wirksamen Schutz

    sei.

    Auf dem Heap werden, wie im Kapitel 2.2.4 beschrieben, jene Puffer abgelegt, die erst

    wahrend der Laufzeit mit speziellen API-Funktionen alloziert (z.B. mit malloc()) und

    spater wieder freigegeben (z.B. mit free()) werden. Dieser dynamische Speicherbe-

    reich (Memory Pool) wird durch das Betriebssystem bzw. dessen Speicherverwaltung

    (Heap Management oder Memory Management) organisiert. Durch die mit der Laufzeit

    zunehmende Fragmentierung des Speichers sind die Speicherblocke in ihrer Reihenfolge

    schwer vorhersehbar im Speicher abgelegt. Belegte und freie Speicherbereiche konnen

    sich beliebig abwechseln. Bei einem Heap Overflow lauft keineswegs der Heap selbst

    uber, sondern, ahnlich wie beim Stack Overflow, ein auf dem Heap abgelegter Puffer.

    Kommt es im Heap zu einem Pufferuberlauf, dann konnen davon freie Speicherberei-

    che, angrenzende belegte Speicherbereiche und auch interne zur Verwaltung des Heaps

    notwendige Verwaltungsdaten betroffen sein, welche ebenfalls im dynamischen Bereich

    des Heap Segments abgelegt werden. Uber Heap Overflows konnen wie bei Stack Over-

    flows auch Zeiger manipuliert werden, uber die auf Daten zugegriffen wird oder uber

    die der Programmfluss kontrolliert wird.

    1 #include

    2 #include

    3

    4 struct PufferPtr

    5 {

    6 char* pPufferA;

    7 char* pPufferB;

    8 };

    9

    10 int main(int argc, char* argv[])

    11 {

    12 PufferPtr* pPufferP = (PufferPtr*)malloc(sizeof(PufferPtr));

    13 pPufferP->pPufferA = (char*)malloc(10);

    14 pPufferP->pPufferB = (char*)malloc(10);

    15

    16 strcpy(pPufferP->pPufferA, argv[1]);

  • 3. Potentielle Schwachstellen 24

    17 strcpy(pPufferP->pPufferB, argv[2]);

    18

    19 free(pPufferP->pPufferB);

    20 free(pPufferP->pPufferA);

    21 free(pPufferP);

    22

    23 return 0;

    24 }

    Listing 3.3: Beispielprogramm HeapOverflow.c

    Ein kurzes Beispiel zeigt einen moglichen Angriff uber einen Heap Overflow. Dabei wird,

    um die Komplexitat derartiger Angriffe zu verdeutlichen, wiederum das Einschleusen

    einer eigenen Funktion angenommen. Um den Code auszufuhren, muss gleichzeitig auch

    der Stack manipuliert werden. Listing 3.3 zeigt eine Applikation, welche zwei Puffer A

    und B auf dem Heap allokiert und die Zeiger der beiden Speicherblocke ebenfalls auf

    dem Heap (Puffer P) ablegt. Das Speicherabbild konnte wie in der Abbildung 3.2.a

    dargestellt aussehen.

    Die Applikation liest nun zwei Benutzereingaben und speichert diese in den beiden

    Puffern A und B. Wei der Angreifer um die Organisation der Datenblocke und deren

    Adressen auf dem Heap, so konnte er versuchen, mit der ersten Eingabe einen Uberlauf

    zu provozieren und gleichzeitig damit seinen Code einzuschleusen (siehe Abbildung

    3.2.b.(1)). Durch den Uberlauf des Puffers A kommt es gleichzeitig zum Uberschreiben

    des Zeigers pPufferB. Der Angreifer lasst diesen Zeiger nun durch seine Manipulati-

    on gezielt auf die auf dem Stack liegende Function Return Address verweisen. Nun

    kommt es zur zweiten Eingabe und der Angreifer gibt die Adresse von Puffer A ein.

    Diese Adresse wird nun auf die Adresse pPufferB geschrieben, also in den Stack (siehe

    Abbildung 3.2.b.(2)). Wird nun die Funktion beendet und der aktuelle Stack Frame

    geloscht, springt das Programm automatisch auf die zuvor im Stack manipulierte Func-

    tion Return Address (siehe Abbildung 3.2.b.(3)). Damit ist es dem Angreifer gelungen,

    in zwei Schritten Code auf dem Heap einzuschleusen und spater auch auszufuhren. Die

    gezeigte Methode ubergeht auch etwaige Schutzmechanismen der Compiler, die spater

    noch diskutiert werden.

  • 3. Potentielle Schwachstellen 25

    hoheAdresswerte

    niedrigeAdresswerte

    vorhergehendeStack Frames

    Function Return Addressgesicherter Frame Pointer

    Lokale Variablen

    Sta

    ck F

    ram

    e fo

    o()

    (a) Heap und Stack vor dem Buffer Overflow (b) Heap und Stack nach dem Buffer Overflow

    Funktionsparameter

    Puffer B

    Puffer A

    char* pPufferAchar* pPufferBP

    AB

    HEAP

    vorhergehendeStack Frames

    Function Return Addressgesicherter Frame Pointer

    Lokale Variablen

    Sta

    ck F

    ram

    e fo

    o() Funktionsparameter

    Puffer B

    Puffer A

    char* pPufferA

    AB

    HEAP

    P char* pPufferB

    Puffer AEingeschleusterCode

    Eingeschleuster Pointer1

    2

    3

    Abbildung 3.2: Einschleusen und Aufrufen von Code uber einen Heap Overflow

    Dieses Szenario mag konstruiert erscheinen, unzahlige Eintrage in einschlagigen Listen

    beweisen aber, dass Heap Overflows oft fur Angriffe genutzt werden. Viele der Heap

    Overflow basierten Attacken stutzen sich auch auf die Manipulation der Verwaltungs-

    daten des Memory Managers im Heap. Diese Attacken bedurfen neben der Lage der

    Speicherblocke und der detaillierten Kenntnisse uber das Programm noch weiterer de-

    taillierter Kenntnisse uber die interne Organisation zur Verwaltung des dynamischen

    Speichers [61]. Interne Strukturen der Heap- bzw. Memory Manager sind aber gut do-

    kumentiert (vgl. [21, 61]) oder der Sourcecode der Manager ist ohnehin frei zuganglich

    (z.B. Linux-Quellcode und Open Source3).

    3Fur die Bibliothek Glibc und somit viele Linux/Unix Systeme wird seit Jahren eine freie Imple-mentierung von Wolfram Gloger mit dem Namen ptmalloc eingesetzt, deren Sourcecode frei zuganglichist. Nahere Informationen dazu sind unter http://www.malloc.de (Stand: 2007.05.02) verfugbar.

    http://www.malloc.de

  • 3. Potentielle Schwachstellen 26

    3.2.3 Array Indexing Overflows

    Array Indexing Overflows passieren, wenn beim Zugriff auf ein Array der Index falsch

    ist und somit ein Zugriff auerhalb des Speicherbereichs eines Arrays erfolgt. Typi-

    scherweise kommt dies dann am ehesten vor, wenn es bei der Indexberechnung zu

    einem Integer Overflow oder Integer Underflow gekommen ist, oder wenn bei einer

    Iteration die erlaubten Indexbereiche verlassen werden.

    hoheAdresswerte

    niedrigeAdresswerte

    Function Return Addressgesicherter Frame Pointer

    Lokal deklarierter Puffer szBuffer[20]

    Sta

    ck F

    ram

    e fo

    o() Funktionsparameter

    pStr

    vorhergehendeStack Frames

    Sta

    ck F

    ram

    e fo

    o()

    Lokal deklarierter Puffer szBuffer[20] mit

    Off-By-One-Overrun

    Sta

    ckw

    achs

    tum

    (a) Stack vor einem Off-By-One Overrun (b) Manipulierter Frame Pointer nach einem Off-By-One Overrun

    Off-By-OneOverrun

    Puf

    fer

    FunktionsparameterpStr

    gesicherter Frame PointerFunction Return Address

    vorhergehendeStack Frames

    Abbildung 3.3: Manipulation des gesicherten Frame Pointers uber einen Off-By-OneOverrun

    Haufige Vertreter dieser Array Indexing Overflows sind die sogenannten Off-By-One

    Overflows. Wie der Name andeutet, handelt es sich dabei um einen Uberlauf um 1

    Byte. Eine typische Auspragung dieses Fehlers ist eine ein Array bearbeitende Schleife,

    wie es das kurze Beispiel aus Listing 3.4 zeigt:

    1 void foo(const char* str)

    2 {

    3 char szBuffer[20];

    4 for( int i=0; i

  • 3. Potentielle Schwachstellen 27

    Durch einen Durchlauf mehr als erlaubt (die Laufbedingung verwendet falschlicherweise

  • 3. Potentielle Schwachstellen 28

    und statische lokale Variablen abgelegt (siehe Listing 2.1, Zeile 1 und 15). BSS Over-

    flows erlauben, ebenso wie auch bei Heap Overflows (Kapitel 3.2.2) gezeigt wurde, die

    Manipulation von Zeigern. Attacken lassen sich besonders dann einfach durchfuhren,

    wenn nicht-initialisierte Zeiger und Puffer in der Applikation verwendet wurden, welche

    dann im BSS Segment liegen und deren Uberlauf nicht verhindert wird. Ein wesentli-

    cher Vorteil und eine deutliche Erleichterung fur einen Angreifer ist die Tatsache, dass

    Puffer im BSS-Segment eine feste Groe haben und in ihrer Lage unverandert bleiben.

    Die Lage der Puffer wird schon vom Compiler und Linker wahrend der Ubersetzung des

    Quelltextes und dem Erstellen einer ausfuhrbaren Datei bestimmt. Kann der Angreifer

    die Reihenfolge der Puffer und die Abstande zueinander erst einmal bestimmen, kann

    er sich auf deren fixe relative Lage zueinander verlassen6.[15]

    Wie durch gezielte Zeiger- und Puffermanipulationen ein Angriff (Exploit) realisiert

    werden kann, ist dem Kapitel 3.2.2 zu entnehmen.

    3.3 Format-String Fehler

    Sicherheitslucken und Exploits aufgrund von Format-String-Schwachstellen sind erst

    in den spaten 1990er Jahren erstmals publiziert worden, obwohl sie eigentlich schon so

    alt sind wie die Programmiersprache C und deren unsichere C-Library selbst (vgl. [17]

    und [31]). Format-String Fehler beschreiben ein sehr C/C++-spezifisches Problem fur

    Funktionen mit einer beliebigen Anzahl von Parametern. Typische Vertreter sind die

    ANSI-C-Funktionen der printf()-Familie zur formatierten Ausgabe und Stringforma-

    tierung. Funktionen mit einer variablen Anzahl von Parametern ist es nicht moglich,

    den Datentyp der uber den Stack ubergebenen Variablen und Konstanten wahrend der

    Laufzeit festzustellen.

    Bei printf()-Funktionen wird der Datentyp mit einem String, welcher der Forma-

    tierung der Ausgabe dient (Format-String), beschrieben. Im Prototyp der Funktion

    int printf(const char* format,...) beschreibt der Stringparameter format jene

    6Oft reicht ein einfaches Probierverfahren aus, um die Lage der Puffer untereinander zu bestimmen.Dabei werden durch manipulierte Eingaben Pufferuberlaufe provoziert und durch die Beobachtungder Ausgaben und anderer Puffer die Lage zueinander bestimmt. Man beobachtet dabei also, wo dieeingeschleusten Daten wieder sichtbar werden.

  • 3. Potentielle Schwachstellen 29

    Zeichenkette, welche den Ausgabetext und die durch das Zeichen % eingeleitete For-

    matanweisungen enthalt. Die Formatanweisung beschreibt dabei nicht nur den Da-

    tentyp, sondern optional das Ausgabeformat (Dezimal, Hexadezimal,. . . ), die Breite

    der Ausgabe, die Anzahl der Vor- und Nachkommastellen, usw.. Wie im Kapitel 2.2.5

    beschrieben, werden beim Aufruf einer Funktion die Parameter auf dem Stack abge-

    legt. Innerhalb der Funktion printf werden nun der format-String geparst, die dem

    %-Zeichen entsprechenden Daten dem Stack entnommen und ein Ausgabestring gene-

    riert. Der erste Parameter wird dabei dem ersten auftretenden %-Zeichen zugeordnet,

    der zweite Parameter dem zweiten %-Zeichen, usw.. Im ISO/IEC 9899:TC2 Standard

    der Programmiersprache C ist zu lesenIf there are insufficient arguments for the for-

    mat, the behavior is undefined.[11, S. 273]. Sowohl die Anzahl der Parameter als auch

    der tatsachlich an die Funktion ubergebene Datentyp muss mit den Platzhaltern des

    Format-Strings in jedem Fall ubereinstimmen. Das heit, die Formatbeschreibung muss

    den auf dem Stack abgelegten Daten entsprechen. [47].

    Das folgende kurze Beispiel aus dem Listing 3.6 zeigt eine typische Format-String-

    Sicherheitslucke, bei der ein String zur Ausgabe mit printf von einem Benutzer

    ubergeben werden kann.

    1 int main(int argc, char* argv[])

    2 {

    3 printf( argv[1] );

    4 return 0;

    5 }

    Listing 3.6: Beispielprogramm PrintDemo.c

    Die Eingabe bleibt in diesem Beispiel ungepruft und der Benutzer konnte das Pro-

    gramm mit PrintDemo "%p %p %p %p" aufrufen. Der String mit den Formatanwei-

    sungen wird als Format-String interpretiert und die Funktion printf versucht vier

    Zeiger aufgrund der vier %p auszugeben. Versuche zeigen nun, dass die Funktionen der

    printf()-Gruppe die Daten entsprechend der Beschreibung im Format-String vom

    Stack nehmen und daraus den Ausgabetext generieren. Dies geschieht unabhangig da-

    von, wie viele Parameter zuvor auf dem Stack abgelegt wurden. Die Funktion printf,

    wie im Listing 3.6 ersichtlich, legt keine zusatzlichen vier Zeiger bzw. 16 Byte auf dem

  • 3. Potentielle Schwachstellen 30

    Stack ab, sondern ausschlielich nur den Zeiger auf das Arrayelement argv[1] bzw. 4

    Byte. Dieser Missstand wird auch nicht erkannt, daher gibt die Funktion die nachsten

    zufallig auf dem Stack liegenden beliebigen 4 * 4 Byte aus:

    [dau]\$ PrintDemo "%p %p %p %p"

    0012FFC0 0040115A 00000002 02DD4FB0

    Vergleicht man diese Ausgabe nun mit einem Stack Dump, der innerhalb der Funkti-

    on main() erstellt wurde, dann ist leicht zu erkennen, dass printf Teile des Stacks

    ausgegeben hat7.

    0x0012FF7C c0 ff 12 00 Ay.. // Gesicherter Frame Pointer

    0x0012FF80 5a 11 40 00 Z.@. // Rucksprungadresse aus der Funktion main()

    0x0012FF84 02 00 00 00 .... // Funktionsparameter argc

    0x0012FF88 b0 4f dd 02 OY. // Funktionsparameter argv

    Listing 3.7: Stackdump innerhalb des Programms PrintDemo.c

    Damit sind fur den Benutzer oder, wenn Absicht dahinter steht, fur den Angreifer die

    Daten des Stacks sichtbar. Die wichtigsten fur einen Angriff verwendeten Format-String

    Platzhalter sind in der folgenden Tabelle 3.1 aufgefuhrt.

    Platzhalter Datentyp, Formatierung

    %d int, Dezimaldarstellung mit Vorzeichen%u unsigned int, Dezimaldarstellung%x unsigned int, Hexadezimaldarstellung%c char, einzelnes Zeichen (Character)%s char*, Zeichenkette (String)%p void-Zeiger (Pointer in Hexadezimaldarstellung)%n int*, schreibt Anzahl der ausgegebenen Zeichen an Adresse

    Tabelle 3.1: Ausgewahlte Format-String Platzhalter der printf-Familie

    Eine vollstandige Beschreibung aller Formatanweisungen und Steuerzeichen ist der

    IEEE Spezifikation [27]8 zu entnehmen.

    7Die umgekehrte Reihenfolge der Bytes entsteht aufgrund der Little Endian Architektur der IntelProzessoren.

    8http://www.opengroup.org/onlinepubs/009695399/functions/printf.html(Stand:2007.04.29)

    http://www.opengroup.org/onlinepubs/009695399/functions/printf.html

  • 3. Potentielle Schwachstellen 31

    Die Moglichkeiten der Manipulation und vor allem die Einfachheit einer Code Injection

    mittels Format-String Exploits zeigen die Beispiele in [50] und der Artikel [34]. Eine

    Tabelle aller unsicheren POSIX-Funktionen der C-Standardbibliothek, zu denen auch

    jene der printf-Gruppe gehoren, ist dem Anhang zu entnehmen (siehe Tabelle A.1).

  • 4

    Lokalisierung potentieller

    Schwachstellen

    Bevor Sicherheitsschwachstellen entfernt oder umgangen werden konnen, mussen die-

    se als solche identifiziert werden. Einfache Sicherheitsschwachstellen weisen oft kla-

    re Merkmale auf und konnen systematisch lokalisiert und vermieden werden. Meist

    handelt es sich um lokale Fehler in der Implementierung, also einfache Codierungs-

    fehler. Komplexe Schwachstellen sind oft schwer zu lokalisieren, so dass sie bei einer

    isolierten Betrachtung im Code keine auffalligen Merkmale aufweisen. Oft entsteht erst

    durch das Zusammenwirken verschiedenster Funktionen, Module oder Applikationen

    eine Schwachstelle, die fur einen Angriff genutzt werden kann. Dabei handelt es sich

    meist um Designfehler, welche sich nicht einfach als Codefehler lokalisieren lassen.

    Dieses Kapitel befasst sich mit den gangigsten Methoden zur Lokalisierung von Sicher-

    heitsschwachstellen. Dabei kommen Werkzeuge zur Untersuchung des Quellcodes und

    zur Untersuchung des kompilierten Programms zur Anwendung. In Programme inte-

    grierte spezielle Zusatzfunktionen dienen einerseits der Uberwachung des Programms,

    andererseits melden diese Programme dadurch Auffalligkeiten, die ebenfalls einer Si-

    cherheitsanalyse dienlich sind.

    32

  • 4. Lokalisierung potentieller Schwachstellen 33

    4.1 Allgemeines

    Die Lokalisierung potentieller Sicherheitsschwachstellen erfolgt uber sogenannte Si-

    cherheitsanalysen. Dies sind spezielle Verfahren zur gezielten Prufung von Software

    auf mogliche Sicherheitslocher im Gesamtsystem.Ein Sicherheitsloch ist jeder Fehler

    in Hardware, Software oder Richtlinien, der es einem Angreifer ermoglicht, unautori-

    sierten Zugang zu Ihrem System zu bekommen. [2, S. 334] Eine Sicherheitsanalyse

    ist demnach immer die Suche moglicher Hardware-, Software- und Designfehler oder

    -schwachen. Ebenso entspricht eine Analyse immer einem systematischen Sammeln von

    Daten zur Informationsgewinnung.

    4.1.1 Informationsgewinnung

    Fur die Analyse eines Systems benotigen Entwickler, Sicherheitsbeauftragte, Tester

    und auch potentielle Angreifer technische Details, wie z.B. Informationen uber die

    laufenden Programme und die Umgebung (z.B. Betriebssystem, Bibliotheken), unter

    denen eine Software lauft. Fur eine umfangreiche Analyse einer Software stehen mehrere

    Informationsquellen zur Verfugung (basierend auf [33]):

    Quellcode (Source Code) der Applikation und/oder der Bibliotheken

    Dokumentation (z.B. Handbucher, technische Dokumentation, Hilfesysteme)

    Datendateien und Speicherabbilder (Core Dumps)

    Log- und Trace-Dateien (z.B. Status- und Fehlerinformationen)

    Informationen aus dem laufenden Prozess durch Debugging

    Grundsatzlich muss davon ausgegangen werden, dass sowohl dem Entwickler als auch

    dem potentiellen Angreifer die gleichen Daten zur Verfugung stehen, auch wenn sich die

    Datengewinnung fur den Angreifer als aufwandiger erweisen kann. Dementsprechend

    sind einerseits die benotigten Kenntnise und andererseits die Mittel und Methoden zur

    Datengewinnung wahrend eines Sicherheitstests denen eines Angreifers nicht unahnlich.

    Treten wahrend der Analyse ungewollt sensible Daten zutage, so stehen diese auch

    jedem Angreifer zur Verfugung.

  • 4. Lokalisierung potentieller Schwachstellen 34

    4.1.2 Vollstandige Sicherheitsanalyse

    Wahrend einer vollstandigen Sicherheitsanalyse werden alle aus den verschiedensten

    Quellen gewonnenen Daten zusammengefasst und mit Fokus auf Sicherheit und Feh-

    lerfreiheit ausgewertet. Die Analyse einer Software auf Sicherheit ist auch immer die

    Analyse einer Software auf Fehlerfreiheit, weil Sicherheitsschwachstellen praktisch im-

    mer als Folge von Design- oder Implementierungsfehlern auftreten. Die vollstandige Si-

    cherheitsanalyse einer Software besteht aus folgenden Teilen (basierend auf [22, 26]):

    Lokalisierung sicherheitsrelevanter Designfehler: Die Lokalisierung der De-

    sign Flaws umfasst die Identifikation der Datenflusse, der Programm- und Daten-

    eintrittspunkte (Entry Points) und der potentiellen Gefahren, welche von Auen

    auf ein Programm einwirken.

    Lokalisierung sicherheitsrelevanter Implementierungsfehler: Die Lokali-

    sierung der Coding Bugs umfasst die Analyse der Quelltexte und der binaren und

    ausfuhrbaren Dateien.

    Stabilitatstests: Diese Tests umfassen Methoden zum absichtlichen Herbeifuhren

    von Ausnahme- und Grenzsituationen. Die Software wird unter Stress getestet

    und ihr Verhalten in Extremsituationen beobachtet (Stress Test).

    Sicherheitstests: Diese Tests umfassen eine Prufung der Daten und Prozesssi-

    cherheit auf Basis der Designvorgaben, wie z.B. die Prufung der Zugriffs- und

    Rechteverwaltung, die Verschlusselung, uvm. (vlg. [26]).

    Das naheliegendste, wenn auch nicht immer einfachste Verfahren, um Fehler und Si-

    cherheitsschwachen einer Software zu finden, ist das manuelle Audit. Liegt der Quell-

    text vor, konnen durch eine umfassende Quelltextinspektion (Source Code Audit) die

    Quelldateien zeilenweise uberpruft und gleichzeitig die gefundenen Schwachstellen be-

    hoben werden. Fehler in einfachen Programmen lassen sich manuell durchaus noch

    lokalisieren. Bei groeren Projekten ist dies aufgrund der hohen Komplexitat und des

    Umfangs des Quelltextes auerst zeit-, ressourcen- und kostenintensiv [31]. Ein weiteres

    und nicht unerhebliches Problem bei der Durchfuhrung manueller Source Code- bzw.

    Sicherheits-Audits ist die dafur notwendige Fachkenntnis im Bereich der defensiven

  • 4. Lokalisierung potentieller Schwachstellen 35

    Programmierung. Die Zuhilfenahme spezieller Werkzeuge fur automatisierte Software-

    Audits liegt ebenso nahe wie die Anwendung verschiedenster Verfahren.[26]

    4.1.3 Statische und dynamische Analyseverfahren

    Bei allgemeinen Softwareanalysen und der Lokalisierung von Sicherheitsschwachstellen

    unterscheidet man zwischen statischen und dynamischen Analyseverfahren. Die stati-

    sche Analyse verzichtet auf das Ausfuhren der Software. Die Software wird auf Basis

    des Quelltextes und zusatzlicher Dateien (wie z.B. Konfigurationsdateien) analysiert.

    Dabei werden Funktionen und Konstrukte im Quelltext gesucht, welche eine Sicher-

    heitslucke darstellen konnten. Die erweiterte statische Analyse ist eine auf dem Quell-

    text basierende Vorwegnahme der Aktionen und Reaktionen der Software. Aus der

    Sicht der Werkzeuge ist sie aufwandig, weil auf Basis des Quelltextes die Ablaufe, die

    moglichen Aktionen und Programmfaden (Threads) simuliert werden mussen. Bei der

    dynamischen Analyse wird das laufende Programm uberwacht bzw. die Datenstrome

    eines Programms analysiert. Die Tests konnen in der realen oder einer simulierten

    und kontrollierten Umgebung stattfinden. Wahrend der Beobachtung der Ablaufe und

    der Re-/Aktionen der Software konnen Logfiles mit Statusmeldungen erzeugt werden.

    Tritt eine Fehler auf, so kann ein Speicherabbild (Memory Dump) oder ein Stackabbild

    Stack Trace erzeugt werden. Auf Basis dieser Daten sind weitere Analysemethoden

    zum Auffinden der Fehlerursache moglich. [31, 60]

    Am Beginn jeder Softwareanalyse steht die Untersuchung des Quelltextes. Diese kann

    und sollte auch schon begleitend wahrend der Entwicklung der Software stattfinden.

    Erst wenn der Code vermeintlich korrekt ausgefuhrt ist, werden komplexere Analyse-

    methoden das System wahrend der Laufzeit analysieren. Der nun folgende Abschnitt

    beschreibt einige Methoden und Werkzeuge der quelltextbasierten Sicherheitsanalyse.

    4.2 Quelltextbasierte Analyse

    Die quelltextbasierte Softwareanalyse gehort zu den statischen Analysemethoden, wel-

    che auf die Ausfuhrung der zu prufenden Software verzichten. Der Quellcode wird einer

  • 4. Lokalisierung potentieller Schwachstellen 36

    Reihe von formalen Prufungen, den sogenannten White Box Tests, unterzogen, wobei

    damit hauptsachlich mogliche Schwachstellen in der Implementierung entdeckt werden.

    Mit der Quelltextanalyse konnen folgende Mangel entweder mit (semi-)automatischen

    Werkzeugen oder durch ein manuelles Code Audit lokalisiert werden:

    fehlerhaften Typenkonvertierungen (Illegal Casts)

    mogliche Uberlaufe (Overflows)

    illegale Zeiger und Nullzeiger (Null Pointer)

    Uber- und Unterschreitung von Speicherbereichsgrenzen (Bounds Checks)

    Speichermanagementfehler (Memory Leaks)

    Compiler- und Betriebssystemabhangigkeiten

    uninitialisierte Variablen und Speicherbereiche

    unsichere Funktionen

    das Nichteinhalten von Codierungsvorschriften (Coding Standards)

    das Nichteinh