DIPLOMARBEIT. Lokalisierung und Vermeidung potentieller Sicherheitsschwachstellen in komplexen Softwaresystemen



Ähnliche Dokumente
Datensicherung. Beschreibung der Datensicherung

Inhalt. 1 Einleitung AUTOMATISCHE DATENSICHERUNG AUF EINEN CLOUDSPEICHER

Binäre Bäume. 1. Allgemeines. 2. Funktionsweise. 2.1 Eintragen

Objektorientierte Programmierung für Anfänger am Beispiel PHP

4D Server v12 64-bit Version BETA VERSION

Albert HAYR Linux, IT and Open Source Expert and Solution Architect. Open Source professionell einsetzen

Vorkurs C++ Programmierung

Fachbericht zum Thema: Anforderungen an ein Datenbanksystem

Systeme 1. Kapitel 6. Nebenläufigkeit und wechselseitiger Ausschluss

Klausurteilnehmer. Wichtige Hinweise. Note: Klausur Informatik Programmierung, Seite 1 von 8 HS OWL, FB 7, Malte Wattenberg.

Datensicherheit. Vorlesung 7: Sommersemester 2015 h_da. Heiko Weber, Lehrbeauftragter

Formular»Fragenkatalog BIM-Server«

SDD System Design Document

ICS-Addin. Benutzerhandbuch. Version: 1.0

Schwachstellenanalyse 2012

Lineargleichungssysteme: Additions-/ Subtraktionsverfahren

Informationssystemanalyse Problemstellung 2 1. Trotz aller Methoden, Techniken usw. zeigen Untersuchungen sehr negative Ergebnisse:

Diplomarbeit. Konzeption und Implementierung einer automatisierten Testumgebung. Thomas Wehrspann. 10. Dezember 2008

Version smarter mobile(zu finden unter Einstellungen, Siehe Bild) : Gerät/Typ(z.B. Panasonic Toughbook, Ipad Air, Handy Samsung S1):

The ToolChain.com. Grafisches Debugging mit der QtCreator Entwicklungsumgebung

Objektorientierte Programmierung

Einführung in PHP. (mit Aufgaben)

Installation SQL- Server 2012 Single Node

Update und Konfiguraton mit dem ANTLOG Konfigurations-Assistenten

OP-LOG

Analyse zum Thema: Laufzeit von Support-Leistungen für ausgewählte Server OS

ISA Server Best Practice Analyzer

! " # $ " % & Nicki Wruck worldwidewruck

Adobe Photoshop. Lightroom 5 für Einsteiger Bilder verwalten und entwickeln. Sam Jost

VB.net Programmierung und Beispielprogramm für GSV

.htaccess HOWTO. zum Schutz von Dateien und Verzeichnissen mittels Passwortabfrage

GEZIELT MEHR SICHERHEIT MIT 4I ACCESS SERVER & 4I CONNECT CLIENT

Anforderungen an die HIS

Anleitung zur Daten zur Datensicherung und Datenrücksicherung. Datensicherung

Research Note zum Thema: Laufzeit von Support-Leistungen für Server OS

Speicher in der Cloud

2 Die Terminaldienste Prüfungsanforderungen von Microsoft: Lernziele:

Handbuch Fischertechnik-Einzelteiltabelle V3.7.3

Robot Karol für Delphi

Task: Nmap Skripte ausführen

Prozessbewertung und -verbesserung nach ITIL im Kontext des betrieblichen Informationsmanagements. von Stephanie Wilke am

Verarbeitung der Eingangsmeldungen in einem Callcenter

Übungen zu Einführung in die Informatik: Programmierung und Software-Entwicklung: Lösungsvorschlag

SafeRun-Modus: Die Sichere Umgebung für die Ausführung von Programmen

C++11 C++14 Kapitel Doppelseite Übungen Musterlösungen Anhang

5 DATEN Variablen. Variablen können beliebige Werte zugewiesen und im Gegensatz zu

Erweiterung AE WWS Lite Win: AES Security Verschlüsselung

Wie halte ich Ordnung auf meiner Festplatte?

Übung zu Grundlagen der Betriebssysteme. 13. Übung

Lokale Installation von DotNetNuke 4 ohne IIS

.NET Code schützen. Projekt.NET. Version 1.0

Virtual Desktop Infrasstructure - VDI

Multicast Security Group Key Management Architecture (MSEC GKMArch)

Das System sollte den Benutzer immer auf dem Laufenden halten, indem es angemessenes Feedback in einer angemessenen Zeit liefert.

Die Programmiersprache Java. Dr. Wolfgang Süß Thorsten Schlachter

Einführung in die technische Informatik

Persönliche Zukunftsplanung mit Menschen, denen nicht zugetraut wird, dass sie für sich selbst sprechen können Von Susanne Göbel und Josef Ströbl

S/W mit PhotoLine. Inhaltsverzeichnis. PhotoLine

Outlook Vorlagen/Templates

Beschreibung und Bedienungsanleitung. Inhaltsverzeichnis: Abbildungsverzeichnis: Werkzeug für verschlüsselte bpks. Dipl.-Ing.

Anleitung mtan (SMS-Authentisierung) mit SSLVPN.TG.CH

Überprüfung der digital signierten E-Rechnung

Was ist PDF? Portable Document Format, von Adobe Systems entwickelt Multiplattformfähigkeit,

Ordner Berechtigung vergeben Zugriffsrechte unter Windows einrichten

Sicherheit in Software

Leichte-Sprache-Bilder

