•  

Fehlersuche in Assembler - Statische Analyse von Binärcode

In den meisten Anwendungen wird Code von Drittherstellern eingesetzt. Das erleichtert zwar die Arbeit der Entwickler-Teams, birgt aber auch Risiken. Denn Fehler oder Sicherheitslücken in diesen Komponenten fallen auf den Anwendungsanbieter zurück. Deshalb sollten auch diese Software-Komponenten bei der Qualitätssicherung genau unter die Lupe genommen werden. Bei Binärcode ist das allerdings nicht so einfach, denn dieser kann meist nur disassembliert werden.

In der Anwendungsentwicklung wird in allen Bereichen auf Code von Drittherstellern zurück-gegriffen. Der Embedded-Bereich macht dabei keine Ausnahme. Datenbanken, Windowing-Toolkits oder Verschlüsselungstechnologien sind in vielfältiger Art von Spezialisten verfügbar, die eigenen Entwicklungsteams können sich durch eine entsprechende Sourcing-Strategie auf die Kernfunktionen der Anwendungen konzentrieren. Vor allem bei Ansätzen wie Continuous Deployment/Continuous Integration mit ihren kurzen Release-Zyklen ist der Einsatz von „Third-Party Code“ fast unumgänglich. Marktbeobachter wie etwa VDC Research schätzen, dass im Embedded-Bereich mittlerweile über 30 Prozent des Codes aus externen Quellen stammt. Mit dieser - um die Sprache der Industrie zu benutzen - geringeren Fertigungstiefe in der Software-Entwicklung einher geht jedoch auch ein Kontrollverlust: Häufig liegt der Fremd-Code nicht als Quellen vor, sondern in binärer Form. Damit ist es schwierig, diese Komponenten auf Fehlerfreiheit und vor allem auf mögliche sicherheitsrelevante Schwachstellen hin zu überprüfen.

Code nicht ausführen, sondern analysieren

Meist bleibt nur das Testing mit definierten Szenarien, um die integrierten Binärcodes zu überprüfen. Dieser Ansatz hat jedoch einen Nachteil: Es werden nur Fehler gefunden, die in den Testfällen auch wirklich auftreten. Potenzielle weitere Schwachstellen bleiben unentdeckt. Hier kann die statische Analyse Abhilfe schaffen. Bei der statischen Analyse wird die Software nicht ausgeführt, sondern ein Modell erzeugt, das geprüft werden kann. Und damit auch mögliche Error Conditions, die in Test-Szenarien in der Regel nicht auftreten, aber dennoch später für Probleme sorgen können. Das Verfahren ist eigentlich für die Untersuchung von Quellcode gedacht. Dieser enthält Informationen, mit denen das Analyse-Tool Daten- und Steuerungsströme erkennen kann. Bei Binärcode ist die Situation grundsätzlich anders.

Der Binärcode von ausführbaren Dateien und Bibliotheken kann auf zwei Arten vorliegen: Er kann Symboltabellen und Debugging-Informationen enthalten (unstripped) oder nicht (stripped). Üblicherweise sind die Dateien gestrippt, der Compiler entfernt standardmäßig alle Informationen, die für die korrekte Ausführung des Programms nicht benötigt werden. Der Sinn dieser Maßnahme ist es, die Größe der EXE-Datei zu minimieren und die Ausführungsgeschwindigkeit zu maximieren. Um die Debugging-Informationen auch in der ausführbaren Datei verfügbar zu halten, muss dies bei den meisten Compilern extra angegeben werden. Beim weit verbreiteten Compiler gcc zum Beispiel geschieht das durch setzen des Flags -g. 

Assembler-Code als Basis

Der erste Schritt der statischen Analyse ist das Disassemblieren des Binärcodes. Das Ergebnis dieses Schritts ist Assembler-Code, der nur theoretisch und mit immensem Aufwand von einem Entwickler gelesen werden kann. Bei gestrippten Dateien enthält der Code zudem zu wenig Informationen, um in seinen Steuerungs- und Datenflüssen nachvollziehbar zu sein. Hier sind Tools wie CodeSonar von GrammaTech unverzichtbar, die das Disassemblieren und Analysieren des Binärcodes anhand des daraus erzeugten Modells übernehmen. Dabei gibt das Tool dem Entwickler zahlreiche Hilfen, um gefundene Fehler genau zu lokalisieren und zu verstehen. Dennoch bleibt es Assembler-Code, der Entwickler sollte also mit dieser Hardware-nahen Programmiersprache vertraut sein. 

