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