Anleitung zur Inbetriebnahme einer FHZ2000 mit der homeputer CL-Software

Virtueller Speicher. SS 2012 Grundlagen der Rechnerarchitektur Speicher 44

Deklarationen in C. Prof. Dr. Margarita Esponda

Diese Ansicht erhalten Sie nach der erfolgreichen Anmeldung bei Wordpress.

schnell und portofrei erhältlich bei beck-shop.de DIE FACHBUCHHANDLUNG mitp/bhv

Reporting Services und SharePoint 2010 Teil 1

GEORG.NET Anbindung an Ihr ACTIVE-DIRECTORY

Es sollte die MS-DOS Eingabeaufforderung starten. Geben Sie nun den Befehl javac ein.

Informationen zum neuen Studmail häufige Fragen

Tevalo Handbuch v 1.1 vom

Avira Support Collector. Kurzanleitung

Synchronisations- Assistent

Lizenzierung von SharePoint Server 2013

Programmieren was ist das genau?

Eigene Dokumente, Fotos, Bilder etc. sichern

SEP 114. Design by Contract

Programme im Griff Was bringt Ihnen dieses Kapitel?

Grundlagen von Python

Handbuch ECDL 2003 Basic Modul 5: Datenbank Grundlagen von relationalen Datenbanken

Das Handbuch zu KNetAttach. Orville Bennett Übersetzung: Thomas Bögel

Zählen von Objekten einer bestimmten Klasse

MetaQuotes Empfehlungen zum Gebrauch von

Installieren von Microsoft Office Version 2.1

Tutorium Informatik 1. Aufgabe 2: Formatierte Ein- und Ausgabe

Folgende Einstellungen sind notwendig, damit die Kommunikation zwischen Server und Client funktioniert:

Buddy - Algorithmus Handbuch für Endnutzer Stand

How-to: Webserver NAT. Securepoint Security System Version 2007nx

Anleitung über den Umgang mit Schildern

Gruppenrichtlinien und Softwareverteilung

Anleitung Captain Logfex 2013

Protect 7 Anti-Malware Service. Dokumentation

M. Graefenhan Übungen zu C. Blatt 3. Musterlösung

Dieses Dokument beschreibt die Installation des Governikus Add-In for Microsoft Office (Governikus Add-In) auf Ihrem Arbeitsplatz.

Einrichtung des Cisco VPN Clients (IPSEC) in Windows7

Transkript:

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

Eidesstattliche Erklärung Hiermit versichere ich, Roland J. Graf, geboren am 6. Mai 1964, dass die vorliegende Diplomarbeit von mir selbständig verfasst wurde. Zur Erstellung wurden von mir keine anderen als die angegebenen Hilfsmittel verwendet. Roland Graf 0310032093 Matrikelnummer ii

Danksagung Alles Wissen und alle Vermehrung unseres Wissens endet nicht mit einem Schlusspunkt, sondern mit Fragezeichen. Hermann Hesse (1877-1962) Meinen Eltern gebührt an dieser Stelle mein besonderer Dank. Gerade die ersten Jahre meiner schulischen Laufbahn waren alles andere als Erfolg versprechend und trotzdem 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 fachlichen auch menschliche Werte vermitteln. Ich hatte das Glück, einige dieser ganz wenigen Lehrer zu treffen. Stellvertretend möchte ich hier besonders Hr. Prof. Dr. Gerold Kerer hervorheben, der nicht nur bereits vor über 20 Jahren als mein HTL-Lehrer mit seinen fachlichen und pädagogischen Fähigkeiten glänzte, sondern mich auch in der FH-Salzburg wieder durch seine außergewöhnliche Menschlichkeit und Qualifikation beeindruckt hat. Dank gilt auch meinen Kollegen am Studiengang ITS, die mich in so manchen Gesprächen und Diskussionen mit ihren Eingaben, Ideen, Fragen und Hinweisen geleitet haben. Besonders hervorheben möchte ich meinen Diplomarbeitsbetreuer DI(FH) Thomas Kurz und allen voran FH-Prof. DI Dr. Thomas Heistracher, welche mich auch als Reviewer mit konstruktiver Kritik sehr unterstützt haben. Von meinen Kommilitonen verdient Dietmar eine Erwähnung. Durch seine kritischen Verbesserungsvorschläge hat er mich oft zu einer Mehrleistung getrieben. Zuletzt möchte ich noch Sabine danken. Ohne sie wäre mein Studium neben dem Beruf so gar nicht möglich gewesen. Sie hat mich über all die Jahre tatkräftig unterstützt und mir auch in schweren Zeiten den notwendigen Halt, die Kraft und die Stabilität gegeben, die ich gebraucht habe. Niemand kann so positiv formulieren, wie sie es tut und so war ich bevorteilt, indem sie als Germanistin all meine Arbeiten sprachlich redigiert hat. Ihr gebührt jedenfalls mein größter Dank! Und wenn Hesse folgend nun all meine Anstrengung zur Vermehrung des Wissens mit einem Fragezeichen endet, dann bleibt noch eine Frage zu stellen: Was kommt jetzt? iii

