Derzeit gibt es zwei Hauptansätze für die Suche nach Schwachstellen in Anwendungen - statische und dynamische Analyse. Beide Ansätze haben ihre Vor- und Nachteile. Der Markt kommt zu dem Schluss, dass beide Ansätze angewendet werden müssen - sie lösen leicht unterschiedliche Probleme mit unterschiedlichen Ergebnissen. In einigen Fällen ist die Verwendung der statischen Analyse jedoch eingeschränkt - beispielsweise wenn kein Quellcode vorhanden ist. In diesem Artikel werden wir über eine eher seltene, aber sehr nützliche Technologie sprechen, mit der Sie die Vorteile statischer und dynamischer Ansätze kombinieren können - die statische Analyse von ausführbarem Code.
Lass uns von weitem gehenLaut McAfee Antivirus-Unternehmen belief sich der weltweite Schaden durch Cyberkriminalität im Jahr 2017 auf rund 600 Milliarden US-Dollar, was 0,8% des globalen BIP entspricht. Wir leben im Zeitalter der Informationstechnologie, deren Besonderheiten die rasche Integration des globalen Netzwerks und der Internet-Technologien in alle Bereiche menschlicher Aktivitäten waren. Jetzt sind Cyberkriminalität nicht mehr ungewöhnlich.
Statistiken zeigen einen exponentiellen Anstieg der Internetkriminalität.
Die Sicherheitsanfälligkeit von Anwendungen hat sich zu einem ernsthaften Problem entwickelt: Laut dem US-amerikanischen Heimatschutzministerium werden mehr als 90% der erfolgreichen Cyber-Angriffe mithilfe verschiedener Sicherheitsanfälligkeiten in Anwendungen implementiert. Die bekanntesten Methoden zur Ausnutzung von Sicherheitslücken sind:
- SQL-Injection
- Pufferüberlauf
- Crossite-Scripting
- Verwenden einer unsicheren Konfiguration.
Die Analyse von Software (Software) auf nicht deklarierte Funktionen (NDV) und Schwachstellen ist die Haupttechnologie zur Gewährleistung der Anwendungssicherheit.
Wenn wir von klassischen und etablierten Technologien zur Analyse von Software auf Schwachstellen und NDV (zur Einhaltung der Anforderungen an die Informationssicherheit) sprechen, können wir unterscheiden:
- statische Code-Analyse (Static Application Security Testing);
- Dynamische Code-Analyse (Dynamic Application Security Testing).
Es gibt IAST (interaktive Analyse), diese ist jedoch im Wesentlichen dynamisch (während des Analyseprozesses beobachtet ein zusätzlicher Agent, was während der Anwendungsausführung geschieht). RASP (Runtime Application Self-Defense), das manchmal auch in einer Reihe von Analysewerkzeugen erwähnt wird, ist eher ein Schutzwerkzeug.
Die dynamische Analyse (die "Black Box" -Methode) ist eine Programmprüfung während ihrer Ausführung. Die folgenden Vorteile können von diesem Ansatz unterschieden werden.
- Da sich Schwachstellen im ausführbaren Programm befinden und der Fehler anhand seiner Operation erkannt wird, ist die Erzeugung von Fehlalarmen geringer als die der statischen Analyse.
- Für die Analyse wird kein Quellcode benötigt.
Es gibt aber auch Nachteile.
- Unvollständige Abdeckung des Codes, und daher besteht das Risiko, dass Sicherheitslücken fehlen. Beispielsweise kann die dynamische Analyse keine Schwachstellen finden, die mit der Verwendung schwacher Kryptografie oder Lesezeichen wie "temporäre Bombe" verbunden sind.
- Die Notwendigkeit, die Anwendung auszuführen, was in einigen Fällen schwierig sein kann. Das Starten der Anwendung erfordert möglicherweise eine komplexe Konfiguration und Konfiguration verschiedener Integrationen. Um die Ergebnisse so genau wie möglich zu machen, ist es außerdem erforderlich, die „Kampfumgebung“ zu reproduzieren. Es ist jedoch schwierig, dies vollständig zu realisieren, ohne die Software zu beschädigen.
Die statische Analyse (die „White Box“ -Methode) ist eine Art von Programmtest, bei dem das Programm nicht ausgeführt wird.
Wir listen die Vorteile auf.
- Vollständige Abdeckung des Codes, wodurch nach weiteren Sicherheitslücken gesucht wird.
- Keine Abhängigkeit von der Umgebung, in der das Programm ausgeführt wird.
- Die Möglichkeit, Tests in der Anfangsphase des Schreibens von Code für ein Modul oder Programm zu implementieren, wenn keine ausführbaren Dateien vorhanden sind. Auf diese Weise können Sie eine ähnliche Lösung bereits zu Beginn der Entwicklung flexibel in den SDLC (Software Development Life Cycle, Software Development Life Cycle) integrieren.
Der einzige Nachteil der Methode ist das Vorhandensein von falsch positiven Ergebnissen: die Notwendigkeit zu bewerten, ob der Analysator einen echten Fehler anzeigt oder ob es wahrscheinlich ist, dass dieses falsch positiv ist.
Wie wir sehen können, haben beide Analysemethoden sowohl Vor- als auch Nachteile. Ist es jedoch in irgendeiner Weise möglich, die Vorteile dieser Methoden zu nutzen und gleichzeitig die Nachteile zu minimieren? Ja, wenn Sie eine binäre Analyse anwenden - die Suche nach Schwachstellen in ausführbaren Dateien durch statische Analyse.
Binäre Analyse oder ausführbare Dateianalysetechnologie
Die binäre Analyse ermöglicht eine statische Analyse ohne Quellcode, beispielsweise bei Drittanbietern. Darüber hinaus ist die Codeabdeckung im Gegensatz zur Anwendung der dynamischen Analysemethode vollständig. Mithilfe der Binäranalyse können Sie die im Entwicklungsprozess verwendeten Bibliotheken von Drittanbietern überprüfen, für die kein Quellcode vorhanden ist. Mithilfe der binären Analyse können Sie auch eine Kontrollprüfung der Version durchführen und die Ergebnisse der Analyse des Quellcodes aus dem Repository und des ausführbaren Codes aus dem Kampfserver vergleichen.
Bei der binären Analyse wird das Binärbild zur weiteren Analyse in eine Zwischendarstellung (interne Darstellung oder Codemodell) umgewandelt. Danach werden statische Analysealgorithmen auf die interne Darstellung angewendet. Infolgedessen wird das aktuelle Modell mit den Informationen ergänzt, die für die weitere Erkennung von Schwachstellen und NDV erforderlich sind. In der nächsten Phase die Anwendung der Regeln für die Suche nach Schwachstellen und NDV.
Wir haben
in einem früheren Artikel mehr über das statische Analyseschema geschrieben. Im Gegensatz zur Quellcode-Analyse, bei der kompilierungstheoretische Elemente (lexikalische, syntaktische Analyse) zum Erstellen des Modells verwendet werden, verwendet die binäre Analyse die umgekehrte Übersetzungstheorie, um das Modell zu disassemblieren, zu dekompilieren und zu deobfuscieren.
Ein bisschen über die Begriffe
Wir sprechen über die Analyse ausführbarer Dateien, die keine Debug-Informationen enthalten. Mit Debug-Informationen wird die Aufgabe erheblich vereinfacht. Wenn jedoch Debug-Informationen vorhanden sind, ist der Quellcode höchstwahrscheinlich und die Aufgabe wird irrelevant.
In diesem Artikel nennen wir Java-Bytecode-Analyse auch Binäranalyse, obwohl dies nicht ganz korrekt ist. Wir tun dies, um den Text zu vereinfachen. Natürlich ist die Analyse des JVM-Bytecodes einfacher als die Analyse des binären C / C ++ - Codes und von Objective-C / Swift. Das allgemeine Analyseschema ist jedoch bei Bytecode und Binärcode ähnlich. Die im Artikel beschriebenen Hauptschwierigkeiten beziehen sich speziell auf die Analyse von Binärcode.
Bei der Dekompilierung wird der Quellcode aus dem Binärcode wiederhergestellt. Sie können über die Elemente der umgekehrten Übersetzung sprechen - Zerlegen (Assembler-Code aus einem Binärbild erhalten), Übersetzen des Assemblers in einen Code mit drei Adressen oder eine andere Darstellung, Wiederherstellen von Konstruktionen der Quellcode-Ebene.
Verschleierung - Transformationen, die die Funktionalität des Quellcodes beibehalten, es jedoch schwierig machen, das resultierende Binärbild zu dekompilieren und zu verstehen. Deobfuscation ist die inverse Transformation. Die Verschleierung kann sowohl auf Quellcodeebene als auch auf Binärcodeebene angewendet werden.
Wie kann man die Ergebnisse sehen?
Beginnen wir ein wenig am Ende, aber die Frage nach der Anzeige der Ergebnisse der binären Analyse wird normalerweise zuerst gestellt.
Für einen Spezialisten, der Binärcode analysiert, ist es wichtig, Schwachstellen und NDV dem Quellcode zuzuordnen. Zu diesem Zweck wird im Endstadium der Prozess der Deobfuscation (Enträtselung) gestartet, wenn verwirrende Konvertierungen angewendet wurden und der Binärcode in die Quelle dekompiliert wurde. Das heißt, Schwachstellen können in dekompiliertem Code nachgewiesen werden.
Selbst wenn wir den JVM-Bytecode dekompilieren, werden beim Dekompilieren einige Informationen nicht korrekt wiederhergestellt, sodass die Analyse selbst in einer Darstellung in der Nähe des Binärcodes erfolgt. Dementsprechend stellt sich die Frage: Wie können Schwachstellen im Binärcode lokalisiert und in der Quelle lokalisiert werden? Die Lösung des Problems für den JVM-Bytecode wurde
in unserem Artikel über die Suche nach Schwachstellen im Java-Bytecode beschrieben . Die Lösung für Binärcode ist ähnlich, dh eine technische Frage.
Lassen Sie uns die wichtige Einschränkung wiederholen - wir sprechen über die Analyse von Binärcode ohne Debug-Informationen. Bei Vorhandensein von Debug-Informationen wird die Aufgabe erheblich vereinfacht.
Die Hauptfrage zur Anzeige der Ergebnisse ist, ob der dekompilierte Code ausreicht, um die Sicherheitsanfälligkeit zu verstehen und zu lokalisieren.
Nachfolgend einige Gedanken zu diesem Thema.
- Wenn wir über den JVM-Bytecode sprechen, lautet die Antwort im Allgemeinen "Ja" - die Dekompilierungsqualität für den Bytecode ist großartig. Fast immer können Sie herausfinden, um welche Sicherheitsanfälligkeit es sich handelt.
- Was die qualitative Lokalisierung der Sicherheitsanfälligkeit beeinträchtigen kann, ist eine einfache Verschleierung wie das Umbenennen von Klassennamen und Funktionen. In der Praxis stellt sich jedoch häufig heraus, dass es wichtiger ist, die Sicherheitsanfälligkeit zu verstehen, als festzustellen, in welcher Datei sie sich befindet. Eine Lokalisierung ist erforderlich, wenn jemand die Sicherheitsanfälligkeit beheben kann. In diesem Fall versteht der Entwickler jedoch auch, wo sich die Sicherheitsanfälligkeit aus dem dekompilierten Code befindet.
- Wenn wir über die Analyse von Binärcode (zum Beispiel C ++) sprechen, ist natürlich alles viel komplizierter. Es gibt kein Tool, das zufälligen C ++ - Code vollständig wiederherstellt. Die Besonderheit unseres Falles ist jedoch, dass wir den Code später nicht kompilieren müssen: Wir benötigen eine ausreichende Qualität, um die Sicherheitsanfälligkeit zu verstehen.
- In den meisten Fällen können Sie eine Dekompilierungsqualität erzielen, die ausreicht, um die gefundene Sicherheitsanfälligkeit zu verstehen. Um dies zu tun, müssen Sie viele schwierige Probleme lösen, aber Sie können sie lösen (im Folgenden werden wir kurz darauf eingehen).
- Für C / C ++ ist es noch schwieriger, die Sicherheitsanfälligkeit zu lokalisieren. Die Namen der Zeichen gehen während des Kompilierungsprozesses auf vielfältige Weise verloren. Sie können sie nicht wiederherstellen.
- Die Situation in Objective-C ist etwas besser - es gibt dort Funktionsnamen und es ist einfacher, die Sicherheitsanfälligkeit zu lokalisieren.
- Die Fragen der Verschleierung stehen auseinander. Es gibt eine Reihe komplexer Transformationen, die die Dekompilierung und Zuordnung von Schwachstellen erschweren können. In der Praxis stellt sich heraus, dass ein guter Dekompiler die meisten verwirrenden Konvertierungen verarbeiten kann (denken Sie daran, dass wir genügend Codequalität benötigen, um die Sicherheitsanfälligkeit zu verstehen).
Als Schlussfolgerung - meistens stellt sich heraus, dass die Sicherheitsanfälligkeit angezeigt wird, damit sie verstanden und überprüft werden kann.
Komplexität und Details der binären Analyse
Hier werden wir nicht über den Bytecode sprechen: Alle interessanten Dinge darüber wurden bereits oben gesagt. Das Interessanteste ist die Analyse von echtem Binärcode. Hier werden wir als Beispiel über die Analyse von C / C ++, Objective-C und Swift sprechen.
Auch beim Zerlegen treten erhebliche Schwierigkeiten auf. Die wichtigste Stufe ist die Aufteilung des Binärbildes in Unterprogramme. Wählen Sie als Nächstes die Assembler-Anweisungen in den Unterprogrammen aus - eine technische Angelegenheit. Wir haben darüber ausführlich
in einem Artikel für die Zeitschrift „Issues of Cybersecurity No. 1 (14) - 2016“ geschrieben , den wir hier kurz beschreiben werden.
Als Beispiel werden wir über die x86-Architektur sprechen. Die darin enthaltenen Anweisungen haben keine feste Länge. In Binärbildern gibt es keine klare Unterteilung in Code- und Datenabschnitte: Importtabellen, virtuelle Funktionstabellen können sich im Codeabschnitt befinden, Übergangstabellen können sich in den Intervallen zwischen den Basisfunktionsblöcken im Codeabschnitt befinden. Dementsprechend müssen Sie in der Lage sein, den Code von den Daten zu trennen und zu verstehen, wo die Routinen beginnen und wo die Routinen enden.
Am gebräuchlichsten sind zwei Methoden zur Lösung des Problems der Ermittlung der Startadressen von Unterprogrammen. Bei der ersten Methode werden die Adressen der Unterprogramme durch das Standardprolog bestimmt (für die x86-Architektur ist es push ebp; mov ebp, esp). Bei der zweiten Methode wird ein Codeabschnitt rekursiv vom Einstiegspunkt mit Erkennung von Unterprogrammaufrufanweisungen durchlaufen. Das Umgehen erfolgt durch Erkennen von Verzweigungsanweisungen. Kombinationen der beschriebenen Methoden werden auch verwendet, wenn eine rekursive Durchquerung von den vom Prolog gefundenen Startadressen gestartet wird.
In der Praxis stellt sich heraus, dass solche Ansätze einen relativ geringen Prozentsatz des erkannten Codes ergeben, da nicht alle Funktionen einen Standardprolog haben und es indirekte Aufrufe und Übergänge gibt.
Grundlegende Algorithmen können durch die folgenden Heuristiken verbessert werden.
- Suchen Sie auf einer großen Testbasis von Bildern eine genauere Liste von Prologen (neue Prologe oder Variationen von Standardprologen).
- Sie können automatisch Tabellen virtueller Funktionen finden und daraus die Startadressen von Unterprogrammen abrufen.
- Startadressen von Unterprogrammen und einigen anderen Konstruktionen können auf der Grundlage von Abschnitten des Binärcodes gefunden werden, die dem Ausnahmebehandlungsmechanismus zugeordnet sind.
- Sie können Startadressen überprüfen, indem Sie im Bild nach diesen Adressen suchen und Anrufanweisungen erkennen.
- Um nach Grenzen zu suchen, können Sie eine rekursive Durchquerung des Unterprogramms mit Erkennung von Anweisungen von der Startadresse aus durchführen. Es gibt Schwierigkeiten mit indirekten Übergängen und No-Return-Funktionen. Die Analyse der Importtabelle und die Erkennung von Switch-Konstrukten können helfen.
Eine weitere wichtige Sache, die während der umgekehrten Übersetzung ausgeführt werden muss, um später normalerweise nach einer Sicherheitsanfälligkeit zu suchen, ist das Erkennen von Standardfunktionen in einem Binärbild. Standardfunktionen können statisch mit dem Bild verknüpft oder sogar inline sein. Der Haupterkennungsalgorithmus ist eine Suche nach Signatur mit Variationen. Für die Lösung können Sie den angepassten Aho-Korasik-Algorithmus anbieten. Um Signaturen zu sammeln, müssen Sie die unter verschiedenen Bedingungen gesammelten Bibliotheksbilder vorab analysieren und als unveränderliche Bytes auswählen.
Was weiter
Im vorherigen Abschnitt haben wir die Anfangsphase der umgekehrten Übersetzung einer binären Bildzerlegung untersucht. Das Stadium ist zwar initial, aber bestimmend. In diesem Stadium können Sie einen Teil des Codes verlieren, was sich dramatisch auf die Analyseergebnisse auswirkt.
Dann passieren viele interessante Dinge. Sagen Sie kurz über die Hauptaufgaben. Wir werden nicht im Detail sprechen: Entweder das Know-how, über das wir hier nicht explizit schreiben können, oder nicht sehr interessante technische und technische Lösungen stecken im Detail.
- Konvertieren von Assemblycode in eine Zwischendarstellung, für die eine Analyse durchgeführt werden kann. Sie können verschiedene Bytecodes verwenden. Für C-Sprachen scheint LLVM eine gute Wahl zu sein. LLVM wird von der Community aktiv unterstützt und weiterentwickelt. Die Infrastruktur, die auch für statische Analysen nützlich ist, ist derzeit beeindruckend. In dieser Phase gibt es eine Vielzahl von Details, auf die Sie achten müssen. Beispielsweise müssen Sie erkennen, welche Variablen auf dem Stapel adressiert sind, um Entitäten in der resultierenden Ansicht nicht zu multiplizieren. Sie müssen die optimale Anzeige von Assembler-Befehlssätzen in Bytecode-Befehlen konfigurieren.
- Stellen Sie Strukturen auf hoher Ebene wieder her (z. B. Schleifen, Zweige). Je genauer es möglich ist, die ursprünglichen Konstruktionen aus dem Assembler-Code wiederherzustellen, desto besser ist die Qualität der Analyse. Die Wiederherstellung solcher Konstruktionen erfolgt unter Verwendung von Elementen der Graphentheorie auf CFG (Kontrollflussgraph) und einigen anderen grafischen Darstellungen des Programms.
- Durchführung statischer Analysealgorithmen. Es gibt Details. Im Allgemeinen ist es nicht sehr wichtig, ob wir die interne Darstellung von der Quelle oder von der Binärdatei erhalten haben - wir alle müssen auch CFG erstellen, Datenflussanalysealgorithmen und andere für die Statik typische Algorithmen anwenden. Bei der Analyse der aus der Binärdatei erhaltenen Ansicht gibt es einige Funktionen, die jedoch technischer sind.
Schlussfolgerungen
Wir haben darüber gesprochen, wie Sie statische Analysen durchführen können, wenn kein Quellcode vorhanden ist. Aus der Erfahrung der Kommunikation mit Kunden geht hervor, dass die Technologie sehr gefragt ist. Die Technologie ist jedoch selten: Das Problem der binären Analyse ist nicht trivial, seine Lösung erfordert komplexe High-Tech-Algorithmen für statische Analyse und umgekehrte Übersetzung.
Dieser Artikel wurde in Zusammenarbeit mit Anton Prokofiev, Solar AppScreener-Analyst, verfasst