Werden potenzielle Fehler gefunden, zeigt CodeSonar eine Warnung mit weiterführenden Hinweisen an. Im untenstehenden Screenshot entdeckte das Tool einen Buffer Overflow im Programm gnuchess. Alle relevanten Codebestandteile werden dabei automatisch markiert, eine detaillierte Meldung hilft bei der Fehlerbeseitigung. Wichtig sind diese Informationen nicht zuletzt deshalb, weil der erzeugte Assembler-Code nicht nur kaum lesbar, sondern auch völlig anders aufgebaut ist als ein Quellcode in einer modernen Programmiersprache: Binärcode kennt keine strukturierten Funktionen; somit lassen sich diese Hilfskonstrukte auch nicht beim Disassemblieren erzeugen. Man sieht nur, dass zum Beispiel die Funktion return_append_str ihren Platz in der Sektion _text hat und an die Adresse 0x419350 geladen wird. Eine manuelle Überprüfung dieses Codes wäre hier nicht nur sehr mühsam, sondern auch extrem fehleranfällig. Um die Fehlerbereinigung durch die Entwickler zu bereinigen, verfügt CodeSonar in der neuesten Version deswegen über einen Decompiler, der den Assembler-Code in C übersetzt.

 

Binär -und Quellcode als Einheit

Für die Analyse der Steuerungs- und Datenflüsse innerhalb der vollständigen Anwendung ist es hilfreich, die als Binär- und Source-Code vorliegenden Bestandteile nicht einzeln zu betrachten, sondern in ihrer Gesamtheit. Ein entsprechendes Tool erzeugt dazu ein Modell des gesamten Programms mit allen Source- und Binary-Bestandteilen: Was als Quellcode vorliegt, wird geparst und Binär-Code wird disassembliert. Das Snalyse-Tool erzeugt daraus eine einheitliche Präsentation, die die Semantik beider Teile konsistent abbildet. Um mögliche Fehler zu erkennen, durchläuft das Werkzeug das Modell interprozedural und sucht nach Anomalien. 

Zu den Fehlern, die im Fokus der Analyse stehen, gehören unter anderem die klassischen Einfallstore für Malware:

• Buffer Overrun/Underrun

• Command Injection

• Integer Overflow of Allocation Size

• SQL Injection

• Non-constant Format String

Dabei kann es sich durchaus als Vorteil herausstellen, wenn Bestandteile der Anwendung nur binär vorliegen. Denn im Gegensatz zur Untersuchung von strukturiertem Quellcode erzeugt die Analyse der Binaries auch Informationen, die sich aus den Quellen nicht ableiten lassen: Bei der Analyse des Binärcodes wird das Programm so betrachtet, wie es nachher wirklich in den Speicher geladen und ausgeführt wird. Dadurch lassen sich auch mögliche Probleme aufdecken, die durch die Tool-Kette mit Compiler und Linker entstehen können. Diese bleiben bei einer reinen Quellcode-Analyse – und auf auch beim dynamischen Testing – unerkannt.

Sichere Software, schneller Entwicklung

Der Einsatz von statischer Analyse bei Binär-Code hilft also dabei, eine hohe Software-Qualität auch dann sicherzustellen, wenn Komponenten Dritter in die Anwendung mit einfließen. Der Ansatz der statischen Analyse hat dabei einige Vorteile gegenüber anderen Methoden: Bei der statischen Analyse wird der Code nicht ausgeführt. Das Analyse-Tool ermittelt alle möglichen Zustände, die das Programm einnehmen kann. So können auch Fehler gefunden werden, die in definierten Test-Szenarien nicht auftreten. Durch die Möglichkeit, die Analyse zu automatisieren, skaliert das bei CodeSonar implementierte Verfahren auch für große und komplexe Software-Projekte. Für die Entwickler und für das Unternehmen bedeutet das: Sichere Software bei einer kürzeren Time to Market. Und gleichzeitig muss den externen Herstellern nicht mehr blind vertraut werden. Denn Vertrauen ist gut, Code-Analyse besser.

Ein erheblicher Teil des bei Embedded-Systemen verwendeten Codes stammt aus externen Quellen.

https://www.grammatech.com/ 

Autor: Mark Hermeling, Senior Director Product Marketing bei GrammaTech, Inc.

Diesen Artikel empfehlen