Informationen Vor- und Zuname: Institution: Studiengang: Titel der Diplomarbeit: Betreuer an der FH: Roland J. Graf Fachhochschule Salzburg GmbH Informationstechnik & System-Management Lokalisierung und Vermeidung potentieller Sicherheitsschwachstellen in komplexen Softwaresystemen DI(FH) Thomas Kurz Schlagwörter 1. Schlagwort: Software Security 2. Schlagwort: Software Vulnerability 3. Schlagwort: Code Injection Abstract This diploma thesis documents the usability of tools to localize potential security vulnerabilities and evaluates the effectiveness of development methods to avoid them. Mostly, vulnerabilities are based on software bugs and design flaws. This paper provides the basics of memory segmentation, processor registers and stack frames, before it explains software bugs as the cause of potential software vulnerabilities and their risk potential. A variety of software tools are available to implement Static White Box Tests and Dynamic Black Box Tests. Source Code Analysis Tools support the developers to parse for potential bugs in the source code, Debugging, Tracing and Monitoring Tools help the software and security testers to spy on data flows, function calls and flaws in executable binaries. This document reports the strengths and weaknesses of tested tools and methods and discusses their expected effectiveness in production environments. Adapted development methods can increase the resistance of software to attacks and unauthorized data manipulations. Finally, an introduction to Defensive Programming with helpful programming hints, additional tables and references, code examples, and Best Practices for programmers will be given, which aims at helping developers to write secure software. iv

Inhaltsverzeichnis Eidesstattliche Erklärung Danksagung Informationen Schlagwörter Abstract Abbildungsverzeichnis Tabellenverzeichnis Listingverzeichnis ii iii iv iv iv x xi xii 1 Einführung 1 1.1 Global vernetzte Sicherheitsschwächen.................. 2 1.2 Stabile Software(un-)sicherheit....................... 3 1.3 Motivation.................................. 4 1.4 Überblick.................................. 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 Vollständige 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 Binärcodebasierte 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 Überwachung................... 63 4.4.1 Bounds Checking.......................... 63 4.4.2 Überwachung des Stacks...................... 64 4.4.3 Überwachung von Funktionen................... 65 4.4.4 Überwachung 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 Überprüfung 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 Zusätzliche 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 Abkürzungsverzeichnis 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 über Security Cookies.............. 120 C.2 Einfache Speicherüberwachung in C++.................. 122 C.3 Defensive Programmierung......................... 123 C.3.1 Überprüfung 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 über Return Codes.......... 127 C.5.2 Sicherung von Funktionen über 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 für 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 Weiterführende 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 Aufspüren 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 Ausgewählte 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 für 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 Überladen 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 über Exceptions in einem korrekten C# Code.... 128 xii

1 Einführung An application should be considered unsafe until demonstrated to be otherwise. (Swiderski, 2004) Die Häufung von Veröffentlichungen kritischer Sicherheitslücken in Applikationen, Netzwerkdiensten und Betriebssystemen macht deutlich, dass ein Großteil der Applikationen und Computersysteme noch immer nicht sicher genug und ausreichend geschützt ist. Technische Detailinformationen über bestimmte Einbruchsmöglichkeiten in Computersysteme und die ausgenutzten Schwachstellen werden größtenteils online publiziert 1. Umfangreiche Beschreibungen der oftmals kreativen Methoden der Angreifer und der möglichen Abwehrmethoden sind jedem Interessierten frei zugänglich. Diese machen auch immer wieder deutlich, dass ein Großteil der Sicherheitsschwachstellen die Folge von Design- und Codierungsfehlern in einzelnen Teilen der Software ist. Ebenso fällt dabei auf, dass bestimmte Fehler besonders häufig als Grund für eine Sicherheitsschwachstelle genannt werden. Die Softwareindustrie wird vermehrt angehalten, fehlerfreie und sichere Software zu entwickeln. Der Druck auf die Softwareentwickler steigt. Trotz der bekannten Mängel 1 Im Internet werden fast täglich Nachrichten von kritischen Softwarefehlern und Sicherheitslücken veröffentlicht. Security Online Archive (z.b. SANS, CERT), einschlägige Mailinglisten, namhafte Unternehmen im Bereich Computer- und Netzwerksicherheit und News-Dienste (z.b. Heise Security, SecurityFocus, Computer Crime & Intellectual Property Section), selbst Hacker und Cracker liefern Informationen dazu größtenteils frei Haus. Eine Reihe weiterer Quellen vervollständigen diese Berichte und stellen Auswertungen über Trends, Statistiken und Top-10 Listen über mehr oder weniger erfolgreiche Attacken auf verwundbare Computersysteme zur Verfügung. Die Gesamtheit dieser Informationen ergibt einen aktuellen Lagebericht über IT-Sicherheit, sicherheitskritische Softwaremängel und deren Ursachen. Eine Liste ausgewählter Quellen findet sich im Anhang F am Ende dieses Dokuments. 1

1. Einführung 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 Sicherheitsschwächen Die Firma Sun Microsystems 2 hat vor einigen Jahren schon in der Vision The Network is the Computer die hochgradige Vernetzung der Informationstechnologie und die damit einhergehenden technologischen Veränderungen vorhergesehen. Mittlerweile sind fast alle Computer über ein Netzwerk oder das weltumspannende Internet miteinander verbunden. Desktop-Computer, Server und Router, Pocket-Computer, mobile Telefone, Embedded Systems, Fernseher, Multimedia Systeme, jede Menge mikroprozessorgesteuerte Geräte und eine Unzahl von Peripheriegeräten sind Teile eines oder des globalen Netzwerks geworden. Dass sich durch diese Vernetzung auch das Bedrohungspotential durch mögliche 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 Geräte und Systeme untereinander wächst auch der Druck, die Sicherheitsvorkehrungen bei Computersystemen und allen Systemteilen entsprechend anzupassen. Konnte früher noch von lokalen Bedrohungen und Angriffen (Local Exploits), von lokalen 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 Systeme stellen dabei ein besonders großes Risiko und manchmal auch einen besonderen Reiz für Angreifer dar. Das Internet ist eine feindselige Umgebung, deshalb muss der Programmcode so entworfen sein, dass er einem Angriff widerstehen kann. Vernetzte Systeme bedürfen also einer expliziten Sicherung gegen mögliche Angriffe. Jede potentielle Schwachstelle eines Systemglieds mindert die Sicherheit des gesamten Systems oder stellt sie gar in Frage. Ein einzelner Sicherheitsmangel einer Applikation kann 2 http://www.sun.com

