Entwicklung einer Bibliothek zur Compiler Erkennung

Transcription

Entwicklung einer Bibliothek zur Compiler Erkennung
Entwicklung einer Bibliothek
zur Compiler Erkennung
7. Januar 2009
Hochschule Ravensburg-Weingarten
Fakultät Elektrotechnik und Informatik
Sebastian Wiedenroth
Matrikelnummer 16847
[email protected]
Betreuer:
Prof. Dr. rer. nat. Martin Zeller
Dipl. -Ing. Stefan Kurtzhals
Hiermit erkläre ich, Sebastian Wiedenroth, geboren am 07.10.1986 in Lindau, dass ich die vorliegende
Bachelorarbeit mit dem Titel "Entwicklung einer Bibliothek zur Compiler Erkennung" selbständig
verfasst habe und keine anderen als die angegebenen Quellen und Hilfsmittel benutzt wurden.
Tettnang, den 7. Januar 2009
Abstract
Um Malware wie Viren, Würmer, Trojaner sowie Ad-/Spyware zu erkennen, verwenden Anti-Viren
Produkte heute unter anderem heuristische Verfahren. Diese sind in der Lage, proaktiv unbekannte
oder veränderte Malware zu erkennen. Damit sie zuverlässig funktionieren, benötigen sie möglichst
viele aussagekräftige Informationen über ein Programm.
Teilweise konstruieren Malware-Autoren die Binaries von Grund auf, um die Erkennung durch
Antiviren-Programme zu erschweren. Normale Compiler hinterlassen jedoch unterschiedliche Spuren
und Merkmale in ausführbaren Dateien, welche Rückschlüsse auf den verwendeten Compiler
ermöglichen. Aus diesem Grund versuchen Malware Autoren ihre Binaries möglichst ähnlich zu
gestalten, wie die Erzeugnisse der weit verbreiteten Compiler. Diese Compiler-Merkmale auf
Konsistenz zu überprüfen und einzelne Eigenschaften in die Heuristik einzubeziehen, kann bei der
Erkennung von Malware hilfreich sein.
In dieser Arbeit werden eine kleine Auswahl, weit verbreiteter bis obskurer, Compiler verglichen,
mit der Absicht, möglichst allgemeingültige Erkennungsstrategien zu finden. Einzelne Merkmale,
welche zur Validierung der Entscheidung beitragen, werden ebenfalls erläutert. Die gesammelten
Erfahrungen werden in einer C Library umgesetzt und in die Avira Scan Engine integriert.
1
Entwicklung einer Bibliothek zur Compiler Erkennung
Vorwort
Projektträger
Die Avira ist mit rund 60 Millionen Kunden und 250 Mitarbeitern ein weltweit führender Anbieter
selbst entwickelter Sicherheitslösungen für den professionellen und privaten Einsatz. Das
Unternehmen gehört mit mehr als zwanzigjähriger Erfahrung zu den Pionieren in diesem Bereich.
Als führender deutscher Sicherheitsspezialist verfügt Avira über fundierte Erfahrung im
Entwickeln und Supporten ihrer Lösungen. Neben Programmen direkt für den Einzelplatzbetrieb
bietet sie hauptsächlich professionelle Lösungen für systemübergreifenden Schutz von Netzwerken
auf verschiedenen Ebenen an. Hierzu zählen u. a. Produkte für Workstations, File-, Mail- und WebServer. Auch Gateway-Rechner können wie Arbeitsplatzrechner über eine zentrale
Verwaltungskonsole betriebssystemübergreifend verwaltet werden. Zu den Verwaltungsprodukten der
einzelnen Lösungen kommen noch Sicherheitsprogramme für PDAs, Smartphones und Embedded
Devices hinzu. Ein signifikanter Sicherheitsbeitrag ist Avira AntiVir Personal, das millionenfach bei
Privatanwendern im Einsatz ist.
Avira ist in Tettnang am Bodensee einer der größten regionalen Arbeitgeber. Sie unterhält
mehrere Unternehmensstandorte in Deutschland und pflegt Partnerschaften in Europa, Asien und
Amerika. Mehrere Dutzend Virus-Researcher in verteilten Virenlabors kümmern sich rund um die
Uhr um die lokalen und globalen Bedrohungen der Virenfront. Bestätigt wird diese Arbeit etwa durch
mehrfache Testauszeichnungen mit dem VB 100% des Virus Bulletin oder der wiederholten TÜVZertifizierung.
Als Technologieführer im UNIX-Bereich entwickelt Avira federführend Standards. Zu weiteren
Avira-Innovationen gehören Virenschutzlösungen unter Symbian oder der weltweit erste SAPzertifizierte Virenschutz.
Neben intelligenten Technologien bietet Avira unter dem Leitsatz „Mehr als Sicherheit“
lösungsorientierte Beratung und individuellen Support durch eigene Experten. Ein signifikanter
Sicherheitsbeitrag und ein weiterer zusätzlicher Schutzwall für Unternehmensnetzwerke ist der
Schutz von Anwendern durch Avira AntiVir Personal.
Auszeichnungen und Zertifizierungen der Avira-Produkte sowie Partnerschaften mit
Unternehmen wie IBM, Sun, Novell, AVM oder Clearswift unterstreichen die Leistungsfähigkeit und
Kernkompetenz des Unternehmens. Sicherheitslösungen von Avira sind global gefragt und über
geschulte, autorisierte Partner direkt vor Ort verfügbar.
2
Weltweit verteilt setzen viele Integrationspartner die Avira AntiVir-Scanengine „Savapi“ direkt in
OEM-Produkten ein. Savapi und die darunterliegende Suchtechnologie zeichnen sich durch
plattformübergreifende und prozessorunabhängige Programmierung aus. Hierzu zählen der Einsatz
sowohl unter Windows und Linux als auch auf Intel und Non-Intel Prozessoren. Dank Einsatz
ressourcenschonender Programmiertechniken kommen die Avira Produkte mit einem geringen
Hauptspeicherbedarf aus.
Darüber hinaus passt sich Avira immer wieder den aktuellen Sicherheitsbedürfnissen ihrer
Anwender an. So ist AntiVir weltweit eines der ersten Produkte mit Dialer-Erkennung überhaupt.
Dieser Dialer-Erkennung folgte sehr bald das Erkennen und Entfernen von Ad- und Spyware.
Aktuelle Entwicklungen in diesem Bereich sind das Erkennen bereits aktiver Rootkits. Die
Einführung von Firewall und Behavior-Blockern sind selbstverständlich. Die InHouse-Entwicklung
gestattet auch das Entwickeln diverser neuer Spezial-Suchtechnologien, beispielsweise für PDAs,
Handhelds und Embedded Devices. Doch auch auf dem Gebiet des proaktiven Systemschutzes zur
Härtung von Windows Systemen ist Avira mit dem Einbau RBCD-Technologien tätig.
Zu den nationalen und internationalen Kunden zählen namhafte börsennotierte Unternehmen
aber auch Bildungseinrichtungen und öffentliche Auftraggeber. Neben dem Schutz der virtuellen
Umgebung kümmert sich Avira durch Fördern der Auerbach Stiftung um mehr Schutz und
Sicherheit in der realen Welt. Die Auerbach Stiftung des Firmengründers unterstützt gemeinnützige
und soziale Vorhaben sowie Kunst, Kultur und Wissenschaft.
Ziel der Arbeit
Innerhalb dieser Arbeit werden die Erzeugnisse unterschiedlicher Compiler verglichen und
analysiert. Es sollen Möglichkeiten gefunden werden, automatisiert den verwendeten Compiler zu
unterscheiden. Ebenfalls sollen Programme erkannt werden, die vorgeben mit einem bekannten
Compiler erzeugt worden zu sein, tatsächlich jedoch nicht mit diesem Compiler generiert wurden.
Nachträglich durch Malware-Autoren veränderte ausführbare Programme sollen ebenfalls erkannt
werden.
Die Ergebnisse sollen in einer C Library umgesetzt werden, die es der Avira Scan Engine
ermöglicht diese Funktionen zu nutzen. Speziell für das Heuristik-Modul der Engine sind diese
Informationen von Interesse.
Da Microsoft Windows momentan die am stärksten von Malware Autoren angegriffene
Plattform ist, beschränkt sich diese Arbeit auf das Erkennen von Windows Anwendungen (PEDateien). Eine spätere Ausweitung auf andere Plattformen, wie etwa ELF für Linux und UNIX, ist
denkbar.
3
Entwicklung einer Bibliothek zur Compiler Erkennung
Struktur der Arbeit
Grundlagen
Im Grundlagenteil wird der Compile- und Linkprozess kurz zusammengefasst. Der Aufbau einer
Portable Executable (PE), dem Dateiformat für ausführbare Programme unter Windows, wird
ebenfalls beschrieben.
Analyse
Im zweiten Teil werden verschiedene Methoden und Ansätze zur Unterscheidung von Compilern
vorgestellt. Bereits existierende Lösungen werden analysiert und bewertet. Spezielle Merkmale
einzelner Compiler können gezielt verwendet werden, um eine Entscheidung zu treffen. Davon
werden in dieser Arbeit einige näher vorgestellt.
Realisierung
Die Bibliothek zur Compiler Erkennung, welche im Rahmen dieser Arbeit erstellt wurde, wird im
dritten Teil näher beschrieben.
Danksagung
An dieser Stelle möchte ich mich bei einigen Personen bedanken, die mich während dieser Arbeit
unterstützt haben. Ich danke Herrn Stefan Kurtzhals, meinem fachlichem Betreuer bei der Avira
GmbH, für sein Engagement. Ebenso danke ich Prof. Dr. rer. nat. Martin Zeller, der diese Arbeit auf
Seiten der Hochschule Ravensburg-Weingarten betreut hat.
Besonderer Dank gilt dem gesamten Engine Team der Avira für die vielen guten Ratschläge und
interessanten Unterhaltungen.
Bedanken möchte ich mich ebenfalls bei Roderick Baier, Thomas Merkel und Philipp Schmid für
die freundliche Hilfe beim Korrekturlesen.
Schließlich bedanke ich mich noch bei meinen Eltern, die mir das Studium ermöglichten und
mich stets unterstützen.
4
Inhaltsverzeichnis
Inhaltsverzeichnis
Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Compilieren und Linken . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Der Windows Loader . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Das PE-Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Analyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Borland C++ Compiler . . . . . . . . . . . . . . . . . . . . . . . . 12
Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Free Pascal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
GCC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Intel Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
LCC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Visual Studio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Vorbereitungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Existierende Techniken . . . . . . . . . . . . . . . . . . . . . . . . . . 14
String Konstanten . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Entry Point Signatur . . . . . . . . . . . . . . . . . . . . . . . . . 14
FLIRT mit IDA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Weitere Möglichkeiten . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Standardwerte für Header . . . . . . . . . . . . . . . . . . . . . . 18
Imports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Section Namen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Paddings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Decompilieren / Assembly Pattern . . . . . . . . . . . . . . . . . . 23
Visual Basic Header
.NET Metadaten
. . . . . . . . . . . . . . . . . . . . . . . . . 24
. . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Microsoft RICH Signatur . . . . . . . . . . . . . . . . . . . . . . . 25
Realisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Prototypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Aufbau
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Erkennung des Compiler . . . . . . . . . . . . . . . . . . . . . . . . . 28
5
Entwicklung einer Bibliothek zur Compiler Erkennung
Optimierungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Anomalie-Prüfung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Literaturverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Anhang A: Strukturdefinitionen . . . . . . . . . . . . . . . . . . . . . . . 33
Datei Header . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Import Tabelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Anhang B: Header Werte im Vergleich . . . . . . . . . . . . . . . . . . . . 36
6
Inhaltsverzeichnis
Abbildungsverzeichnis
1.
2.
3.
4.
5.
6.
7.
8.
9.
Alignment im Vergleich zwischen Datei und Speicher
Aufbau einer PE-Datei . . . . . . . . . . . . . . . . . .
PEiD erkennt Programm als "Borland C++ 1999" . . .
IDA Pro . . . . . . . . . . . . . . . . . . . . . . . . . . .
IDA Pro Navigation Bar . . . . . . . . . . . . . . . . .
Aufbau einer .NET Anwendung . . . . . . . . . . . . .
Aufbau der RICH-Signatur . . . . . . . . . . . . . . . .
Entry-Point Signaturen als Baum . . . . . . . . . . . .
Konvertierung der Signaturen in C-Code . . . . . . . .
7
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 9
. 10
. 15
. 16
. 16
. 24
. 26
. 29
. 29
Entwicklung einer Bibliothek zur Compiler Erkennung
Listingverzeichnis
1. Formel zur Umrechnung einer RVA in die Dateiposition . . . . . . . . . . . . . . . . . .
2. 18 Byte am Entry Point einer Anwendung in Zeile 1, Signatur für "Borland C++ 1999" in
Zeile 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3. Assembler Code für den in gezeigten Abschnitt . . . . . . . . . . . . . . . . . . . . . . . .
4. Funktions Pattern für IDA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5. Section Namen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6. Beispiel für Visual Basic Startup Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7. Auszug einer PE Datei, RICH-Signatur hervorgehoben . . . . . . . . . . . . . . . . . . .
8. RICH-Signatur nach Anwenden der XOR-Verknüpfung . . . . . . . . . . . . . . . . . .
9. Code zum Überprüfen der XOR-Mask . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10. Entry-Point Signaturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11. IMAGE_DOS_HEADER . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12. IMAGE_FILE_HEADER . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13. IMAGE_DATA_DIRECTORY . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14. IMAGE_OPTIONAL_HEADER32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15. IMAGE_NT_HEADERS32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16. IMAGE_SECTION_HEADER . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17. IMAGE_IMPORT_BY_NAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18. IMAGE_THUNK_DATA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19. IMAGE_IMPORT_DESCRIPTOR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
. 11
. 15
. 15
. 17
. 22
. 24
. 25
. 25
. 26
. 28
. 33
. 33
. 33
. 34
. 34
. 34
. 35
. 35
. 35
Grundlagen
Grundlagen
Compilieren und Linken
Der Compiler erzeugt aus Quellcode die Objektdateien (Objektcode). Der Linker fügt mehrere
solcher Objektdateien und eventuell verwendete Libraries zu einem Programm (EXE) oder einer
Library (DLL) zusammen. Die Objektdateien enthalten den Maschinencode, der von der CPU
ausgeführt werden kann und Metadaten, die vom Linker benötigt werden. Verweise auf Funktionen
aus anderen Objektdateien heißen Symbole. Diese werden im Linkvorgang aufgelöst und durch
Adressen ersetzt. Man unterscheidet zwischen statischem und dynamischem Linken. Im statischen
Fall wird der Objektcode direkt in das Programm eingefügt. Für dynamisch gelinkte Libraries wird
nur ein Vermerk in der Import Tabelle sowie nötige Relocation Informationen gespeichert. Erst zur
Laufzeit kümmert sich der Loader darum, die in der Import Tabelle angegebenen Libraries zu laden
und die Adressen entsprechend der Relocation Informationen anzupassen.
Der Windows Loader
Der Windows Loader ist Teil des Betriebssystems und dafür verantwortlich Programme in den
Speicher zu laden und auszuführen. Ähnlich wie der Linker verknüpft der Loader verschiedene Codeteile: Das eigentliche Programm und die benötigten dynamischen Libraries.
Datei
Speicher
Abbildung 1: Alignment im Vergleich zwischen Datei und Speicher
Eine Programmdatei besteht in den meisten Fällen aus mehreren Bereichen (Sections). Diese Sections
werden einzeln vom Windows Loader in den Speicher geladen, um die Ausrichtung (Alignment)
anzupassen. Ausgerichtet wird an einem Vielfachen der Pagesize, dem Section-Alignment. Dies ist
nötig, da die Sections innerhalb der Datei meist an einem kleinerem Wert ausgerichtet sind, dem FileAlignment. Abbildung 1 illustriert diesen Vorgang. Ebenso unterscheiden sich die Section-Größe
abhängig davon, ob die Datei in den Speicher geladen wurde.
9
Entwicklung einer Bibliothek zur Compiler Erkennung
Der Linker erstellt eine Anwendung für eine bestimmte Zieladresse (Image-Base). Für den Fall,
dass diese Adresse zur Startzeit belegt ist, muss der Loader das Programm an eine neue
Speicheradresse verschieben (Relocation). Dazu passt der Loader die in der Relocation-Table (auch
Fixup-Table) vermerkten Codestellen an die neue virtuelle Adresse an.
Das PE-Format
Portable Executable (PE) ist das Dateiformat für
Programme unter Microsoft Windows. Die
Struktur ist in der Header-Datei WINNT.H in dem
Abschnitt "Image Format" festgelegt. Eine
ausführliche Erklärung liefert die Referenz
[coff08]. Die wichtigsten Strukturen sind knapp
in Anhang A auf Seite 33 zusammengefasst.
DOS Stub
NT Header
PE Signature
File Header
Optional Header
Image Data Directory
Section Headers
Ein PE-File beginnt mit dem MS-DOS Stub.
Dieser besteht aus dem MZ-Header, benannt
nach dem Microsoft Entwickler Mark
Zbikowski, dessen Initialen die ersten zwei Byte
jeder MS-DOS und Windows Anwendung
bilden. Startet man das Programm unter DOS
wird aus Kompatibilitätsgründen meist die kurze
Meldung "This program cannot be run in DOS
mode" ausgegeben. Der Windows Loader
ignoriert den DOS Stub mit Ausnahme des
e_lfanew Felds. Darin ist ein Pointer auf den
eigentlichen NT-Header enthalten.
.text
.data
.edata
Der NT Header beginnt mit der PESignatur, die aus den vier Byte "PE\0\0" besteht.
Darauf folgt der File Header. Dieser enthält
unter anderem Informationen zur Architektur,
für welche das Programm compiliert wurde.
Einige weitere Charakteristika, sowie die Anzahl
der Sections sind ebenfalls enthalten.
.idata
.reloc
Abbildung 2: Aufbau einer PE-Datei
Der Optional Header darf in einer PE-Datei trotz seines Namens nicht fehlen. Darin enthalten
ist unter anderem die Adresse des Entry Points. Dies ist die Stelle, an der mit dem Ausführen von
Programmcode begonnen wird. Ebenfalls sehr wichtig ist das Image Data Directory, welches angibt,
wofür bestimmte Speicherbereiche, definiert durch eine Relative Virtual Address (RVA) und Länge,
verwendet werden. Dazu gehören beispielsweise Import und Export Bereiche, Ressourcen, das Security
Directory, Debug Informationen oder Copyright Hinweise.
10
Grundlagen
Das Section Directory besteht aus den Section Headern, gefolgt von den Sections, in denen die
eigentlichen Daten enthalten sind. Die Section Header legen fest an welche Position im Speicher eine
Section geladen werden soll. Ebenfalls bestimmt der Section Header gewisse Charakteristika einer
Section (Read, Write, Execute, Caching, Sharing, etc.). Die Sections haben Namen und sind meist für
eine gewisse Aufgabe bestimmt.
So ist die Section mit dem Namen ".text" für Programmcode, ".data" für statische Variablen.
Für die Export / Import Tabellen üblich sind ".edata" und ".idata". ".reloc" enthält die Relocation
Informationen. Diese Namen sind jedoch nicht verbindlich, nur das Image Data Directory gibt
Auskunft über die Verwendung.
Das PE-Format verwendet an einigen Stellen Relative Virtual Addresses (RVAs). Dies sind
Offsets, gemessen von der Image-Base, nachdem die Anwendung in den Speicher geladen wurde.
Benötigt man die Adresse innerhalb einer Datei, muss die RVA mit der Formel in Listing 1
umgerechnet werden.
position in file = RVA - virtual address + pointer to raw data
Listing 1: Formel zur Umrechnung einer RVA in die Dateiposition
Beispielsweise ist die ".data" Section mit der VirtualAddress 0x2000 und dem PointerToRawData
0x800 gegeben. Die ImageBase ist 0x400000. Gesucht wird die Position in der Datei für die virtuelle
Adresse 0x402012. Zuerst zieht man von der virtuellen Adresse die ImageBase ab: 0x402012 0x400000 = 0x2012. Dann ergibt sich: 0x2012 - 0x2000 + 0x800 = 0x812.
11
Entwicklung einer Bibliothek zur Compiler Erkennung
Analyse
Compiler
Die Anzahl existierender Compiler ist enorm. Aus diesem Grund beschränkt sich diese Arbeit auf eine
kleine Auswahl weit verbreiteter Produkte. Besonderer Wert wurde darauf gelegt allgemeingültige
Erkennungstechniken zu finden, die einfach erweitert werden können. Einzelne Merkmale spezieller
Compiler sind dennoch wichtig, um exakte Angaben machen zu können und eventuelle
Manipulationen durch Malware oder künstlich generierte Binaries festzustellen. Die folgenden
Compiler wurden dazu näher untersucht.
Borland C++ Compiler
Borland C++ Compiler in der Version 5.5.1 kann inzwischen kostenfrei von CodeGear1 geladen
werden. Enthalten ist auch der Borland Turbo Incremental Linker.
Delphi
Ebenfalls von Borland ist Delphi, eine objektorientierte Weiterentwicklung von Pascal.
Free Pascal
Free Pascal2 2.2.0 (FPC) ist ein Open-Source Compiler für Pascal und Object-Pascal.
GCC
Die auf der GNU Compiler Collection3 basierenden Windows Varianten MingGW4 3.4.5 und
Cygwin5 GCC 3.4.4 kamen mit den C und C++ Frontends zum Einsatz.
1.
2.
3.
4.
5.
Borland C++ Builder: http://www.codegear.com/downloads/free/cppbuilder
Free Pascal: http://www.freepascal.org/
GNU Compiler Collection: http://gcc.gnu.org/
MinGW: http://www.mingw.org/
Cygwin: http://www.cygwin.com/
12
Analyse
Intel Compiler
Der Intel C++ Compiler (ICL) erstellt speziell für Intel-Prozessoren optimierten Code. Für diese Arbeit
wurden die Versionen 7 und 8 verwendet. Der ICL bringt keinen eigenen Linker mit und ist deshalb
auf Microsoft Visual Studio angewiesen.
LCC
Der Lcc-win32 von Q Software Solutions6 implementiert den ANSI-C Standard. C++ wird nicht
unterstützt. Ein eigener Linker wird mitgeliefert, die Objekt-files sind aber auch zu Microsofts Linker
kompatibel. Untersucht wurde die Version 3.8.
Visual Studio
Visual Studio wurde in den Versionen 6.0, 2005 und 2008 untersucht. In Visual Studio 6.0 ist ein
Visual Basic und ein C/C++ Compiler (MSVC) enthalten. Visual Studio 2005 und 2008 bringen
zusätzlich zu nativem C/C++ auch die .NET Varianten von Visual Basic sowie C# mit. Für Malware
wird C# und .NET jedoch kaum eingesetzt.
Vorbereitungen
Die genannten Produkte wurden auf virtualisierte Windows XP Instanzen installiert. Dazu kam ein
VMware Server7 zum Einsatz. Um die Binaries der verschiedenen Compiler untersuchen und
vergleichen zu können, wurden einige Testprogramme geschrieben. Der OpenSSH8 Server aus dem
Cygwin Projekt ermöglichte die Automatisierung des Buildprozesses über ein Python9 Script. Zur
Abstraktion der unterschiedlichen Compiler-Umgebungen wurde CMake10 verwendet. CMake ist ein
Plattform-übergreifendes Buildsystem, das bereits eine Vielzahl von Compilern unterstützt. Nur für
LCC musste eine neue Toolchain geschrieben werden. Diese Umgebung gestattet ein schnelles
Recompilieren der Testprogramme auf allen Compilern.
6.
7.
8.
9.
10.
Q Software Solutions: http://www.q-software-solutions.de/products/lcc-win32/
VMware Server: http://www.vmware.com/products/server/
OpenSSH: http://www.openssh.com/
Python: http://www.python.org/
CMake: http://www.cmake.org/
13
Entwicklung einer Bibliothek zur Compiler Erkennung
Existierende Techniken
Manuell lässt sich anhand von bestimmten Zeichenketten oft sehr einfach ausmachen, welcher
Compiler für ein Programm verwendet wurde. Diverse Programme haben allerdings bereits die
Funktion, Compiler zu erkennen. Eine sehr einfache Methode ist das Verwenden einer Signatur am
Entry Point. IDA Pro von Hex-Rays11 verwendet einen ähnlichen Ansatz mit der "Fast Library
Identification and Recognition Technology".
String Konstanten
Einige Standard Libraries enthalten Copyright-Hinweise oder andere Strings, durch die sie den
Compiler verraten. Beispiele dafür sind:
•
•
•
•
•
•
•
"Borland C++ - Copyright 1999 Inprise Corporation"
"FPC 2.2.0 [2007/09/09] for i386 - Win32"
"-LIBGCCW32-EH-3-SJLJ-GTHR-MINGW32"
"lcc runtime"
"Microsoft (R) Optimizing Compiler"
"Microsoft Visual C++ Runtime Library, Error R6035"
"Open Watcom C/C++32 Run-Time system. Portions Copyright
(C) Sybase, Inc. 1988-2002. "
Einzigartige Format-Strings, Assert- und Fehlermeldungen können ebenfalls dazu beitragen einen
Compiler zu identifizieren. Ein weiteres Beispiel ist der String "This program was not built to run
on the processor" aus dem Intel Compiler 8.
Für das menschliche Auge ist dies wohl die einfachste Methode einen Compiler zu erkennen. Ein
Hex-Editor oder das UNIX Kommando strings sind alles, was dazu benötigt wird.
Programmiertechnisch wäre eine Suche nach diesen Zeichenketten ebenso trivial. Jedoch hinterlässt
nicht jeder Compiler eindeutige Strings. Hinzu kommt, dass eine ganze Datei nach diesen Strings zu
durchsuchen sehr ineffizient und anfällig für Manipulationen ist.
Entry Point Signatur
Der Entry Point eines PE-Files ist die Stelle, an der die Ausführung des Programms gestartet wird.
Hier wird der Code aus der Standard Library des Compilers gelinkt, der einige Vorbereitungen trifft,
um die eigentlich Startfunktion (main() in C/C++) aufzurufen. Dieser Bereich ist, abgesehen von
wenigen Bytes, die Sprungadressen (RVA) enthalten, meist statisch und kann gegen eine Signatur
geprüft werden. Somit lässt sich schnell und effizient auf den verwendeten Compiler schließen.
11. IDA Pro: http://www.hex-rays.com/idapro/
14
Analyse
Abbildung 3: PEiD erkennt Programm als "Borland C++ 1999"
Dieses Verfahren wird beispielsweise bei PEiD12 verwendet. PEiD Signaturen werden byteweise ASCII
codiert in eine INI Datei eingetragen. Variable Bytes werden wie in Listing 2 mit "??" maskiert.
Betrachtet man Listing 3 wird verständlich, weshalb gerade diese Bytes veränderlich sind: Adressen für
Sprünge und Daten, die sich von Anwendung zu Anwendung unterscheiden.
EB 10 66 62 3A 43 2B 2B 48 4F 4F 4B 90 E9 A0 61 41 00 A1 93 61 41 00 C1
EB 10 66 62 3A 43 2B 2B 48 4F 4F 4B 90 E9 ?? ?? ?? ?? A1 ?? ?? ?? ?? C1
Listing 2: 18 Byte am Entry Point einer Anwendung in Zeile 1, Signatur für "Borland C++ 1999" in Zeile
2
Ein solcher Signaturvergleich ist trivial zu implementieren, was die Vielzahl von Tools erklärt, die eine
Compiler Erkennung auf diese Weise umgesetzt haben. Das ExeFormat13 Plugin für den Total
Commander14 erkennt ebenfalls den Compiler mit einfacher Signaturprüfung am Entry Point. Das
Format für die Signatur Datenbank ist ähnlich.
eb10
66623a432b2b484f4f4b
90
e9a0614100
a193614100
|
|
|
|
|
jmp 0x12
db 'fb:C++HOOK'
nop
jmp 0x4161b2
mov eax,[0x416193]
Listing 3: Assembler Code für den in Listing 2 gezeigten Abschnitt
12. PEiD: http://www.peid.info
13. ExeFormat: http://wincmd.ru/plugring/exeformat.html
14. Total Commander: http://www.ghisler.com/
15
Entwicklung einer Bibliothek zur Compiler Erkennung
FLIRT mit IDA
IDA Pro, der Interactive Disassembler and Debugger von Hex-Rays, enthält die Fast Library
Identification and Recognition Technology (FLIRT). FLIRT ermöglicht es Library Funktionen im
Disassembly zu erkennen und mit Namen zu versehen. Da Anwendungen im Durchschnitt zu 50%
aus gelinkten Library Funktionen bestehen15, lässt sich viel Zeit einsparen, wenn die Hälfte des Codes
nicht mehr von Hand analysiert werden muss.
Abbildung 4: IDA Pro
Die Information, welche Abschnitte eines Programms, Code einer Library ist, wird auch für die
Programm Navigation Bar verwendet. Mit der Navigation Bar, zu sehen in Abbildung 5, ist es einfach
möglich zur eigentlichen Programmlogik zu springen. Über die Platzierung von Library Code und
normalem Code lässt sich theoretisch der Compiler erkennen. Die Voraussetzung dafür ist, dass
FLIRT zuverlässig die entsprechenden Codeteile identifiziert. Dieses Ziel zu erreichen ist jedoch mit
einigen Schwierigkeiten verbunden.
Abbildung 5: IDA Pro Navigation Bar
15. nach [guil97]
16
Analyse
Bedenkt man den Speicherplatz, der für alle Versionen aller Libraries, die mit jedem Compiler
produziert wurden, verbraucht werden würde, gelänge man schnell in unpraktikable Dimensionen.
Wie auch schon bei der Entry Point Signatur verhindern variable Bytes das Berechnen einer einfachen
Prüfsumme. Ebenfalls problematisch sind kurze, vollständig identische, Funktionen mit
unterschiedlichen Namen. Besonders kurze Exemplare bestehen teilweise sogar nur aus call + ret
oder simplen jmps. Die einzige Möglichkeit zu differenzieren, ist zu unterscheiden, welche Funktionen
die Funktion selbst aufruft.
Eine Kombination aus mehreren Techniken verhilft zu einer sehr zuverlässigen Erkennung. Die
ersten 32 Byte einer Funktion werden gegen Signaturen mit variablen Bytes verglichen. Ist dies nicht
ausreichend, um die Funktion eindeutig zu bestimmen, wird eine CRC16 ab Byte 33 bis zu der ersten
variablen Stelle berechnet. Für den Fall, dass dies noch immer nicht ausreicht, um eine Entscheidung
zu treffen, kann eine Liste von referenzierten Funktionen angegeben werden.
Wie in Listing 4 (Beispiel entnommen aus [guil97]) gut zu sehen, beginnen viele Funktionen sehr
ähnlich. Das Speichern der Daten als Tree bietet sich an. Durch die Tree-Struktur ergibt sich ein
geringer Platzbedarf. Ebenso wird die Suche auf eine Komplexität von Ο(log n) beschränkt, wobei n
die Anzahl der bekannten Funktionen ist.
558BEC0EFF7604..........59595DC3558BEC0EFF7604..........59595DC3
558BEC1E078A66048A460E8B5E108B4E0AD1E9D1E980E1C0024E0C8A6E0A8A76
558BEC1EB41AC55604CD211F5DC3....................................
558BEC1EB42FCD210653B41A8B5606CD21B44E8B4E088B5604CD219C5993B41A
_registerbgidriver
_biosdisk
_setdta
_findfirst
Listing 4: Funktions Pattern für IDA
FLIRT liefert sehr ausführliche Informationen und ist eine nützliche Erweiterung für IDA. Trotz
hoch-effizienter Implementierung und vielen Optimierungen, wird zur vollständigen Erkennung aller
Funktionen viel Zeit benötigt. Dass die Dateien vollständig gelesen werden ist problematisch.
Schwerer noch wiegt die beanspruchte Rechenzeit, um die Verweise auf andere Funktionen zu
sammeln und zu vergleichen. Aus diesen Gründen ist es nicht praktikabel, ein so aufwendiges
Verfahren in eine Scan-Engine zu integrieren.
17
Entwicklung einer Bibliothek zur Compiler Erkennung
Weitere Möglichkeiten
Standardwerte für Header
Unterschiede in der Art und Weise wie Compiler implementiert wurden, spiegeln sich auch in den
einzelnen Werten der Header wider. So gibt es etliche Header-Werte, die für einen bestimmten
Compiler immer gleich bleiben, bzw. in einem bekannten Wertebereich liegen. Es ist jedoch zu
beachten, dass es sich hierbei um Standardwerte handelt, die teilweise sehr einfach über BuildParameter geändert werden können. Diese Werte alleingestellt zu betrachten, um eine Entscheidung
über den Compiler zu treffen, ist deshalb unzureichend.
Während die meisten Compiler den Microsoft DOS-Stub übernommen haben, werden mit
Borland und Watcom eigenständige Stubs erzeugt. Sie unterscheiden sich in einigen Headerflags wie
Tabelle 1 zeigt. Im Gegensatz zur üblichen Meldung "This program cannot be run in DOS mode"
geben Borland Programme "This program must be run under Win32" unter DOS aus. Watcom
Compilate melden sich mit "This is a Windows NT character-mode executable" bzw. "This is a
Windows NT windowed executable".
Headerflag borland gcc/cygwin gcc/mingw icl7 icl8 lcc
vs6
vs05 vs08 watcom
e_cblp
80
144
144
144 144 144 144 144
144
128
e_minalloc
15
0
0
0
0
0
0
0
0
0
e_ovno
26
0
0
0
0
0
0
0
0
0
Tabelle 1: DOS Headerflags im Vergleich
Der Pointer auf den PE Header, e_lfanew, wird in Tabelle 2 verglichen. Im Regelfall beginnt der PE
Header an dem Offset 128. Borland fängt erst an der Position 512 an und füllt den Bereich bis dorthin
mit Null-Bytes. Auffällig sind Compiler, welche den Microsoft Linker verwenden: ICL und Visual
Studio. Aufgrund der Rich-Signatur (siehe Microsoft RICH Signatur auf Seite 25) liegt der PE Header
an unterschiedlichen Positionen, abhängig davon wieviele Libraries statisch gelinked wurden.
18
Analyse
Test
borland
gcc_cygwin
gcc_mingw
icl7
icl8
lcc
vs6
vs05
vs08
watcom
C_1
512
128
128
200
208
128
208
224
224
128
C_2
512
128
128
200
208
128
200
216
216
128
C_3
512
128
128
200
208
128
200
232
232
128
C_4
512
128
128
200
208
128
200
224
216
128
C_5
512
128
128
216
208
128
208
216
224
128
C_6
512
128
128
216
208
128
200
216
232
128
C_7
512
128
128
200
208
128
200
232
232
128
DLL_1
512
128
128
200
200
128
192
224
240
128
DLL_2
512
128
128
200
200
128
192
224
240
128
CPP_1
512
128
128
224
232
208
224
216
128
CPP_2
512
128
128
216
232
200
240
232
128
CPP_3
512
128
128
216
208
208
216
224
128
CPP_4
512
128
128
208
208
208
216
224
128
CPP_5
512
128
128
208
232
200
224
216
128
CPP_6
512
128
128
208
232
208
240
240
128
Tabelle 2: DOS Header, e_lfanew
Tabelle 3 zeigt anhand von acht ausgewählten C und C++ Anwendungen, welche Compiler den Entry
Point an eine fixe Adresse legen: Borland C++, beide GCC Varianten sowie LCC. Bei anderen
Compilern, auch den nicht in der Tabelle aufgeführten FreePascal und Visual Basic 6, hängt der Entry
Point von der Länge des Objektcodes ab. Das Testprogramm "C_1" ist, aufgrund der hohen Anzahl
von Funktionen, zu groß, so dass der Intel Compiler 8 sowie Watcom kein Binary erstellen können.
Der LCC ist ein reiner C Compiler - somit existieren für die C++ Programme hier ebenfalls keine
Daten. Eine weitere Schwäche dieser Art Compiler zu erkennen wird durch Borland C++ sowie GCC/
Cygwin deutlich. Nur weil ein Compiler, unabhängig vom Quellcode, immer einen festen Wert
verwendet, ist das noch keine Garantie für Eindeutigkeit.
19
Entwicklung einer Bibliothek zur Compiler Erkennung
Test
borland gcc/mingw gcc/cygwin
icl7
icl8
lcc
vs6
vs05
vs08
watcom
C_1
4096
4736
4096
4256
4184
4645
4256
4992
5008
4200
C_2
4096
4736
4096
4160
4164
4645
4176
4928
4944
4172
C_3
4096
4736
4096
4256
4174
4645
4288
5040
5056
4220
C_4
4096
4736
4096
4256
4185
4645
4208
4960
4976
4222
C_5
4096
4736
4096
4560
4356
4645
4432
5168
5184
4390
CPP_1
4096
4736
4096
4992
11604
5088
5456
5472
4416
CPP_2
4096
4736
4096
4496
5462
4640
7200
7232
4188
CPP_3
4096
4736
4096
9120
13988
7344
8080
8096
56506
Tabelle 3: Optional Header, AddressOfEntryPoint für diverse C und C++ Programme
Die Standardgröße des Stacks (in Tabelle 4), sowie des Stack Commits (in Tabelle 5) unterscheiden
sich ebenfalls bei einigen Compilern. Es handelt sich dabei jedoch, wie bei den meisten Headerflags,
nur um Default-Werte. Mit dem /STACK:reserve[,commit]16 Parameter kann beispielsweise der
Microsoft Visual C++ Linker angewiesen werden diese Werte zu ändern.
borland
cygwin
mingw
icl7
icl8
lcc
vs6
vs05
vs08
watcom
10000000 2097152 2097152 10000000 10000000 1048576 10000000 10000000 10000000 65536
Tabelle 4: Optional Header, SizeOfStackReserve
borland
10000000
cygwin
mingw
4096
4096
icl7
icl8
lcc
vs6
4096
4096
4096
4096
vs05
4096
vs08
4096
watcom
65536
Tabelle 5: Optional Header, SizeOfStackCommit
Der Optional Header enthält auch Platz für die Versionsnummer des Linkers, welche zwar selbst
nicht direkt auf einen Compiler schließen lässt, zur Überprüfung anderer Erkennungen aber
mitverwendet werden kann. Einige weitere Tabellen mit Header-Werten sind in Anhang B auf Seite
36 zu finden.
16. MSDN: /STACK (Stack Allocations): http://msdn.microsoft.com/en-us/library/8cxs58a6(VS.80).aspx
20
Analyse
Imports
Die Imports Tabelle informiert den Loader welche Symbole aus einer DLL geladen werden müssen.
Der Linker hat vorher festgelegt welche Symbole in welcher DLL vorzufinden sind.
Abhängig vom Compiler unterscheiden sich auch die Import Tabellen. Offensichtliche Merkmale
sind beispielsweise, dass Microsoft Visual Basic 6 nur Symbole aus MSVBVM60.DLL importiert. Ebenso
importieren alle .NET Anwendungen (Visual Basic .NET, C#, etc.) von Microsoft Compilern die
Bibliothek mscoree.dll. Von Mono17 übersetzte .NET Programme beziehen sich auf mono.dll. GCC/
Cygwin Programme haben eine Abhängigkeit auf die eigene cygwin1.dll.
Die Dateien mt7r17.dll, clbr17.dll und plbr17.dll sind typisch für Anwendungen, die mit
Watcom kompiliert wurden. Interessant ist auch die Datei KERNEL32.DLL. Borland C++ vermerkt den
Dateinamen komplett in Großbuchstaben, Delphi und FreePascal dagegen komplett in
Kleinbuchstaben. Die meisten anderen Compiler, welche diese Datei importieren verwenden die
folgende Schreibweise: KERNEL32.dll
Weitere Delphi typische Bibliotheken sind
Versionnummer steht.
vclnn.dll
und
rtlnn.dll,
wobei "nn" für eine
Ob ein GCC/MinGW Programm in C oder C++ geschrieben wurde lässt sich ebenfalls über die
Import Tabelle erfahren. MinGW Anwendungen in C++ enthalten zwei IMPORT_DESCRIPTOR18
Angaben für die Datei msvcrt.dll.
Bei anderen Compilern kann durch Betrachten der Symbolnamen teilweise entschieden werden,
ob es sich um C++ oder C Handelt. Die Symbolnamen werden durch Name-Mangling aus den
eigentlichen Funktionsnamen abgeleitet. Name-mangling soll verhindern, dass Funktionen die eine
inkompatible Calling Convention verwenden, versehentlich aufgerufen werden.
Calling Conventions sind Konventionen, die beschreiben, wie eine Funktion aufgerufen wird.
Dazu gehören Eigenschaften wie die Reihenfolge der übergebenen Parameter, wie diese übergeben
werden (über definierte Register oder den Stack). Ebenso hängt es von der Calling Convention ab, ob
die aufgerufene oder die aufrufende Funktion dafür verantwortlich ist den Stack zu leeren.
17. Mono-Project: http://www.mono-project.com
18. siehe Listing 19
21
Entwicklung einer Bibliothek zur Compiler Erkennung
Für Funktionen, die der "cdecl" Calling Convention folgen, wird meistens einfach ein "_"
vorangestellt. Symbole für C++ verwenden komplexere Name-mangling Schemata. So wird "class
std::basic_ostream<char,struct std::char_traits<char> > std::cout" von Microsoft Visual C++
2008 in das Symbol "?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A " umgewandelt
und aus der Datei MSVCP90.dll importiert. Es gibt jedoch keinen Standard für C++ Name-mangling,
weshalb sich unterschiedliche Systeme entwickelt haben. Diese lassen wiederum Rückschlüsse auf den
Compiler zu.
Section Namen
Die Sections haben einen 8 Byte langen Namen. Dieser muss nicht Null-Terminiert sein. Die nicht
verwendeten Zeichen sind allerdings meist mit Null-Bytes aufgefüllt. Ausgefallene Section-Namen
sowie die Anordnung der Sections sind Unterscheidungsmerkmale der Compiler. Bei dem
Untersuchen der Binaries konnte das in Listing 5 abgebildete Regelwerk erstellt werden. Sections in
eckigen Klammern sind dabei optional.
.text .data .tls .idata .edata .rsrc .reloc
.text .data .tls .rdata .idata .edata .rsrc .reloc
=> borland c++ (dll)
=> borland c++
.text .rsrc .reloc
=> c#
CODE DATA BSS .idata .tls .rdata .reloc .rsrc
=> delphi
.text .data .bss .idata
.text .data .bss .idata .stab .stabstr
=> freepascal
=> freepascal (debug)
.text
.text
.text
.text
=>
=>
=>
=>
.data .rdata .bss .edata .idata .reloc
.data .rdata .bss .idata
.bss .edata .idata .reloc
[.rdata] .bss .idata
gcc/mingw (dll)
gcc/mingw, gcc/cygwin
gcc/cygwin (dll)
gcc/cygwin
.text [.text1] [.rdata] .data [.idata] [.data1] [.reloc]
=> icl7, icl8
.text [.bss] .rdata .data .idata [.reloc] [.edata]
=> lcc
.text .rdata .data [.idata] [.reloc]
.text .rdata .data [.idata] .rsrc [.reloc]
=> msvc6
=> msvc05, msvc08
.text .data .rsrc
.text .sdata .rsrc .reloc
=> vb6
=> vb.net
AUTO .idata DGROUP .reloc
AUTO .idata .edata .reloc
=> watcom
=> watcom (dll)
Listing 5: Section Namen
22
Analyse
Paddings
Paddings sind Bereiche, die keine relevanten Daten enthalten. Sie werden meist eingefügt, um das
Speicher-Alignment zu verbessern. Es gibt viele Stellen, an denen Compiler und Linker Padding-Bytes
einfügen: Zwischen den Sections, am Ende innerhalb einer Section, zwischen Headern und im Code,
beispielsweise zwischen den Funktionen.
In den meisten Fällen wird mit 0-Bytes aufgefüllt. Abweichungen davon oder bekannte Längen
können helfen einen Compiler zu identifizieren. So wird der String "PAPADDINGXX" verwendet, um die
Ressource Section aufzufüllen. Normalerweise ein Zeichen für Microsoft Visual C++. Ursprünglich
gelangt diese Zeichenkette jedoch über den Windows API Aufruf UpdateResource()19 in die Section
und kann auch auf eine Manipulation durch andere Tools hindeuten.
Ein interessanter Padding-Bereich ist das Ende der .text-Section. Zwischen der letzten CPU
Instruktion und den 0-Bytes, welche die Section füllen, fügen einige Compiler 0xCC-Bytes ein. CC ist
die Intel Instruktion INT 3, welche den Debug Exception Handler aufruft. Borland C++ fügt entweder
zwei oder keine dieser Bytes ein. Watcom Anwendungen enden immer mit genau einem CC-Opcode.
Der Intel Compiler sowie Microsoft Visual C++ bewegen sich meist im vierstelligen Bereich an 0xCCPaddingbytes.
Decompilieren / Assembly Pattern
Untersucht man den eigentlichen Code ein, finden sich ebenfalls Möglichkeiten den Compiler zu
erkennen. Mit Hilfe eines Disassemblers kann man die einzelnen CPU-Instruktionen aufzählen und
bewerten. Daraufhin kann nach bestimmten Eigenheiten und bekannten Strukturen gesucht werden.
Beispielsweise fügen der Intel Compiler, wie auch Microsoft Visual C++ INT 3 Sequenzen
zwischen den Funktionen ein. Borland C++ sowie die GCC-Varianten fügen NOPs ein. NOPInstruktionen, mit dem Opcode 0x90, haben keine Auswirkung (No Operation).
19. MSDN: UpdateResource Function: http://msdn.microsoft.com/en-us/library/ms648049(VS.85).aspx
23
Entwicklung einer Bibliothek zur Compiler Erkennung
Visual Basic Header
Visual Basic in den Versionen 5 und 6 können Binaries in zwei Varianten erzeugen: Native und PCode. P-Code Binaries enthalten die Programmlogik als Bytecode und benötigen zur Ausführung den
Visual Basic Interpreter. Bis zur Version 5 wurden alle Visual Basic Programme so erstellt. Bei NativBinaries wird direkt Objektcode generiert, der von der CPU ausgeführt wird.
Der Visual Basic Startup Code am Entry Point ist sehr kurz, weshalb ein Matching mit einfacher
Signatur nicht zuverlässig ist. Der Code besteht gerade einmal aus zwei Instruktionen, wie Listing 6
zeigt und entspricht in etwa ThunRTMain(&RT_MainStruct);. Wie in [gedd03] beschrieben, ist das die
Startfunktion der VBVM (Thunder Runtime Engine), welche dynamisch in jedes Visual Basic
Programm gelinkt ist. Der Parameter ist ein Pointer auf eine komplexe Struktur, mit der die gesamte
Anwendung beschrieben wird.
68a8134000
e8eeffffff
| push dword 0x4013a8
| call 0xfffffff8
Listing 6: Beispiel für Visual Basic Startup Code
Durch Parsen der Struktur ist es nun möglich zu erkennen, ob es sich um ein Visual Basic Programm
handelt. Das erste Objekt ist der VB Header, der mit dem 4 Byte großem Magic String "VB5!" beginnt.
Der weitere Aufbau ist größtenteils in [ione04] dokumentiert.
.NET Metadaten
Ähnlich wie bei Visual Basic 5 und 6 haben bei
.NET Metadaten eine große Bedeutung.
Anwendungen in .NET werden von einer
Hochsprache wie VB.net, C# oder J# in die
Common Intermediate Language (CIL) übersetzt.
Der CIL Bytecode wird zur Laufzeit von der
Common Language Runtime (CLR) in Maschinen
Code übersetzt und ausgeführt.
PE Headers
CLR Header
CLR Data
Native Data / Code
Abbildung 6: Aufbau einer .NET Anwendung
24
Analyse
Im Gegensatz zu normalen PE Files sind .NET Programme um sehr komplexe Strukturen
erweitert. Eine Vielzahl von neuen Headertypen liefern Informationen für die CLR. Der 15te
Directory Entry des PE Headers enthält RVA und Größe des Common Language Runtime Headers.
Der Metadata Storage Signature Header beginnt mit dem Magic 0x424a5342 bzw. "BSJB" - nach
[lidi06] die Initialen von Brian Harry, Susan Radke-Sproull, Jason Zander und Bill Evans, die 1998 mit
der Entwicklung der Runtime begonnen haben. Viele weitere Header können geparsed werden, um zu
entscheiden, ob es sich um eine .NET Anwendung handelt.
Microsoft RICH Signatur
Die RICH Signatur ist ein Merkmal, das nur der Microsoft Linker hinterlässt. Sie wird im Anschluss
an den DOS Stub vor den eigentlichen NT Header eingefügt. Für das Ausführen des Programms ist
die RICH Signatur nicht von Bedeutung, sie wird vom Loader übersprungen und ignoriert. Von
Microsoft existiert keine Dokumentation dazu. Bis zur Veröffentlichung von [pist08], war nicht
vollständig geklärt, welche Daten darin enthalten sind.
0000000:
0000010:
0000020:
0000030:
0000040:
0000050:
0000060:
0000070:
0000080:
0000090:
00000a0:
00000b0:
00000c0:
00000d0:
00000e0:
00000f0:
4d5a
b800
0000
0000
0e1f
6973
7420
6d6f
410a
22ad
c664
22ad
22ad
0000
a52e
0b01
9000
0000
0000
0000
ba0e
2070
6265
6465
9407
9754
a754
9454
8254
0000
0849
0800
0300
0000
0000
0000
00b4
726f
2072
2e0d
056b
116b
076b
026b
046b
0000
0000
004c
0000
0000
0000
0000
09cd
6772
756e
0d0a
fa54
fa54
fa54
fa54
fa54
0000
0000
0000
0400
4000
0000
0000
21b8
616d
2069
2400
056b
22ad
056b
22ad
5269
5045
0000
003c
0000
0000
0000
0000
014c
2063
6e20
0000
fa54
8154
fb54
8654
6368
0000
0000
0000
ffff
0000
0000
d800
cd21
616e
444f
0000
056b
006b
486b
046b
056b
4c01
e000
0000
0000
0000
0000
0000
5468
6e6f
5320
0000
fa54
fa54
fa54
fa54
fa54
0500
0301
0000
MZ..............
........@.......
................
................
........!..L.!Th
is program canno
t be run in DOS
mode....$.......
A....k.T.k.T.k.T
"..T.k.T"..T.k.T
.d.T.k.T.k.THk.T
"..T.k.T"..T.k.T
"..T.k.TRich.k.T
........PE..L...
...I............
.....L...<......
Listing 7: Auszug einer PE Datei, RICH-Signatur hervorgehoben
Die Daten sind mit einem Double-Word großem Key XOR-verschlüsselt. Aufgrund der geringen
Key-Länge und vielen 0-Bytes in den Originaldaten bildet sich, wie in Listing 7, ein auffälliges Muster
ab. Der Key folgt direkt auf die Bytes "Rich". Nach der XOR-Verknüpfung mit dem Key erhält man
die ursprünglichen Daten (siehe Listing 8).
0000080:
0000090:
00000a0:
00000b0:
00000c0:
00000d0:
4461
27c6
c30f
27c6
27c6
0000
6e53
6d00
5d00
6e00
7800
0000
0000
1400
0200
0700
0100
0000
0000
0000
0000
0000
0000
0000
0000
27c6
0000
27c6
5269
0000
7b00
0100
7c00
6368
0000
0500
4d00
0100
056b
0000
0000
0000
0000
fa54
Listing 8: RICH-Signatur nach Anwenden der XOR-Verknüpfung
25
DanS............
'.m.....'.{.....
..].........M...
'.n.....'.|.....
'.x.....Rich.k.T
........
Entwicklung einer Bibliothek zur Compiler Erkennung
Es ergibt sich ein Aufbau wie in Abbildung 7:
Nach dem Identifier "DanS" wird dreimal der
Key angegeben. Es folgt eine Liste von DataPaaren. Nach dem Wort "Rich" wird der Key
noch einmal wiederholt. Beendet wird die
Struktur durch einen Padding Bereich, der mit
0-Bytes gefüllt ist.
"DanS"
XOR Key
XOR Key
XOR Key
Data 1
Ein Data-Paar enthält im ersten DoubleWord
eine
"comp.id".
Dies
sind
Versionsnummern der statisch gelinkten
Libraries. Im Low-Word ist die Build Nummer,
im High-Word die Major Nummer (4 Low-Bits)
und die Subversion Nummer (4 High-Bits). Das
zweite Double-Word ist die Anzahl, wie oft diese
comp.id referenziert wird.
Data 2
...
Data n
"Rich"
Die XOR Mask ist eine Prüfsumme, welche
XOR Key
über den DOS-Stub und die Data-Liste
berechnet wird. Um die Gültigkeit der
Padding
Prüfsumme zu testen kann das Verfahren aus
Listing 9 verwendet werden. Initial wird die
Mask auf 0x80 gesetzt. Jedes Byte des DOS-Stub
Abbildung 7: Aufbau der RICH-Signatur
wird um den Wert seiner Position nach Links
rotiert und zur Maske addiert. Einzige
Ausnahme bildet DWord 0x3c, das ignoriert wird. An dieser Stelle befindet sich e_lfanew - der Pointer
auf den PE-Header. Zu dem Zeitpunkt, an dem die XOR Mask generiert wird, ist der Wert noch nicht
bekannt und wird deshalb durch 0 ersetzt. Die "comp.id" DWords werden um die Anzahl der
Referenzen nach Links rotiert und ebenfalls addiert.
/* rotate x left by n bits */
unsigned int rol(unsigned int x, unsigned int n) {
return ((x << n) | (x >> (32 - n)));
}
mask = 0x80;
for(i = 0; i < 0x80; i++) {
if(i == 0x3c) continue;
mask += rol(dos_stub[i], i);
}
for(i = 0; data[i] != 0x6863652; i++) {
mask += rol(data[i], data[++i]);
}
Listing 9: Code zum Überprüfen der XOR-Mask
26
Realisierung
Realisierung
Die in der vorangegangenen Analyse gesammelten Erfahrungen wurden in einer C Library umgesetzt.
Diese ermöglicht eine zuverlässige Erkennung und Unterscheidung, mit welchem Compiler
ausführbare Windows-Programmdateien erzeugt wurden. Außerdem befähigt die Bibliothek die Avira
Scan-Engine Windows-Programmdateien zu erkennen, die vorgeben mit einem bekannten Compiler
erzeugt worden zu sein, tatsächlich jedoch nicht mit diesem Compiler generiert wurden.
Der Quellcode ist portabel und auf allen unterstützten Prozessoren (x86, Sparc, S390, PowerPC
und ARM) sowie verschiedenen Betriebssystemen (UNIX, Windows) kompilierbar. Die Bibliothek
kann sowohl statisch als auch dynamisch gelinkt werden.
Prototypen
Um diverse Erkennungsverfahren schon während der Analyse zu testen wurden mehrere Prototypen
entwickelt. Die dafür benötigte Entwicklungszeit konnte durch die Script-Sprache Python relativ kurz
gehalten werden. Die Python Module pefile1, um PE Dateien einzulesen und pydasm2, als x86
Disassembler waren ebenfalls an vielen Stellen hilfreich. Durch IPython3, eine erweiterte Python
Shell, war interaktives Ausprobieren vieler Ideen schnell und einfach möglich.
Aufbau
Die Compiler Detection Library (CDL) kann in zwei Aufgabenbereiche unterteilt werden:
1. Erkennung des Compilers
2. Anomalie-Prüfung
Das Erkennen des Compilers erfolgt möglichst effizient und präzise. Über aufwändigere oder
unschärfere Techniken kann bei Bedarf das Ergebnis verifiziert werden.
1. pefile: http://dkbza.org/pefile.html
2. libdasm und pydasm: http://www.nologin.org/main.pl?action=codeView&codeId=49
3. IPython: http://ipython.scipy.org
27
Entwicklung einer Bibliothek zur Compiler Erkennung
Erkennung des Compiler
Um den Compiler zu erkennen wird ein optimiertes Entry-Point-Signatur Verfahren eingesetzt.
Dieses Verfahren bringt etliche Vorteile mit sich. So ist es einfach erweiterbar durch Hinzufügen
neuer Signaturen. Der Code muss dazu nicht verändert werden. Des Weiteren existieren bereits
größere Signatur-Datenbanken für die populärsten Compiler. Der Algorithmus selbst ist sehr einfach
verständlich und somit leicht wartbar.
Problematisch sind Fälle, in denen der Code am Entry-Point zu kurz ist, um eine zuverlässige
Signaturprüfung durchzuführen. Das betrifft hauptsächlich Visual Basic und .NET Anwendungen.
Glücklicherweise liefern diese genügend andere Merkmale. Die CDL enthält deshalb Funktionen,
welche vor der Signatur Prüfung aufgerufen werden um diese Fälle abzudecken.
Optimierungen
Im Gegensatz zu den existierenden Programmen, die eine Compiler Erkennung über den Entry-Point
implementieren, benötigt die CDL weniger Speicher und CPU-Zeit. Das ist möglich, da genannte
Implementationen alle Signaturen getrennt überprüfen und die Signaturdatenbank als ASCII-Datei
speichern.
5589e583ec08c7042401000000ff15e4404000:
5589e583ec08c7042401000000ff15fc404000:
5589e583ec8083e4f0a100??400085c07410cc:
558bec6aff686864a100000000506489250000:
8bff558bece846e00000e8110000005dc3cccc:
'GCC/MinGW 3.2.x (main)'
'GCC/MinGW 3.2.x (WinMain)'
'GCC/Cygwin 3.4.4'
'Microsoft Visual C++ 6.0'
'Microsoft Visual C++ 2008'
Listing 10: Entry-Point Signaturen
Betrachtet man die Entry-Point Signaturen in Listing 10 (zur Übersicht auf 20 Byte gekürzt), sind
Ähnlichkeiten schnell erkennbar. Wie bei den IDA FLIRT Signaturen wird deshalb ein Tree als
Datenstruktur für die Signaturen verwendet. Abbildung 8 zeigt, wie die Signaturen als Baum
zusammengefasst werden können. Wird eine größere Anzahl an Signaturen verwendet verstärkt sich
dieser Effekt. Die Komplexität der Signatur-Vergleiche verbessert sich von linear auf logarithmisch in
Abhängigkeit zur Signaturanzahl.
28
Realisierung
e4404000
GCC/MinGW 3.1.x (main)
fc404000
GCC/MinGW 3.2.x (WinMain)
08c7042401000000ff15
5589e583ec
8083e4f0a100??400085c07410cc
GCC/Cygwin 3.4.4
55
8bec6aff686864a100000000506489250000
8bff558bece846e00000e8110000005dc3cccc
Microsoft Visual C++ 6.0
Microsoft Visual C++ 2008
Abbildung 8: Entry-Point Signaturen als Baum
Um ein Byte ASCII-Kodiert darzustellen werden zwei Zeichen benötigt. Indem die Signaturen in
Binärform, im Gegensatz zum ASCII Format, gehalten werden, kann der Speicherverbrauch um die
Hälfte gesenkt werden. Die Zeichenfolge "??", welche ein Varianten-Byte repräsentiert, kann jedoch
nicht direkt in das Binärformat übernommen werden. Zusätzlich zu der Signatur wird deshalb eine
Bitmask gespeichert. Soll das n-te Byte übersprungen werden wird auch das n-te Bit der Maske auf
eins gesetzt.
Da die Signaturen schon zu einzelnen Knoten zusammengefasst wurden, reduziert sich deren
Länge. Die Bitmask muss nicht mehr besonders groß sein, um jedes Byte zu adressieren. Für den Fall,
dass die Bitmask nicht mehr ausreicht, um ein Variantenbyte zu markieren, kann der Knoten in
kleinere Stücke geteilt werden.
Die Signaturen in einer Text-Datei zu halten bringt jedoch auch Vorteile mit sich. So ist es
wesentlich einfacher die Signaturdatenbank zu verwalten und zu bearbeiten. Aus diesem Grund wird
der Tree erst zur Buildzeit automatisch erstellt. Ein Python Script generiert eine C-Datei mit einer
Funktion, um den Tree aufzubauen.
Python Script
ASCII Datei
C-Source
Abbildung 9: Konvertierung der Signaturen in C-Code
29
Entwicklung einer Bibliothek zur Compiler Erkennung
Anomalie-Prüfung
Nachdem der Compiler im ersten Schritt erkannt wurde, kann ein Binary auf mögliche
Ungereimtheiten überprüft werden. Diese können beispielsweise durch ungewöhnliche Parameter im
Build-Prozess oder nachträgliche Manipulation der Anwendung auftreten.
Die CDL bietet dazu zwei Möglichkeiten an. Eine Funktion, die alle bekannten Tests durchführt
und eine Liste der gefundenen Abweichungen zurückliefert. Dies ist einfach zu verwenden und bietet
eine stabile API.
Werden jedoch genauere Details zu einzelnen Abweichungen benötigt, können die Tests auch
direkt aufgerufen werden. So ist auch das gezielte Untersuchen bestimmter Merkmale möglich. Da
die Tests tiefer in die Struktur der Anwendung eintauchen als die Entry-Point Signatur, benötigen
diese oft mehr Ressourcen. Um Geschwindigkeits-Vorteile zu erzielen ist es möglich Tests, die nicht
von Interesse sind, zu überspringen.
Momentan existieren Tests für einige Header Defaults, Section Namen und die RICH-Signatur.
Weitere Test-Module in die CDL zu integrieren ist mit geringem Aufwand möglich. Da diese Module
komplexe Datenstrukturen wie die RICH-Signatur parsen, können sie die Resultate über eine eigene
API zur Verfügung stellen.
Das Regelwerk für den Test der Section Namen wurde schon vorgestellt. Auch hier wird eine
Baumstruktur aufgebaut. Das Vorhanden sein der RICH-Signatur ist leicht zu prüfen. Der
Algorithmus um die RICH-Signatur auf Gültigkeit zu testen wurde ebenfalls vorgestellt. Die
Standardwerte für Header können als einfache Bedingungen im Code definiert werden.
30
Fazit
Fazit
Übersetzt man ein Programm mit mehreren Compilern ist das Resultat (im Normalfall) in der
Funktionsweise identisch. Die erzeugten Binaries unterscheiden sich jedoch erheblich. So bieten sich
viele Möglichkeiten, durch Betrachten der erzeugten Binaries, Rückschlüsse auf den verwendeten
Compiler zu ziehen. Einige davon wurden in Rahmen dieser Arbeit analysiert und als Compiler
Erkennung in Form einer Library umgesetzt.
Als hauptsächliches Erkennungsverfahren wird jedoch eine schon bekannte Technik eingesetzt.
Die Signatur über den Code am Entry Point ist einfach umzusetzen und funktioniert zuverlässig über
eine Großzahl der bekannte Compiler. Das bestätigt auch Chris Eagle in [eagl08]:
“It turns out that the entry point code generated by various compilers is sufficiently
different that matching entry point signatures is a useful technique for identifying the
compiler that may have been used to generate a given binary.”
Dieses Verfahren wurde für den Einsatz in der Avira Scan Engine optimiert. Um eine Anwendung auf
mögliche Manipulation zu überprüfen, werden einige der weiteren, in dieser Arbeit vorgestellten
Merkmale verglichen.
Eine noch zuverlässigere Manipulationserkennung wäre möglich, durch eine automatisierte
Analyse des Objektcodes. Muster in den verwendeten CPU Instruktionen können nicht nur
Rückschlüsse auf den verwendeten Compiler, sondern auch auf verwendete Optimierungsoptionen
und Build-parameter geben.
Auf Grund der Komplexität eines Compilers gibt es jedoch noch viele weitere Merkmale. Diese
können in Zukunft untersucht und in die CDL integriert werden. Ebenso denkbar wäre eine
Erweiterung der CDL, um ELF1 und andere Programmformate verstehen zu können. Die Anzahl der
verschiedenen Compiler und Compiler-Versionen wird künftig weiterhin wachsen und somit die
Artenvielfalt der Erkennungsmuster steigern.
1. Executable and Linking Format, Binärformat für Programme unter Linux und FreeBSD
31
Entwicklung einer Bibliothek zur Compiler Erkennung
Literaturverzeichnis
[coff08]
Microsoft Portable Executable and Common Object File Format Specification.
Revision 8.1 Microsoft Corporation, 2008.
- http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx
[eagl08]
Eagle, Chris: The IDA Pro Book. No Starch Press, 2008.
- ISBN 978-1593271787
[gedd03]
Geddon, Andrea: Visual Basic Reversed - A decompiling approach. 2003.
- http://reteam.org/papers/e46.pdf
[guil97]
Guilfanov, Ilfak: Fast Library Identification and Recognition Technology. 1997.
- http://www.hex-rays.com/idapro/flirt.htm
[ione04]
Ionescu, Alex: Visual Basic Image Internal Structure Format. 2004.
- www.alex-ionescu.com/vb.pdf
[levi99]
Levine, John R.: Linkers & Loaders. Morgan Kaufmann, 1999.
- ISBN 978-1558604964
[lidi06]
Lidin, Serge: Expert .NET 2.0 IL Assembler. Apress Academic, 2006.
- ISBN 978-1590596463
[piet94]
Pietrek, Matt: Peering Inside the PE: A Tour of the Win32 Portable Executable
File Format. 1994. - http://msdn.microsoft.com/en-us/library/ms809762.aspx
[pist08]
Pistelli, Daniel: Microsoft's Rich Signature (undocumented). 2008. -
http://www.ntcore.com/Files/richsign.htm
32
Anhang A: Strukturdefinitionen
Anhang A: Strukturdefinitionen
Die wichtigsten Strukturdefinitionen für das PE Format werden hier kurz zusammengefasst.
Entnommen aus winnt.h des Wine Projektes1. Copyright Alexandre Julliard zu den Bedingungen der
LGPL2.
Datei Header
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic;
/* 00: MZ Header signature */
WORD e_cblp;
/* 02: Bytes on last page of file */
WORD e_cp;
/* 04: Pages in file */
WORD e_crlc;
/* 06: Relocations */
WORD e_cparhdr;
/* 08: Size of header in paragraphs */
WORD e_minalloc;
/* 0a: Minimum extra paragraphs needed */
WORD e_maxalloc;
/* 0c: Maximum extra paragraphs needed */
WORD e_ss;
/* 0e: Initial (relative) SS value */
WORD e_sp;
/* 10: Initial SP value */
WORD e_csum;
/* 12: Checksum */
WORD e_ip;
/* 14: Initial IP value */
WORD e_cs;
/* 16: Initial (relative) CS value */
WORD e_lfarlc;
/* 18: File address of relocation table */
WORD e_ovno;
/* 1a: Overlay number */
WORD e_res[4];
/* 1c: Reserved words */
WORD e_oemid;
/* 24: OEM identifier (for e_oeminfo) */
WORD e_oeminfo;
/* 26: OEM information; e_oemid specific */
WORD e_res2[10];
/* 28: Reserved words */
DWORD e_lfanew;
/* 3c: Offset to extended header */
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
Listing 11: IMAGE_DOS_HEADER
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
Listing 12: IMAGE_FILE_HEADER
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
1. WineHQ: http://www.winehq.org/
2. GNU Lesser General Public License: http://www.gnu.org/licenses/lgpl-3.0.html
33
Entwicklung einer Bibliothek zur Compiler Erkennung
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
Listing 13: IMAGE_DATA_DIRECTORY
typedef struct _IMAGE_OPTIONAL_HEADER {
/* Standard fields */
WORD
BYTE
BYTE
DWORD
DWORD
DWORD
DWORD
DWORD
DWORD
Magic; /* 0x10b or 0x107 */
MajorLinkerVersion;
MinorLinkerVersion;
SizeOfCode;
SizeOfInitializedData;
SizeOfUninitializedData;
AddressOfEntryPoint;
BaseOfCode;
BaseOfData;
/* 0x00 */
/* 0x10 */
/* NT additional fields */
DWORD ImageBase;
DWORD SectionAlignment;
/* 0x20 */
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
/* 0x30 */
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
/* 0x40 */
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
/* 0x50 */
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; /* 0x60 */
/* 0xE0 */
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
Listing 14: IMAGE_OPTIONAL_HEADER32
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; /* "PE"\0\0 */
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
Listing 15: IMAGE_NT_HEADERS32
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
34
/* 0x00 */
/* 0x04 */
/* 0x18 */
Anhang A: Strukturdefinitionen
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
Listing 16: IMAGE_SECTION_HEADER
Import Tabelle
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD
Hint;
BYTE
Name[1];
} IMAGE_IMPORT_BY_NAME,*PIMAGE_IMPORT_BY_NAME;
Listing 17: IMAGE_IMPORT_BY_NAME
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString;
DWORD Function;
DWORD Ordinal;
DWORD AddressOfData;
} u1;
} IMAGE_THUNK_DATA32,*PIMAGE_THUNK_DATA32;
Listing 18: IMAGE_THUNK_DATA
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD
Characteristics;
/* 0 for terminating null import descriptor */
DWORD
OriginalFirstThunk;
/* RVA to original unbound IAT */
} DUMMYUNIONNAME;
DWORD
TimeDateStamp;
/* 0 if not bound,
* -1 if bound, and real date\time stamp
*
in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT
* (new BIND)
* otherwise date/time stamp of DLL bound to
* (Old BIND)
*/
DWORD
ForwarderChain;
/* -1 if no forwarders */
DWORD
Name;
/* RVA to IAT (if bound this IAT has actual addresses) */
DWORD
FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;
Listing 19: IMAGE_IMPORT_DESCRIPTOR
35
Entwicklung einer Bibliothek zur Compiler Erkennung
Anhang B: Header Werte im Vergleich
Im Rahmen dieser Arbeit wurden Test-Binaries erstellt und deren Header Werte in Abhängigkeit des
Compilers verglichen. Der Vollständigkeit halber werden einige hier aufgeführt.
Test
borland
gcc/cygwin
gcc/mingw
icl7 icl8
C_1
0
2126336
2128896
0
0
1884770
0
0
0
0
C_2
0
59392
61952
0
0
92258
0
0
0
0
C_3
0
3072
5632
0
0
44642
0
0
0
0
C_4
0
3072
5632
0
0
44642
0
0
0
0
C_5
0
3072
5632
0
0
44642
0
0
0
0
C_6
0
3072
5632
0
0
44642
0
0
0
0
C_7
0
3072
5632
0
0
44642
0
0
0
0
C_8
0
2048
5632
0
0
4362
0
0
0
0
C_9
0
2048
5632
0
0
4362
0
0
0
0
C_10
0
3584
5632
0
0
44642
0
0
0
0
DLL_1
0
4096
5632
0
0
4128
0
0
0
0
DLL_2
0
4096
5632
0
0
4128
0
0
0
0
CPP_1
0
276480
276480
0
0
0
0
0
0
CPP_2
0
276992
276480
0
0
0
0
0
0
CPP_3
0
276480
275968
0
0
0
0
0
0
CPP_4
0
276992
276480
0
0
0
0
0
0
CPP_5
0
281600
280576
0
0
0
0
0
0
CPP_6
0
291328
289280
0
0
0
0
0
0
Tabelle 6: Optional Header, PointerToSymbolTable
36
lcc
vs6 vs05 vs08 watcom
Anhang B: Header Werte im Vergleich
Test
borland
gcc/cygwin
gcc/mingw
icl7
icl8
lcc
vs6 vs05 vs08
watcom
C_1
0
50250
50461
0
0
51142
0
0
0
0
C_2
0
1577
1788
0
0
2476
0
0
0
0
C_3
0
265
475
0
0
1144
0
0
0
0
C_4
0
265
475
0
0
1185
0
0
0
0
C_5
0
250
460
0
0
1144
0
0
0
0
C_6
0
250
460
0
0
1144
0
0
0
0
C_7
0
250
460
0
0
1144
0
0
0
0
C_8
0
242
451
0
0
310
0
0
0
0
C_9
0
242
451
0
0
310
0
0
0
0
C_10
0
252
463
0
0
1146
0
0
0
0
DLL_1
0
224
321
0
0
252
0
0
0
0
DLL_2
0
224
321
0
0
252
0
0
0
0
CPP_1
0
4708
5265
0
0
0
0
0
0
CPP_2
0
4718
5276
0
0
0
0
0
0
CPP_3
0
4710
5267
0
0
0
0
0
0
CPP_4
0
4714
5272
0
0
0
0
0
0
CPP_5
0
4774
5332
0
0
0
0
0
0
CPP_6
0
5166
5732
0
0
0
0
0
0
Tabelle 7: Optional Header, NumberOfSymbols
37
Entwicklung einer Bibliothek zur Compiler Erkennung
project
borland gcc/cygwin gcc/mingw
icl7
icl8
lcc
vs6
vs05
vs08
watcom
C_1
1536
1024
1024
4096
4096
1024
4096
1024
1024
1024
C_2
1536
1024
1024
4096
4096
1024
4096
1024
1024
1024
C_3
1536
1024
1024
4096
4096
1024
4096
1024
1024
1024
C_4
1536
1024
1024
4096
4096
1024
4096
1024
1024
1024
C_5
1536
1024
1024
4096
4096
1024
4096
1024
1024
1024
C_6
1536
1024
1024
4096
4096
1024
4096
1024
1024
1024
C_7
1536
512
1024
4096
4096
1024
4096
1024
1024
1024
C_8
1536
512
1024
4096
4096
1024
4096
1024
1024
1024
C_9
1536
1024
1024
4096
4096
1024
4096
1024
1024
1024
C_10
1536
1024
1024
4096
4096
1024
4096
1024
1024
1024
CPP_1
1536
1024
1024
4096
4096
4096
1024
1024
1024
CPP_2
1536
1024
1024
4096
4096
4096
1024
1024
1024
CPP_3
1536
1024
1024
4096
4096
4096
1024
1024
1024
CPP_4
1536
1024
1024
4096
4096
4096
1024
1024
1024
CPP_5
1536
1024
1024
4096
4096
4096
1024
1024
1024
CPP_10
1536
1024
1024
4096
4096
4096
1024
1024
1024
Tabelle 8: Optional Header, SizeOfHeaders
38
Anhang B: Header Werte im Vergleich
project
borland gcc/cygwin gcc/mingw
icl7
icl8
lcc
vs6
vs05
vs08
watcom
C_1
1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 8192
C_2
1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 8192
C_3
1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 8192
C_4
1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 8192
C_5
1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 8192
C_6
1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 8192
C_7
1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 8192
C_8
1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 8192
C_9
1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 8192
C_10
1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 8192
CPP_1 1048576 1048576 1048576 1048576 1048576
1048576 1048576 1048576 8192
CPP_2 1048576 1048576 1048576 1048576 1048576
1048576 1048576 1048576 8192
CPP_3 1048576 1048576 1048576 1048576 1048576
1048576 1048576 1048576 8192
CPP_4 1048576 1048576 1048576 1048576 1048576
1048576 1048576 1048576 8192
CPP_5 1048576 1048576 1048576 1048576 1048576
1048576 1048576 1048576 8192
CPP_6 1048576 1048576 1048576 1048576 1048576
1048576 1048576 1048576 8192
Tabelle 9: Optional Header, SizeOfHeapReserve
39
Entwicklung einer Bibliothek zur Compiler Erkennung
project
borland
gcc/cygwin
gcc/mingw
icl7
icl8
lcc
vs6
vs05 vs08
watcom
C_1
5
2
2
6
6
2
6
8
9
2
C_2
5
2
2
6
6
2
6
8
9
2
C_3
5
2
2
6
6
2
6
8
9
2
C_4
5
2
2
6
6
2
6
8
9
2
C_5
5
2
2
6
6
2
6
8
9
2
C_6
5
2
2
6
6
2
6
8
9
2
C_7
5
2
2
6
6
2
6
8
9
2
C_8
5
2
2
6
6
2
6
8
9
2
C_9
5
2
2
6
6
2
6
8
9
2
C_10
5
2
2
6
6
2
6
8
9
2
DLL_1
5
2
2
6
6
2
6
8
9
2
DLL_2
5
2
2
6
6
2
6
8
9
2
CPP_1
5
2
2
6
6
6
8
9
2
CPP_2
5
2
2
6
6
6
8
9
2
CPP_3
5
2
2
6
6
6
8
9
2
CPP_4
5
2
2
6
6
6
8
9
2
CPP_5
5
2
2
6
6
6
8
9
2
CPP_6
5
2
2
6
6
6
8
9
2
Tabelle 10: Optional Header, MajorLinkerVersion
40
Anhang B: Header Werte im Vergleich
project
borland
gcc/cygwin
gcc/mingw
icl7
icl8
lcc
vs6
vs05 vs08
watcom
C_1
0
56
56
0
0
55
0
0
0
18
C_2
0
56
56
0
0
55
0
0
0
18
C_3
0
56
56
0
0
55
0
0
0
18
C_4
0
56
56
0
0
55
0
0
0
18
C_5
0
56
56
0
0
55
0
0
0
18
C_6
0
56
56
0
0
55
0
0
0
18
C_7
0
56
56
0
0
55
0
0
0
18
C_8
0
56
56
0
0
55
0
0
0
18
C_9
0
56
56
0
0
55
0
0
0
18
C_10
0
56
56
0
0
55
0
0
0
18
DLL_1
0
56
56
0
0
55
0
0
0
18
DLL_2
0
56
56
0
0
0
0
0
18
CPP_1
0
56
56
0
0
0
0
0
18
CPP_2
0
56
56
0
0
0
0
0
18
CPP_3
0
56
56
0
0
0
0
0
18
CPP_4
0
56
56
0
0
0
0
0
18
CPP_5
0
56
56
0
0
0
0
0
18
CPP_6
0
56
56
0
0
0
0
0
18
Tabelle 11: Optional Header, MinorLinkerVersion
41