Malicious Code: Würmer, Viren und Co Proseminar im Sommer

Transcription

Malicious Code: Würmer, Viren und Co Proseminar im Sommer
Malicious Code: Würmer, Viren und Co
Proseminar im Sommer Semester 09
Disassembling
Philipp Willmertinger
Technische Universität München
25.5.2009
Zusammenfassung
Disassembling ist eine Technik, mit der der binär (bzw. hexadezimal)
codierte Maschinencode eines ausführbaren Programms zurück in Assemblercode portiert werden kann. Assemblercode ist für Menschen lesbar.
Deshalb kann der genaue Ablauf eines Programms nachvollzogen oder sogar geändert werden.
In dieser Seminararbeit werden das Disassembling und die dafür nötigen
Grundlagen erklärt und Anwendungsgebiete dieser Technik, vor allem in
Bezug auf das Erkennen von bösartigem Code, aufgezeigt.
1
Inhaltsverzeichnis
1 Einleitung
3
2 Ein Programm erzeugen
2.1 Compiling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2 Assembling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
3
4
3 Assemblercode auf einer x86-Systemstruktur
3.1 Maschinenstruktur . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2 Assemblercode . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
4
5
4 Programm ausführen
6
5 Disassembling
5.1 Definition . . . . . . . . . . . . . . . . . .
5.2 Softwarelösungen . . . . . . . . . . . . . .
5.2.1 OllyDbg . . . . . . . . . . . . . . .
5.2.2 IDA Pro . . . . . . . . . . . . . . .
5.3 Codebeispiele . . . . . . . . . . . . . . . .
5.3.1 Einfache IF-Anweisung . . . . . .
5.3.2 Loginabfrage als C++ - Programm
5.3.3 Conficker . . . . . . . . . . . . . .
5.4 Anwendungsgebiete . . . . . . . . . . . . .
5.5 Schutz vor Disassembling . . . . . . . . .
5.6 Rechtliche Grundlagen . . . . . . . . . . .
6 Schluss
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6
6
7
7
11
12
12
12
15
16
17
17
18
2
1
Einleitung
Was macht dieses Programm eigentlich genau? Werden vielleicht private Daten
ausgespäht und gesammelt? Das sind heute für viele Programmierer und Endnutzer entscheidende Fragen. In der Benutzeroberfläche bekommt man zwar
alles zu sehen, was man sehen soll, aber eben meistens nicht viel mehr. Man
spricht hier auch von WYSINWYX (What you see is not what you execute);
in der Maschinensprache können noch andere Prozesse ablaufen, die in der Benutzeroberfläche nicht zu erkennen sind. Eine Lösung für dieses Problem ist
Disassembling. Dadurch können ausführbare Programme aus dem Maschinencode zurück in Assemblercode übersetzt werden.
In dieser Arbeit wird erklärt, wie die Technik des Disassembling funktioniert
und welche Vorteile dadurch entstehen. Dazu wird zunächst erörtert, wie ein
ausführbares Programm entsteht, um ein grundlegendes Verständnis für den Arbeitsbereich eines Disassemblers zu geben. Anschließend werden grundlegende
Elemente von Assembler und Systemarchitekturen erklärt, um den im Beispiel
verwendeten Code verständlich zu machen.
Des Weiteren wird ein Überblick über Softwarelösungen und Anwendungsgebiete des Disassemblings gegeben und die rechtlichen Aspekte dieses Gebiets
aufgezeigt.
2
Ein Programm erzeugen
Programme werden meist in einer sogenannten höheren Programmiersprache
(Hochsprachen) geschrieben. Diese vereinfacht die Programmierung für den
Menschen enorm, da sie eine für ihn äußerst verständliche Sprache ist, da die
Syntax oft an menschliche Denkgewohnheiten angepasst ist. Den Hochsprachen
ist gemeinsam, dass sie nicht direkt auf der Maschine ausgeführt werden können,
sondern zunächst von einem sogenannten Compiler in eine maschinenlesbare
Form übersetzt werden müssen.
2.1
Compiling
Aufgabe von Compilern ist die Übersetzung von Programmen, die in einer
Hochsprache geschrieben sind, in maschinenlesbaren Code. Die Zielsprache ist
von der Zielplattform abhängig. Oft übersetzen sie die Hochsprache zunächst
in Assemblercode.
Es gibt spezielle Compiler, die ein Programm erst kompilieren, kurz bevor
es ausgeführt wird. Diese Just-in-Time-Compiler (werden zum Beispiel für
.NET-Applikationen verwendet) übersetzen das Programm während es läuft,
mit Hilfe eines Zwischencodes in Maschinensprache.
3
2.2
Assembling
Ein Assembler ist im Grunde ein spezieller Compiler: Sein Quellcode ist immer Assemblercode und sein Zielcode Maschinencode. Voraussetzung für das
Assembling ist, dass der Compiler ein Zielprogramm in Assemblercode ausgegeben hat oder das Programm direkt in Assemblerclde geschrieben ist. Durch ein
Assemblerprogramm wird dieser in binären Maschinencode übersetzt, welcher
direkt vom Prozessor ausführbar ist.
Zunächst werden jedoch einige Merkmale von Assemblercode erklärt.
3
Assemblercode auf einer x86-Systemstruktur
Da die Assemblersprache die Maschinensprache direkt repräsentiert, ist sie direkt von der Maschinenarchitektur abhängig. Jede Computerarchitektur hat
folglich ihre eigene Assemblersprache.
In diesem Kapitel werden nun einige Befehle der Assemblersprache und grundlegende Maschinenstrukturen erklärt, die notwendig sind, um das Beispiel im
Kapitel 5 zu verstehen.
Als Maschinenarchitektur wird ein x86-Prozessor (eine von Intel entwickelte Architektur von Mikroprozessoren. [6]) gewählt.
3.1
Maschinenstruktur
Ein aktueller Intel x86-Prozessor hat neun 32-bit Register.
Abbildung 1: Register eines Intel x86-Prozessors
4
Die Register verschiedener Größen (z. B. 8-, 16- oder 32-Bit-Register) können
theoretisch für jeden Zweck verwendet werden. In der Regel werden dort Daten
oder Befehle mit den vorgegebenen Längen gespeichert werden.
Das Register EIP nimmt dabei eine besondere Rolle ein: In ihm ist der aktuelle
Stand des Befehlszählers gespeichert. So kann das laufende Programm jederzeit
auslesen, welcher Befehl als nächstes zu verarbeiten ist.
Die grundlegende Funktionsweise einer Maschinenarchitektur, wie wir sie in aktuellen Intel-Maschinen finden, wurde bereits 1946 von Von Neumann beschrieben :
1. Ein Rechner besteht aus vier Werken:
-Haupt- bzw. Arbeitsspeicher (speichert Programme und Daten)
-Leitwerk (interpretiert Programm)
-Rechenwerk (führt arithmetische Operationen aus)
-Ein/Ausgabewerk incl. Sekundärspeicher (kommuniziert mit
Umwelt, Langfristspeicher)
2. Die Struktur des Rechners ist unabhängig vom bearbeiteten
Problem (Programmsteuerung!)
3. Programm und Daten stehen in demselben Speicher (Hauptspeicher) und können durch die Maschine verändert werden.
4. Der Hauptspeicher ist in Zellen gleicher Größe geteilt, die durch
fortlaufende Nummern (Adressen) bezeichnet werden.
5. Das Programm besteht aus einer Folge von Befehlen (Instruktionen), die i.a. nacheinander ausgeführt werden (sequentiell).
6. Von der Folge kann durch bedingte oder unbedingte Sprungbefehle abgewichen werden, die die Programmfortsetzung aus
einer anderen Zelle bewirken. Bedingte Sprünge sind von gespeicherten Werten abhängig.
7. Die Maschine benutzt Binärcodes, Zahlen werden dual dargestellt.
Bei aktuelleren Maschinenarchitekturen ist der Speicher für den Code schreibgeschützt.
3.2
Assemblercode
Ein Assemblerprogramm besteht aus einzelnen Assembleranweisungen.
Diese bestehen jeweils aus einem Opcode in symbolischer Schreibweise und den
zugehörigen Operanden.
Beispiele für Befehle:
5
MOV AL, 62h
XOR EAX, EAX
ADD EAX, [EBP]
INC EBX
MOV ist der Opcode für den Move-Befehl, der hier den
hexadezimalen Wert 62 (98 dezimal) ins Register ’AL’ lädt.
Verknüpft die Operanden durch ein logisches exklusives
Oder (Antivalenz). (Das Register EAX ist danach immer
0)
Addiert zum Register EAX den Wert einer über das Register EBP bestimmten Speicherzelle.
Addiert 1 zum Zieloperanden EBX.
Der Assembler übersetzt nun z.B. den Befehl XOR EAX, EAX in den hexadezimalen Maschinencode 33C0.
Wenn ein Programm in Abhängigkeit von Bedingungen verschiedene Wege
einschlagen kann, werden sogenannte Jumps verwendet. Diese verändern, je
nachdem ob eine Bedingung gegeben ist oder nicht, den EIP.
Für die Jumps spielen einige Flags eine große Rolle. Der wichtigste ist wohl der
Z-Flag (Zero-Flag). Er zeigt an, ob die aktuelle Operation das Ergebnis 0 hat.
In diesem Fall hat er den Wert 1, bei jedem anderen Ergebnis 0.
Der Jump JNZ (’JUMP NOT ZERO’) wird zum Beispiel nur ausgeführt, wenn
der Z-Flag nicht gesetzt ist, also momentan den Wert 0 hat. (Siehe Listing 4)
Ein weiterer interessanter Jump ist der JMP-Jump, der den EIP in jedem Fall
verändert. Der Befehl NOP (’No Operation’) hingegen macht gar nichts und
das Programm läuft auf dem normalen Weg weiter.
4
Programm ausführen
Der fertige Maschinencode muss in der für ihn bestimmten Systemumgebung
ausgeführt werden. Dazu benötigt er verschiedene Bibliotheken. In diesen Bibliotheken finden sich oft verwendete, vordefinierte Funktionen, die in das Programm eingebunden werden können. Der Linker verbindet diese und erzeugt
eine ausführbare Datei.
Wird eine solche Datei gestartet, reserviert der Lader die benötigten Speicherbereiche, lädt den Maschinencode in den Speicher und bewirkt die Ausführung
des Programms.
5
5.1
Disassembling
Definition
Die Technik, mit der ausführbare Programme, die in Maschinencode verfasst
sind, wieder zurück in Assemblersprache übersetzt werden, nennt man Disassembling. Die Software, die diesen Vorgang ausführt, muss wissen, auf welcher Maschinenarchitektur sie arbeitet. Im Rahmen dieser Arbeit wird ein x86Prozessor verwendet. [7]
6
Ein großer Anteil von Malware ist direkt in Assemblersprache geschrieben.
Durch Disassembling lässt sich Schadsoftware identifizieren und klassifizieren.
5.2
Softwarelösungen
Als dafür mögliche Software wird eine Vielfalt an Programmen angeboten. Meistens ist der Disassembler in Entwicklerumgebungen integriert, damit der Hochsprachencode zum Testen ausgeführt werden kann und im Falle von Fehlern der
Maschinencode ausgelesen werden kann. (Kapitel 5.4). Im Folgenden sollen zwei
Disassembler betrachtet werden, die zum Disassembling und zum anschließenden Reassembling verwendet werden können.
5.2.1
OllyDbg
OllyDbg [12] (Abbildung 2) ist ein Disassembler für Microsoft Windows. Es
wird als Shareware angeboten, hat aber keine Beschränkungen.
Die Benutzeroberfläche ist in sechs Bereiche aufgeteilt. In der Steuerungsleiste,
die sich im oberen Bereich des Disassemblers befindet, finden sich Schaltflächen,
die es dem Anwender ermöglichen, ein ausführbares Programm einzulesen,
schrittweise durch dieses zu navigieren und den aktuellen Status des Programms
zu erkennen.
Die restlichen Bereiche, welche alle den aktuellen Status des laufenden, zu
analysierenden Programms anzeigen, und deren Bedeutung werden erklärt:
7
8
Abbildung 2: Benutzeroberfläche von OllyDbg
Abbildung 3: OllyDbg - CPU-Fenster
Das erste Fenster, das CPU-Fenster, ist in in vier Spalten unterteilt. Die erste
Spalte zeigt die virtuellen Adressen des Speichers in Hexadezimalschreibweise,
in die Windows das Programm geladen hat. Die zweite zeigt die darin enthaltenen Opcodes (auch hexadezimal) und die dritte den dafür von OllyDbg
ermittelten Assemblercode. In der letzten Spalte wird ein Kommentar zur
Funktion, die durch diese Adresse angesteuert wird, und deren Parameter in
lesbarer Form angezeigt.
Abbildung 4: OllyDbg - nächste Operation
Im nächsten Fenster sehen wir weitere Informationen zu der Operation, die im
nächsten Schritt ausgeführt wird.
9
Abbildung 5: OllyDbg - DUMP-Fenster
Im dritten sehen wir das sogenannte DUMP-Fenster, in dem der Inhalt der
betroffenen Speicheradressen im Maschinencode angezeigt wird.
Abbildung 6: OllyDbg - System-Stack
Im vierten Fenster sehen wir die aktuelle Belegung des System-Stacks.
10
Abbildung 7: OllyDbg - Register und Flags
Im letzten Fenster sehen wir die aktuelle Belegung der Register und Flags. In
diesem Beispiel ist das Zero(Z)-Flag gesetzt, da es den Wert 1 hat.
5.2.2
IDA Pro
Die deutlich professionellere und kommerziellere Lösung für einen Disassembler heißt IDA Pro [5]. Die Anwendung wird frei nur in einer veralteten Version
angeboten, für die aktuelle gibt es nur eine zeit- und funktionsbeschränkte Demoversion.
Aufgrund des gleichen Anwendungsgebietes weist dieses Programm starke Parallelen zu OllyDbg auf. Allerdings sticht sofort der Kontrollflussgraph ins Auge, der den Verlauf des Programms in Form eines Ablaufdiagramms graphisch
darstellt. So lässt sich schnell ein Überblick über den gesamten Ablauf des Programms erkennen. Des Weiteren unterstützt IDA Pro viel mehr Maschinenstrukturen: Währen Olly nur auf einem x86-System mit Microsoft Windows (32-bit)
arbeitet, unterstützt IDA Pro in der Vollversion mehr als 50 Prozessorfamilien,
darunter auch PDAs, Mobiltelefone und Spielkonsolen [5].
11
Abbildung 8: Beispielprogramm im Programmplotter von IDA Pro
12
5.3
5.3.1
Codebeispiele
Einfache IF-Anweisung
Es wird gezeigt, wie eine einfache If-Anweisung (Programmiersprache C) in
Assemblercode umgesetzt wird.
1
2
3
int main ( ) {
int x , y , z ;
z = ( x>y ) ? 2 : 3 ;
4
5
}
Listing 1: Beispielprogramm ’if-Anweisung’ in C
1
2
3
4
5
6
7
8
8048355:
8048358:
804835 b :
804835 d :
8048364:
8048366:
804836 d :
8048370:
8b
3b
7e
c7
eb
c7
8b
89
45
45
09
45
07
45
45
45
f8
f4
e8 02 00 00 00
e8 03 00 00 00
e8
f0
mov
cmp
jle
movl
jmp
movl
mov
mov
−0x8(%ebp ) ,% eax
−0xc(%ebp ) ,% eax
8048366 <main+0x22>
$0x2 ,−0 x18(%ebp )
804836 d <main+0x29>
$0x3 ,−0 x18(%ebp )
−0x18(%ebp ) ,% eax
%eax ,−0 x10(%ebp )
Listing 2: Beispielprogramm ’if-Anweisung’ in Assemblercode
Im Listing 1 wird der C-Code der If-Anweisung, im Listing 2 wird der disassemblierte Code des ausführbaren If-Programms gezeigt.
In diesem Assemblercode ist in Zeile 1 die Zuweisung des Werts von x (= 0x8(%ebp)) auf das Register EAX zu sehen. In Zeile 2 wird der Wert von y
(= -0xc(%ebp)) mit EAX verglichen. Liefert der Vergleich die Bedingung kleiner oder gleich, wird das Programm an der Adresse 8048366 fortgesetzt und
dort (Zeile 6) wird der Wert 3 in die temporäre Speicherzelle mit Adresse 0x18(%ebp) geschrieben. Im anderen Vergleichsfall wird das Programm in Zeile
4 fortgesetzt, wo der Wert 2 in die gleiche temporäre Speicherzelle geschrieben
wird, und in Zeile 5 wird der Jump über die andere Zuweisung (Zeile 6) ausgeführt, damit die Speicherzelle nicht erneut belegt wird. In Zeile 7 wird der
Wert der temporären Speicherzelle ins Register EAX geladen und in Zeile 8
wird der Wert von EAX in die für z (= -0x10(%ebp)) bestimmte Speicherzelle
geschoben.
5.3.2
Loginabfrage als C++ - Programm
Um die Möglichkeiten der beiden Disassembler (siehe Kapitel 5.2.2 und 5.2.1)
besser zu verstehen, wird nun eine kleine C++-Anwendung (Win-32) mit Microsoft Visual Studio 2008 erstellt, die u.a. auch eine Schleife beinhaltet.
1
2
#include ” s t d a f x . h”
#include ” s t r i n g . h”
13
3
4
5
int
{
tmain ( int argc , char argv [ ] )
char pwd [ 1 0 0 ] ;
p r i n t f ( ” Passwort e i n g e b e n : ” ) ;
f g e t s (pwd , 1 0 0 , s t d i n ) ;
pwd [ s t r l e n (pwd) −1] = ’ \0 ’ ;
6
7
8
9
10
i f ( strcmp (pwd , ”admin” ) == 0 )
p r i n t f ( ” Login e r f o l g r e i c h \n” ) ;
else
p r i n t f ( ” Login f e h l g e s c h l a g e n \n” ) ;
return 0 ;
11
12
13
14
15
16
}
Listing 3: Beispielprogramm ’LoginWindow’ in C++
Dieses Beispiel beinhaltet nur eine Passwortabfrage und -überprüfung und
könnte zum Beispiel am Anfang eines zugriffsbeschränkten Programms stehen,
welches es nur nach Eingabe des richtigen Passworts öffnet.
Beim Öffnen des Programms wird die Ausgabe ’Passwort eingeben:’ angezeigt
und der Befehl fgets in Zeile 8 liest die Eingabe in die Variable pwd ein.
Anschließend wird in Zeile 9 das letzte Zeichen - das bei der Eingabe ein Return
ist - durch einen Nullbyte ersetzt. Danach wird das Passwort überprüft.
Das fertige Programm lässt sich nun mit Disassemblern betrachten: In IDA
pro lässt sich der Programmverlauf im graphischem Ablaufdiagramm (siehe
Abbildung 8) erkennen, das in Assembler letztendlich der Jump JNZ für die
Überprüfung des Passworts verantwortlich ist. Wenn wir dieses Programm in
OllyDbg öffnen, können wir es Schritt für Schritt durchlaufen lassen, so kann
hier der Programmverlauf in Assemblersprache nachvollzogen werden.
Zwischen den virtuellen Adressen 00091040 und 00091045 (erste und zweite
rote Markierung, Abbildung 3) ist die strlen-Funktion in Assemblercode zu
sehen, die die Länge des eingegebenen Strings ermittelt.
Am Beispiel dieser Funktion wird hier nun die Aufgabe des JNZ-Jumps in der
Schleife dieser Funktion erklärt.
1
2
3
4
5
00DF1040
00DF1042
00DF1043
00DF1045
00DF1047
MOV CL,BYTE PTR DS : [ EAX]
INC EAX
TEST CL, CL
JNZ SHORT LoginWin . 0 0 DF1040
SUB EAX, EDX
Listing 4: strlen-Funktion in Assemblercode
In Zeile 1 wird das Zeichen an der Position EAX (beim ersten Durchlauf 0 und
damit das erste Zeichen) aus der Adresse DS in CL verschoben. Danach wird
14
der Wert von EAX um 1 erhöht. In Zeile 3 wird dann getestet, ob CL einen Inhalt hat. Wenn dies der Fall ist, bleibt das Zero-Flag 0, da das Ergebnis dieser
Operation true bzw. 1 war.
Der JNZ-Jump lässt das Programm zurück zu Zeile 00DF1040 (siehe Zeile 4)
springen. Wenn aber TEST CL,CL false ergibt, also kein Zeichen mehr übrig ist,
läuft das Programm weiter (Zeile 5). Von EAX wird nun der Wert des Registers
EDX abgezogen, der vor der Funktion mit EAX gleichgesetzt wurde.
Zwischen den nächsten beiden roten Markierungen (Adressen 00091056 und
00091070) ist die Entsprechung der strcmp-Funktion.
Hier wird das Passwort überprüft, und wenn es richtig ist, wird das ZeroFlag gesetzt. Wenn das Zero-Flag nicht gesetzt ist, wird durch den JNZ-Jump
(009107D) zur Ausgabe des Strings ’Login fehlgeschlagen’ gesprungen. Ansonsten wird der String ’Login erfolgreich’ ausgegeben. Außerdem lässt sich unschwer erkennen, mit welchem String das Passwort verglichen wird, so dass wir
das Programm erfolgreich ausführen können: ’admin’ (siehe Abbildung 3)
Da wir in OllyDbg auch Schreibzugriff haben, besteht auch die Möglichkeit,
den Befehl JNZ durch den Befehl NOP zu ersetzen, was zur Folge hat, dass
unabhängig von der Eingabe immer der String ’Login erfolgreich’ ausgegeben
wird.
So kann ein nicht berechtigter Anwender Zugang zum Programm erhalten. Dieses Beispiels spiegelt nun einige grundliegende Anwendungsmöglichkeiten von
Disassembling wieder, gibt aber dennoch nur einen kleinen Einblick in die zahlreichen Anwendungsgebiete für Disassembling.
5.3.3
Conficker
Um Schwachstellen in einem Systemprozess zu finden, können diese disassembliert werden. So kann ein Angreifer herausfinden, wie die Schwachstelle genau
aussieht, und in auf welche Weise schadhafter Code in diese Prozesse injiziert
werden kann.
Der Conficker-Wurm, der seit November 2008 ca. 3 Millionen Rechner befallen
hat, produziert in der Systemfunktion MS08-067 des Microsoft-Server-Dienstes
einen Pufferüberlauf (buffer overflow ). Dieses ist für das Canonicalizing
eines aufgerufenen Serverstrings zuständig. Das bedeutet, dass aufgerufene
Adressen in eine vorschriftsmäßige Form gebracht werden. So wird zum
Beispiel die Adresse \\server1\freigabe\dir1\..\dir2\ umgewandelt in
\\server1\freigabe\dir2\.
Durch einen Fehler in dieser Routine kann, wenn die Anweisung \..\ über das
root-Verzeichnis hinausgeht (z.B. \\server1\freigabe\..\..\) in Speicherbereiche, die von svchost.exe als Dienst ausgeführt werden, geschrieben werden.
Die Grundidee des Conficker-Wurms ist es, Code, der von einem Systemdienst
mit allen Privilegien ausgeführt werden kann, auf die Maschine zu bringen.
Also ruft Conficker nun auf der zu infizierenden Maschine eine Adresse wie
\\server1\freigabe\.. \..\urlmon(http://114.44.XX.XX:2363/wkpqz)
auf. Der Befehl urlmon(http://114.44.XX.XX:2363/wkpqz) wird in Speicherbereiche geschrieben, die dann von dem Serverprogramm svchost.exe im
15
Dienstmodus ausgeführt werden. Dem Serverprogramm svchost.exe ist standardmäßig erlaubt, eine Verbindung ins Internet herzustellen. Auch Firewalls
sind meist so konfiguriert, dass Dienste, die durch svchost.exe ausgeführt
werden, nicht geblockt werden. Dadurch wird dann die angegebene URL in
eine Datei auf der Maschine geladen. Von dort aus wird sie ausgeführt und
installiert Conficker. Dieser kann sich nun von der infizierten Maschine aus
weiter verbreiten.
5.4
Anwendungsgebiete
Die Technik des Disassembling wird in verschiedenen Bereichen angewendet, die
in diesem Kapitel voneinander abgegrenzt werden.
Anti-Viren-Programme
Ein Anwendungsgebiet ist die Virenerkennung in ausführbaren Dateien. Damit
man Schadmuster im Maschinencode entdecken kann, kann der Hersteller
schädlichen Maschinencode durch Disassemblierung identifizieren. Gefährliche
Muster, wie zum Beispiel Endlosschleifen, werden (in Maschinencode) in
sogenannten Viral definition files gespeichert und als Update für die Endnutzer
zur Verfügung gestellt. Wird ein Programm dann verdächtigt, bösartige Ziele
zu verfolgen, da es mit Mustern aus dem Viral definition file übereinstimmt,
kann es sofort gestoppt werden und der Benutzer über eine mögliche Infektion
seines Systems gewarnt werden.
Überprüfung von Software
Ein weiteres Gebiet ist die Überprüfung von Software, deren Code nicht einsehbar ist (Closed Code). Es stellt sich die Frage, ob Programme ohne Bedenken
verwendet werden können. Dieses Gebiet lässt sich am Beispiel ’The Silver Needle in the Skype’ erklären [3]: Im Auftrag von EADS wurde überprüft, ob Skype,
das ein seltsames Trafficverhalten (auch im unbenutzten Modus) aufweist, an
Firmenrechnern verwendet werden kann oder ob dadurch Daten ausgespäht werden können und Angreifer Zugang zum Firmennetzwerk haben.
So konnte herausgefunden werden, dass Skype zwar eine gute Verschlüsselung
benutzt, aber Mitarbeiter von Skype jederzeit Konversationen mithören könnten. Skype lässt sich auf keine generelle Sicherheitspolice ein, es werden jedoch
nur vertrauenswürdige Server verwendet. Durch das Aufbauen eines eigenen
Netzwerks könnten Nutzer und deren Traffic ausgespäht werden. Aus diesem
Grund muss auch jeder Kontakt vertrauenswürdig sein.
Jedoch konnte in dieser Studie nicht herausgefunden werden, ob man sich vor
Angriffen schützen oder herauszufinden kann, ob eine Backdoor existiert.
16
Bösartiger Code (Malware)
Wie in Listing 5.3.2 gezeigt und am Beispiel des Conficker-Wurms wurde, ist Disassembling auch eine Technik, die Virenschreiber verwenden. Ein Programmierer kann versuchen, entsprechende Passwörter oder Codes ausfindig zu machen,
die Passwortabfrage in einem Programm zu umgehen, nicht aktivierte, kostenpflichtige Teile eines Programms zu aktivieren oder gewisse Teile des Programms
zu ändern. Hierbei spricht man auch vom Cracken oder Patchen von Software.
Natürlich kann auch Schadcode in eine ansonsten vertrauenswürdige Datei eingeschleust werden (Beispiel: Conficker, Kapitel 5.3.3).
Konkurrenten haben auch die Möglichkeit, benötigte Funktionen aus Programmen zu kopieren und für eigene Software wiederzuverwenden.
Entwicklung von Software
Auch für Entwickler von Software ist das Disassembling relevant: Um zu
testen ob geschriebene Software so funktioniert, wie man erwartet, beinhalten
Entwicklungstools Disassembler (Reverse Engineering). So können Fehler
im Code schnell gefunden werden. Des Weiteren besteht für Entwickler die
Möglichkeit, Schnittstellen von Sekundärsoftware zu verstehen. So kann das
eigene Programm kompatibel zu anderen Programmen gemacht werden.
5.5
Schutz vor Disassembling
Im Bereich der Softwareentwicklung verbreiten sich inzwischen aber schon Softwarelösungen, die in Programme integriert werden, um deren Assemblercode
möglichst kompliziert zu gestalten. Hierbei werden zum Beispiel eigene Lader
in die Datei integriert, die sich erst nach Programmstart selbst entpacken und
anders agieren als der Standard-Windows-Lader. Sie führen während der Laufzeit des Programms Hash-Überprüfungen durch, die das laufende Programm
verifizieren, verschlüsseln Daten und gestalten den Programmverlauf deutlich
komplizierter. Dadurch wird ein Disassembling zwar stark erschwert, aber nicht
unmöglich.
Als konkretes Beispiel für ein solches Schutzsystem sei hier SoftwarePassportArmadillo der Firma Silicon Realms [10] genannt, das unter anderem den kompletten Maschinencode des zu schützenden Programms verschlüsselt abspeichert. Zur Laufzeit wird der Code im Systemspeicher entschlüsselt und erst
dann ausgeführt.
5.6
Rechtliche Grundlagen
Häufig wird bei Software in der EULA, dem End-User-Lizenz-Vertrag [2], das Disassemblieren der Software vom Hersteller untersagt. Dies ist jedoch in Deutschland in den meisten Fällen nicht rechtlich bindend, da §69 des Gesetzes über
Urheberrecht und verwandte Schutzrechte das Disassemblieren zu bestimmten
Zwecken ausdrücklich erlaubt. Dort heißt es:
17
(1) Die Zustimmung des Rechtsinhabers ist nicht erforderlich, wenn
die Vervielfältigung des Codes oder die Übersetzung der Codeform
im Sinne des § 69c Nr. 1 und 2 unerlässlich ist, um die erforderlichen Informationen zur Herstellung der Interoperabilität eines unabhängig geschaffenen Computerprogramms mit anderen Programmen zu erhalten, sofern folgende Bedingungen erfüllt sind:
1. Die Handlungen werden von dem Lizenznehmer oder von einer
anderen zur Verwendung eines Vervielfältigungsstücks des Programms berechtigten Person oder in deren Namen von einer
hierzu ermächtigten Person vorgenommen;
2. die für die Herstellung der Interoperabilität notwendigen Informationen sind für die in Nummer 1 genannten Personen noch
nicht ohne weiteres zugänglich gemacht;
3. die Handlungen beschränken sich auf die Teile des ursprünglichen Programms, die zur Herstellung der Interoperabilität notwendig sind.
Das Disassemblieren von Fremdsoftware ist aus gesetzlicher Sicht nur für die
Herstellung von Kompatibilität der eigenen Software gedacht. Dies wird in den
Absätzen (2) und (3) dieses Paragraphen genauer geregelt [2].
6
Schluss
Die Technik Disassembling eignet sich zum Erkennen und Debuggen von
• Malware,
• Sicherheitslücken in Benutzer- und Systemprogrammen und
• fehlerhaftem oder unverständlichem Code in entwickelter Software.
Oft spielt Disassembling auch eine Rolle beim Nachvollziehen von Programmen
mit nicht öffentlich zugänglichem Code.
Neben dem Cracken von Software sind das Stehlen von Code aus Fremdsoftware und das Einschleusen von schadhaftem Code in Software gängige
Vorgehensweisen.
Disassembling ist eine spannende und nützliche Technik, die aus vielen Bereichen der Informatik nicht weg zu denken ist.
18
Literatur
[1] Günter Born. Assembler enträtselt. Systhema Verlag GmbH, 1991.
[2] Bundesministerium der Justiz. Gesetz über Urheberrecht und verwandte
Schutzrechte. http://bundesrecht.juris.de/urhg/ 69e.html.
[3] Philippe Biondi & Fabrice Desclaux.
Silver Needle in the Skype.
EADS, 2006. http://www.blackhat.com/presentations/bh-europe-06/bheu-06-biondi/bh-eu-06-biondi-up.pdf.
[4] Otmar Feger. MC-Tools 2 - Die 8051-Mikrocontroller-Familie - Einführung
in die Software mit Assembler und Disassembler. Feger & Co. Hardware +
Software Verlag OHG, 1990.
[5] Hex-Rays. IDA Pro. http://www.hex-rays.com/idapro/.
[6] Intel.
Intel
Architecture
Software
Developer’s
nual
Volume
2:
Instruction
Set
Reference,
http://www.intel.com/design/pentium/manuals/24319101.PDF.
Ma1997.
[7] Wolfgang Link. Assembler Programmierung. Franzis, 2004.
[8] Trutz Eyke Podschun. Das Assembler-Buch. Addison-Wesley Verlag, 2002.
[9] Klaus Schmaranz. Software-Entwicklung in C++. Springer-Verlag, 2003.
[10] Silicon
Realms.
http://www.siliconrealms.com/.
SoftwarePassport/Armadillo.
[11] Peter Szor. Virus Research and Defense. Linda McCarthy (Symantec
orporation), 2005.
[12] Oleh Yuschuk. OllyDbg, 2000-2009. http://ollydbg.de/quickst.htm.
19