1. Einführung 3 schon für den Angriff des Gesamtsystems missbraucht werden. Ziel und Voraussetzung für ein sicheres Computersystem ist demnach die Sicherheit jeder einzelnen Komponente. Nur so kann die Sicherheit des Gesamtsystems gewährleistet werden und ein System den Attacken und Gefahren des Wild Wild Web [26, S. 5] standhalten. Die Entwicklung fehlerfreien Quellcodes ist längst nicht mehr genug, wenngleich eine der unabdingbaren Voraussetzung für sichere Software. 1.2 Stabile Software(un-)sicherheit Wenn von Sicherheitslücken 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 darüber seltener berichtet wird, sind ebenfalls möglich und nicht minder gefährlich. Viele dieser oftmals lange unentdeckten Fehler stellen massive Sicherheitslücken dar. Sie bieten eine Angriffsfläche für mögliche Attacken gegen einzelne Applikationen, Computersysteme oder gesamte Netzwerke. Vor einigen Jahren galt es noch als ausreichend stabile Software zu entwickeln. Es genügte, wenn eine Applikation die Anforderungen der Endbenutzer erfüllte. Zusätzliche sicherheitsrelevante Forderungen wurden kaum erhoben. Softwareentwickler wurden angehalten soliden, stabilen, wartbaren und erweiterbaren Code zu schreiben. In dieser Zeit spielte Codesicherheit selbst in klassischen Standardwerken der Softwareentwicklung wie [35] und [36] kaum eine Rolle. Gute Software stellte sich dem Benutzer ausschließlich als stabil laufende Software dar. Dem Schutz vor mutwilligen Manipulationen wurde kaum Beachtung geschenkt. Mittlerweile wird vermehrt und ausdrücklich die Entwicklung sicherer Software gefordert. Langsam beginnen auch Softwareproduzenten und Benutzer ein allgemeines Sicherheitsbewusstsein zu entwickeln. Für 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 Hinblick auf die Abwehr potentieller Gefahren und Attacken entworfen wurden. Auch wenn fehlerfreier Code nicht automatisch eine sichere Software garantiert, so gilt ein Gutteil

1. Einführung 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 spezielle Methoden schon während der Entwicklung und während des gesamten Lebenszyklus einer Applikation erreicht werden. Die Bedrohungsmodellierung (Threat Modeling) hilft Gefährdungspotentiale frühzeitig zu identifizieren, zu evaluieren, sie zu dokumentieren und Gegenmaßnahmen 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 möglichen gezielten Angriffen standzuhalten. Die verwendeten Modelle, die Methoden und die Implementierungen werden stetig angepasst und nötigenfalls erweitert, um im veränderten Risikoumfeld zu bestehen und den ständig neuen Anforderungen zu entsprechen. Begleitende Maßnahmen während aller Entwicklungsphasen einer Applikation oder eines Softwaremoduls schaffen erst die Voraussetzungen für 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 Überblick beschreibt die einzelnen Kapitel der vorliegenden Arbeit.

1. Einführung 5 1.4 Überblick Das folgende Kapitel 2 führt in ausgewählte technische Grundlagen der Softwareentwicklung ein und erklärt die zum Verständnis notwendigen Begriffe sowie das Speichermanagement einer Applikation. Nachdem ein Großteil der Systemsoftware nach wie vor in C/C++ programmiert ist, werden diese beiden Programmiersprachen auch bevorzugt in die Erklärungen einfließen. In Kapitel 3 werden potentielle Sicherheitsschwachstellen einer Software und darauf basierende Angriffsmethoden vorgestellt. Einige einfache Beispiele zeigen die praktische Umsetzung und Einfachheit eines Buffer Overflow Angriffs auf ungesicherte Software. Das Kapitel 4 untersucht ausgewählte Methoden der systematischen Lokalisierung potentieller Code- und Sicherheitsschwachstellen. Zur Anwendung kommen dabei quellcodebasierte und binärcodebasierte Analysemethoden. Nachdem die manuelle Prüfung von Code oft nicht effizient genug, sehr aufwändig und teuer ist, werden ebenso Werkzeuge zur automatischen Softwareanalyse geprüft. In Kapitel 5 steht die Vermeidung potentieller Schwachstellen im Vordergrund. Schwerpunkt ist dabei die Diskussion von Methoden zur Erstellung von sicheren Designs und zur Entwicklung sicherer Implementierungen. Das schon für die Designphase empfohlene Threat Modeling bleibt hier ebenso wenig unbehandelt wie die Anwendung der Prinzipien des defensiven Programmierens. In Kapitel 6 schließt ein kurzer Ausblick in die Zukunft die Arbeit ab. Darin wird erläutert, wie aus der Sicht von Experten die Sicherheit komplexer Softwaresysteme in Zukunft gewährleistet werden könnte. Die kurze Zusammenfassung schließt mit der Beantwortung der Frage ab, ob die aktuellen Methoden der Softwareentwicklung schon heute ausreichen würden, um die Sicherheit komplexer Softwaresysteme sicherzustellen oder ob erst in Zukunft eine echte Softwaresicherheit möglich sein wird.

2 Grundlagen Fast alle Sicherheitslücken basieren, wie aus den im Kapitel 1 angeführten Quellen hervorgeht, auf Programmfehlern und ein Großteil aller Angriffe basiert auf dem Prinzip der Speichermanipulation. Selbst wenn ein Programm im Normalbetrieb über längere Zeit stabil läuft, so bedeutet dies nicht zwingend, dass sich keine Fehler im zugrundeliegenden Programmcode befinden. Erst die Konfrontation eines Programms bzw. einer Funktion mit für 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 Komplexität eines Quelltextes oder einer Softwarearchitektur. Bevor die technischen Grundlagen des Speichermanagements einer Applikation erklärt werden, wird der Begriff komplexe Softwaresysteme eingeführt. 2.1 Komplexe Softwaresysteme Die Definition eines komplexen Softwaresystems kann gerade aus der Sicht der Softwareentwicklung eindeutig festgelegt werden. Unter komplex kann jede Software bezeichnet 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. Zeitgemäße und fortgeschrittene Entwicklungsmethoden versuchen der Komplexität durch Modularisierung und Aufteilung in überschaubare Funktionseinheiten entgegenzuwirken. Dass dieses Vorhaben nicht zwingend zum Erfolg führen muss, zeigt die Zahl der Veröffentlichungen (z.b. Bug Reports) und Fehlerkorrekturen (Bug Fixes) komplexer Software der letzten Jahre 1. 2.2 Speicherorganisation Dieses Kapitel führt einige Begriffe der Speicherverwaltung (Memory Management) ein. Es erklärt 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 für fast alle anderen gängigen Prozessorarchitekturen. Im folgenden Listing 2.1 wird ein kurzes C-Programm in Auszügen gezeigt. Es verwendet 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 Erläuterungen 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 } 1 Häufig wird z.b. bei größeren Service Packs von Betriebssystemen und Office Paketen die Anzahl der behobenen Fehler mit einigen Hundert angegeben. Microsoft veröffentlicht in einem monatlichen Updatezyklus Service Packs, Updates und Patches und beziffert die Anzahl der kritischen und zusätzlich geschlossenen Sicherheitslücken im Schnitt mit etwa 20 Fehlern pro Monat. Siehe dazu auch 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 <= ilen; i++ ) 21 pbuff1[i] = argv[1][i]; // Heap und Off-By-One Overflow 22 strcpy( pbuff2, argv[2]); // => 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 ausführbare Datei 2 (Executable), welche auf einem Datenträger gespeichert ist. Dabei kann es sich zum Beispiel, wie unter 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 ausführbaren Programmcode und dessen statische Daten, sondern beschreiben die Objektdatei. Sie speichern ebenso zusätzliche 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, 2 Ausführbare Scriptdateien, wie sie z.b. unter Unix-basierten Systemen häufig vorkommen, sind von diesen Betrachtungen ausgenommen. Diese können nicht direkt ausgeführt werden, sondern benötigen ein zusätzliches Programm (Interpreter), welches die einzelnen Script Statements interpretiert und zur Ausführung bringt. 3 Diese Bezeichnung ist eigentlich irreführend, da dieser Header nicht optional ist und unbedingt notwendige Informationen zur Größe des beim Start benötigten Speichers beinhaltet.

2. Grundlagen 9 Teile dieser Objektdatei vom Program Loader in den Hauptspeicher geladen, der Speicher entsprechend konfiguriert und der Programmcode zur Ausführung 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 Einsprungpunkt (Entry Point) die Funktion main. Ein laufendes Programm wird als Prozess bezeichnet [23]. In modernen Betriebssystemen wird jedem laufenden Prozess ein virtueller Adressraum zur Verfügung gestellt, welcher von der Memory Management Unit (MMU) in physische Speicheradressen umgesetzt wird. Einem Prozess stehen nun separat organisierte Speicherregionen bzw. Speichersegmente (Memory Segments) innerhalb seines Adressbereichs zur Verfügung, in denen sich sein Programmcode und statische Daten befinden und auch temporäre Daten abgelegt werden können. 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) ausführt, abgelegt. Dieses Segment ist als read-only markiert, das heißt, es kann nur lesend darauf zugegriffen werden. Damit kann der ausführbare Code des Prozesses weder versehentlich noch mutwillig modifiziert werden. Jeder Versuch, den Speicher in diesem Segment zu manipulieren, würde sofort zu einer entsprechenden Ausnahmebehandlung (Exception) und zu einem Programmabbruch führen. 2.2.3 Data-Segment Im Data-Segment werden nur bestimmte Daten des Prozesses, jedoch kein ausführbarer Code abgelegt. Das Data-Segment hat eine feste, beim Programmstart zugewiesene Größe und nimmt alle vor dem eigentlichen Programmstart initialisierten globalen Variablen (z.b. primitive Variablen, Arrays, Puffer, Strukturen, Zeiger, Objektdaten) auf.

2. Grundlagen 10 Das Data-Segment kann während der Programmausführung gelesen und beschrieben werden, um den Inhalt der dort gespeicherten Variablen während der Laufzeit ändern zu können. 0xC0000000 hohe Adresswerte Stack dynamisches Wachstum vorhergehende Stack Frames Verfügbarer Speicher Heap BSS Data dynamisches Wachstum Function Stack Frame Funktionsparameter Funktion Return Address. gesicherter Frame Pointer Lokal deklarierte Variablen und Puffer optionale Prozessdaten niedrige Adresswerte 0x08000000 Text Abbildung 2.1: Typischer Prozessspeicher- und Stackaufbau einer C/C++ Applikation Der BSS-Bereich 4 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), wohingegen alle initialisierten globalen und statischen Variablen außerhalb 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 Betriebssystem mit Nullen gefüllt. 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 Länge 4 BSS steht als Abkürzung für Block Started by Symbol 5 Ein Großteil 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-Bits enthält) abgeschlossen sein muss. A string is a contiguous sequence of characters terminated by and including 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 value of a string is the sequence of the values of the contained characters, in order. [11, S. 164] Explizite Längenangaben werden also nicht gespeichert. Würde das 0-Byte als Ende-Zeichen fehlen, würden String-verarbeitende Funktionen diese Zeichenkette als so lange interpretieren, bis zufällig ein 0-Byte im Speicher vorkommt.

2. Grundlagen 11 0 [11]. So wird sichergestellt, dass sich keine unerwünschten Werte in den uninitialisierten 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 Möglichkeit, erst während der Programmausführung Speicher vom Betriebssystem anzufordern. Dafür werden eigene Bibliotheksfunktionen (Memory Management Functions) zur Verfügung gestellt, die den verfügbaren und belegten Speicher verwalten. Ein Prozess kann einen Speicher anfordern und erhält dabei einen Zeiger auf diesen Speicher (z.b. über malloc(), siehe Listing 2.1, Zeile 19). Benötigt 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 Speicherallokation noch die Größe des Speichers vorgegeben ist, wird von einer dynamischen Speicherverwaltung bzw. einer Dynamic Memory Allocation gesprochen. Alle dynamisch angeforderten Speicherblöcke befinden sich innerhalb eines speziell dafür vorgesehenen, dynamisch wachsenden Speicherbereichs, dem sogenannten Heap des Programms. Die Größe des Heaps wird durch den verfügbaren Speicher abzüglich der Größe des Stacks limitiert (siehe Abbildung 2.1). Ein Prozess kann maximal soviel Speicher benutzen, wie diesem von der Speicherverwaltung auf Abruf zur Verfügung gestellt wird. Die Lage der Speicherblöcke ist vom laufenden Prozess nicht beeinflussbar und wird von der Speicherverwaltung des Betriebssystems bestimmt. Mehrmalige Speicheranforderungen und Freigaben führen aufgrund der internen Organisation der belegten und freien Speicherbereiche zu einer Fragmentierung (Memory Fragmentation) des Speichers. Auf dem Heap allokierter Speicher ist solange gültig, bis er wieder freigegeben 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 Verfügung stellen zu können.

2. Grundlagen 12 2.2.5 Stack Der Stack wächst dynamisch und teilt sich gemeinsam mit dem Heap den einer Applikation zur Verfügung stehenden freien Speicher. Der Stack wächst, 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 höchsten Adresse beginnend nach und nach den Stack befüllt. Auf dem Stack eines Prozessors einer Intel x86 Architektur können folgende Daten innerhalb eines einzigen Stackframes abgelegt werden [8]: Funktionsparameter - Alle beim Aufruf einer Funktion übergebenen Parameter liegen auf dem Stack. Innerhalb der Funktion entspricht ein übergebener Parameter einer lokalen Variablen. Funktionsrücksprungadresse - Unmittelbar vor Beendigung einer Funktion wird der Rückgabewert der Funktion in einem Register des Prozessors oder auf dem Stack abgelegt und zur aufrufenden Funktion bzw. zur Funktionsrücksprungadresse (Function Return Address) zurückgekehrt. 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 Effizienzgründen auf dem Stack gesicherte Frame Pointer enthält 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 müssen, entweder im allgemeinen Datensegment oder im BSS-Bereich abgelegt. Optionale Prozessdaten und Zeiger - Je nach Architektur und Compiler können noch weitere Daten auf dem Stack abgelegt werden, z.b. eine Adresse zur Ausnahmebehandlung (Exception Handler Frame), zwischengespeicherte Register des Prozessors (Callee Save Registers).

2. Grundlagen 13 Im Gegensatz zu Speicherblöcken auf dem Heap hat ein Stack Frame immer eine begrenzte Lebensdauer und limitierte Größe. Im Beispiel aus Listing 2.1 werden alle lokalen auto-variablen (siehe Listing 2.1, Zeilen 16 und 7) und Verwaltungsdaten (Funktionsrücksprungadresse) auf dem Stack gespeichert. Wird die Funktion verlassen, werden auch die aktuellen Stack Frames wieder vom Stack entfernt. Die Zugriffsorganisation erfolgt ähnlich 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 Größe des Stacks. Eine Fragmentierung des Stacks aufgrund der LIFO-Organisation ist nicht zu befürchten. 2.3 Register Ein Prozessor besitzt nur einen sehr kleinen, jedoch sehr schnellen internen Speicher zur Abarbeitung eines Programms. Ein Teil dieses Speichers wird für interne Zwecke verwendet und ist von Außen bzw. für Programme nicht zugänglich. Ein anderer kleiner Teil dieser Speicherplätze wird für Ein- und Ausgabeoperationen, das Verschieben und Manipulieren von Speicher, zur Übergabe bestimmter Parameter, zur Adressierung und Indizierung von Speicher, zur Rückgabe von Ergebnissen und für Zähler verwendet. Dieser Teil der Speicherplätze wird im Allgemeinen als Register bezeichnet. Die für eine Applikation verfügbaren Register werden als General Purpose Registers (GPR) bezeichnet, welche in allen Architekturen in ähnlicher Form zur Verfügung 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 für 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 angeführten 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 Prozessoren haben in der Regel einen größeren und schnelleren internen Speicher und können

2. Grundlagen 14 oft mehrere Speicherplätze in kürzester Zeit oder parallel bearbeiten. Sie bieten eine größere Registerbreite (Bits pro Register) und können dadurch mehr Speicher direkt adressieren. Die Anzahl und Art der Register ist höchst unterschiedlich, alle modernen 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, für Angreifer vielleicht beabsichtigte, Auswirkung auf das Programm oder den Programmfluss haben kann. Die für 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 für einen Zeiger auf eine Speicheradresse. Man unterscheidet dabei zwischen Zeigern auf Daten (Data Pointer) und Zeigern auf Funktionen (Function Pointer). Ein Zeiger zeigt immer an den Beginn eines Speicherbereichs. Über den Zeiger selbst, welcher ausschließlich nur die Adresse darstellt, kann keinerlei Aussage über die Größe des Speicherblocks, die an dieser Adresse abgelegten Daten und deren Datentypen getroffen werden. Über die Lage des Speicherblocks kann maximal auf die grundsätzliche Verwendung - Daten oder Programmcode - geschlossen werden.[47] Datenzeiger (Data Pointer) Funktionszeiger (Function Pointer) Normale Variable int ivalue; Wert Pointer Variable int (*pffoo)(); Adresse pffoo(); Pointer Variable int* pvalue; Adresse *pvalue = ivalue; Wert int foo() {... } Abbildung 2.2: Daten- und Funktionszeiger Innerhalb eines Programms wird häufig über Zeigervariablen auf Daten oder Funktionen zugegriffen (siehe Abbildung 2.2). Programmiersprachen wie C und C++ stellen

2. Grundlagen 15 dafür eigene Pointertypen zur Verfügung, die einen einfachen Zugriff auf Speicher und eine einfache Zeigermanipulation (Zeigerarithmetik) ermöglichen. In den Programmiersprachen C und C++ repräsentiert schon ein Funktionsname den Zeiger auf die Funktion, also jene Speicheradresse, an der der Funktionscode beginnt. Erst die Klammerung nach dem Funktionsnamen lässt den Compiler erkennen, dass es sich um einen Aufruf der Funktion an dieser Adresse mit den angegebenen Parametern handelt. Zeigervariablen können sich prinzipiell in jedem Speicherbereich befinden, je nachdem welcher Speicherklasse sie zugeordnet werden. Typische auf dem Stack abgelegte Pointer sind beispielsweise die als Argumente einer Funktion übergebenen Zeiger, lokale Zeigervariablen der Speicherklasse auto innerhalb einer Funktion, Rücksprungadressen, Adressen auf Exception Handler und gesicherte Frame Pointer. In C++ geschriebene Programme speichern für deren Objektinstanzen während der Laufzeit auch Tabellen mit Zeigern auf Funktionen (Vector Tables oder VTables), um virtuelle Methoden abzubilden. Zeiger innerhalb des Prozesspeichers stellen bei allen Attacken das Hauptangriffsziel dar. Können Zeiger oder ganze Zeigertabellen von Außen manipuliert werden, können damit andere Daten, als ursprünglich vorgesehen, gelesen oder gespeichert werden. Bei manipulierten Funktionszeigern werden nicht vorgesehene Funktionen aufgerufen und damit der Programmfluss gezielt verändert. Ebenso ist es denkbar, Zeiger auf Dateien zu manipulieren und damit externe Dateien in einen laufenden Prozess einzuschleusen. Auch Handles 6 auf Dateien sind letztendlich nur Zeiger. 6 Als Handle wird in der Softwareentwicklung ein Identifikator, Nickname oder Alias auf digitale Objekte wie z.b. Dateien, Prozesse, Ressourcen, angeschlossene Geräte, usw. bezeichnet. Das Betriebssystem vergibt ein systemweit eindeutiges Handle beim Erzeugen eines Objekts oder dem Aufbau einer Verbindung mit einem Objekt. Im Programm werden diese Objekte dann nur noch über dieses Handle angesprochen.

3 Potentielle Sicherheitsschwachstellen Unter einer potentiellen Sicherheitsschwachstelle (Security Vulnerability) versteht man eine Systemschwäche, welche einen Einbruch in das System zumindest theoretisch möglich macht. Eine Schwachstelle ist immer die Folge eines Fehlers im Code (Coding 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, welche für einen Großteil der Sicherheitsschwachstellen verantwortlich sind. Allen voran Designfehler und die sogenannten Pufferüberläufe. 3.1 Designfehler A flaw is instantiated in software code but is also present (or absent!) at the design level. schreiben Hoglund und McGraw in [24, S. 39]. Ein Designfehler kann also im Quelltext einer Software zu finden sein. Oft aber sind designbasierte Sicherheitsschwachstellen die Folge von fehlenden Codeteilen oder einer unzureichenden Implementierung notwendiger Sicherungs- und Verteidigungsmechanismen. Die folgende 16

3. Potentielle Schwachstellen 17 Auflistung sicherheitsrelevanter Designfehler lässt schnell erkennen, welche fehlerhaften oder fehlenden Funktionen die typischen Designfehler heutiger Software sein können: Eingabe- und Parameterprüfung: Buffer Overflows, Parametermanipulation, SQL Injection und Cross-Site Scripting (XSS) basieren auf ungeprüften Benutzereingaben oder Funktionsparametern. Nur die strikte Überprüfung aller Eingangsdaten kann Manipulationen verhindern. Authentisierung und Authentifizierung: Das erste zweier Subjekte (z.b. Benutzer, Prozesse, Services, Clients) muss einen Nachweis seiner Identität erbringen, sich authentisieren. Das zweite Subjekt als sein Gegenüber muss die Identität seines Gegenübers überprüfen, die Identität seines Partners authentifizieren. Verschlüsselung: Unverschlüsselte Daten sind für jedermann lesbar. Eine Verschlüsselung der Daten lässt nur jenen die Informationen zukommen, für die sie auch gedacht sind. Sicherung: Ungesicherte Daten sind manipulierbar. Codierungsverfahren können Daten vor Manipulationen sichern oder jede Manipulation aufdecken (z.b. Checksumme, Signatur). Zugriffs- und Ausführungsberechtigungen: 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 interne Systeminformationen. Logdateien, Speicherauszüge und Stacktraces sollten nicht sichtbar sein oder mit einer entsprechenden Zugriffsberechtigung versehen werden. Designfehler lassen sich im Allgemeinen nicht durch die Prüfung einzelner Codezeilen erkennen. Was für eine einfache Applikation an Daten- und Codesicherung noch ausreichend sein mag, ist für eine sicherheitskritische Anwendung bei weitem nicht genug. Erst eine Klassifizierung der Sicherheitsanforderungen, das Erkennen der Bedrohungsszenarien, das Zusammenwirken einzelner Module und die Identifikation der

3. Potentielle Schwachstellen 18 Datenströme lässt mögliche Designfehler und darauf basierende Sicherheitsschwachstellen sichtbar werden. 3.2 Overflow Fehler Die in den letzten Jahrzehnten weitaus am häufigsten zum Einbruch in ein Computersystem genutzten Programmfehler stellen so genannte Pufferüberläufe (Buffer Overflows oder Buffer Overruns) dar. Sie treten immer dann auf, wenn ein Programm bzw. eine Funktion Daten in einem Speicher bestimmter Länge verarbeitet und dabei die Grenzen eines Puffers (Memory Buffer) schreibend über- oder unterschreitet. Dabei werden angrenzende, sich ebenfalls im Speicher befindliche Daten oder Zeiger auf Daten und Funktionen überschrieben, welche funktions- und ablaufrelevant sind. Besonders häufig treten diese Fehler bei C- und C++-Programmen auf, da hier seitens der Sprachkonzepte und Compiler keine Überprüfungen der Speicher- und Arraygrenzen erfolgen. Für die Vermeidung eines Buffer Overflows ist letztendlich immer der Programmierer zuständig. Moderne Programmiersprachen unterstützen die Entwickler, indem sie Array- und Speichergrenzen verwalten und Zugriffe auf illegale Speicherbereiche unterbinden. Die Überprüfung aller Speicherzugriffe und Speichergrenzen hat natürlich Performanceeinbußen zur Folge. Overflows können durch einen Fehler scheinbar zufällig auftreten oder - bei Attacken - absichtlich provoziert werden. In allen Fällen ist ein Programmfehler die Voraussetzung für einen Überlauf. Das Problem kann über längere Zeit unentdeckt bleiben, wenn das Programm keine sichtbaren Veränderungen im weiteren Ablauf zeigt. Ein Angreifer, der derartige Sicherheitsschwachstellen (Security Vulnerabilities) ausnutzen möchte, 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 außerhalb der Spezifikationen liegen. Werden diese Eingangsdaten keiner expliziten Prüfung unterzogen, kann es zu einem Pufferüberlauf kommen und im Speicher befinden 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 ausgewählte Überlauffehler 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 lokale Variablen, Sprungadressen und Funktionsparameter kurzfristig abgelegt werden. In IA-32 Architekturen wächst, wie bei vielen anderen Prozessorarchitekturen auch, der Stack von höheren zu niedrigeren Speicheradressen. Wird nun ein Puffer auf dem Stack angelegt und kommt es bei einem Schreibvorgang zu einem Überschreiten der Puffergrenzen, so werden an den Puffer angrenzende Speicherstellen auf dem Stack überschrieben. 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.Generation 1 bezeichnet [21], weil dieser Overflow wohl zu den am längsten bekannten Schwachstellen gehört. Bei einem klassischen Stack Overflow Exploit ist das Ziel meist die Manipulation der Function Return Address, also der Rücksprungadresse zur aufrufenden Funktion. Kann dieser Zeiger gezielt manipuliert werden, kann eine eigene eingeschleuste Funktion oder eine Bibliotheksfunktion aufgerufen werden. Ein kurzes Beispiel soll die Vorgänge auf dem Stack bei einem Funktionsaufruf und einem Stack Overflow verdeutlichen. In dem im Listing 3.1 gezeigten Beispielprogramm wird aus der Funktion main() die Funktion foo() aufgerufen. Die Applikation ist syntaktisch korrekt und ausführbar, obwohl die Funktion foo einige Fehler bzw. Schwachstellen aufweist, welche für einen Angriff missbraucht werden könnten. Die unsichere 1 Halvar teilt in [21] die verschiedenen Exploit-Techniken erstmals in Generationen ein. Er klassifiziert damit die Arten der Buffer Overflow Schwachstellen anhand der zeitlichen Abfolge, in der diese veröffentlicht wurden. Darauf basierend erweitert Klein in [31] diese Einteilung und weist die Overflows jeweils einer bestimmten Generation zu.