Software Reverse Engineering

Der Softwaremarkt ist in vielen Bereichen hart umkämpft. Es existieren viele Mitbewerber und unzählig viele Produkte in den verschiedensten Bereichen. Um sich als Firma durchzusetzen und einen möglichst hohen Marktanteil zu ergattern, muss sich eine Software in der Regel von den Konkurrenzprodukten absetzen. Ich denke dabei an Innovationen: Besondere Funktionalität und sehr ausgeklügelte Lösungsansätze, aber auch an Performance-Erwartungen.

Um diesen Vorsprung zu erreichen, muss ein Unternehmen viel investieren. Es ist notwendig Fachleute anzustellen und die Entwicklung und Umsetzung des Konzepts benötigt mehr oder weniger Zusatzaufwand. Es müssen Tests gefahren werden, die den ordentlichen Betrieb dieser Neuerung gewährleisten. All dies führt zu Kosten, die dann (so die Erwartung) durch den besseren Absatz gedeckt werden können.

Je mehr Entwicklungsaufwand betrieben wird und je mehr es beim Vertrieb auf diese Innovationen ankommt, desto mehr sollte man sich die Frage stellen, wie diese Software zu schützen ist. Dieser Blogeintrag soll einen Denkanstoß in diese Richtung geben. Dabei geht es nicht um einen Kopierschutz, sondern um die Gefahren und den Schutz vor ungewollten Einblicken durch die Konkurrenz mittels Software Reverse Engineering.

Was ist Software Reverse Engineering?

Im Allgemeinen versteht man unter Software Reverse Engineering (kurz: SRE) den Vorgang, mit dem man bestehende Software wieder lesbar macht bzw. versucht zu verstehen, was die Software im Detail durchführt. Gängige Software besteht meistens aus Binärdaten, nur selten sind es frei einsehbare Skripte, wie z.B. bei Webanwendungen. Diese Binärdaten sind für uns mehr oder weniger lesbar. In der Regel steigt die Komplexität, je hardwarenäher die Programmiersprache ist.

Dieser Bytecode enthält nach einer bestimmten Kodiervorschrift alle Befehle des Programms. Das Ziel des Angreifers ist es, diese Codes zu verstehen und den Ablauf evtl. in sein eigenes Programm einzubauen. Ebenfalls wäre denkbar, dass ein Angreifer einen eigenen Code in das Programm einfügt.

Motive für Software Reverse Engineering

Zugegeben, diese Einleitung hat das SRE in einem schlechten Licht erscheinen lassen. Wenn man sich über die Motive für diese Analyse Gedanken macht, dann kommt man auch auf positive Beweggründe.

Positive Motive:

  • In einer Software treten Produktfehler auf und die Herstellerfirma gibt es nicht mehr. (z.B. wegen Insolvenz etc.). Es können daher keine Reparaturen in Form von Updates durchgeführt werden und es ist auch sonst keinerlei Aussicht auf eine Art von Wartung gegeben. Dadurch ist man gezwungen, das Problem selbst zu beheben.
  • Setzt man das Programm in einem komplexen System ein, kann eine Analyse für eine bessere Kompatibilität mit den verbundenen Komponenten sorgen, da man nach den Fehlerursachen forschen kann.
  • Durch die Analyse einer verdächtigen Software kann bösartiger Programmcode entdeckt und evtl. beseitigt werden.
  • Trat bereits der Fall ein, dass ein Konkurrent das patentierte Verfahren kopiert hat, kann über SRE ein Nachweis von Patentverletzungen oder Plagiaten erfolgen.

Negative Motive:

  • Besitzt eine Software eine bestimmte Art von Kopierschutz (z.B. eine Produktaktivierung, Seriennummerncheck), dann lässt sich durch SRE die Funktion zur Prüfung der Lizenz analysieren und eventuell mit anderen Maßnahmen austricksen. Denkbar wäre es z.B. den Algorithmus des Seriennummernchecks nachzubilden, um eigene Seriennummern zu erstellen. Oder durch Einschleusen von Code die Prüfroutine zu umgehen.
  • Das Programm kann vollständig dekompiliert werden. Anschließend werden eigene Routinen eingebracht, die Oberfläche mit dem eigenen Logo ausgestattet und alles neu gebaut. Es entsteht daraus ein neues Produkt, welches eine Person unter eigenem Namen verkaufen könnte.
  • Durch SRE können Konkurrenten die Algorithmen und verwendeten Techniken im Programm analysieren und in das eigene Produkt einbauen. Ein teurer Wissensvorsprung ist somit schnell und kostengünstig in die Hände des Mitbewerbers gefallen.
  • Ist die Software in einem sicherheitskritischen Bereich im Einsatz, so können durch Dekompilierung und Analyse Schwachstellen und Angriffspunkte leichter ausfindig gemacht werden.

Rechtliche Aspekte

Generell sollte man sich gut absichern und juristisch beraten lassen, sobald man ein Reverse Engineering durchführt. Nicht selten werden solche Maßnahmen in den Lizenzbedingungen untersagt. Die Legalität von SRE hängt von vielen Faktoren ab. Dazu gehören bspw. der Anwendungsbereich (privat/kommerziell) und der analysierte Softwarebestandteil (Kommunikation, Konfigdateien, Bibliotheken, Programm).

Dekompilierung

Das Reverse Engineering selber ist relativ unkompliziert. Man benötigt lediglich einen Dekompiler wie z.B. JAD oder CAVAJ. Diesem Programm übergibt man die JAR oder CLASS-Datei und die Dekompilierung beginnt sofort.

Als Beispiel habe ich folgendes Programm gewählt. Es prüft die Lizenz und führt das Programm in der Execute-Methode aus.

public class MeinGeheimesProgramm {

      public static void main(String[] args) {

            // Dieses Programm ist geheim

            // Die Funktion checkLizenz prüft die übergebene Lizenz

            if(checkLizenz(args[0]) == true)

            {

                  execute();

            }

      }

      private static void execute() {

            // Hier wird was ausgeführt.....

      }

      private static boolean checkLizenz(String key) {

            // Prüfe die Lizenz -> Kriterium: Schlüssel muss pauschal 10 Zeichen lang sein

            if(key.length() == 10)

                  return true;

            else

                  return false;

      }

}

Aus diesem Programm wurde eine JAR-Datei erzeugt. Eine Jar-Datei ist nichts anderes als ein komprimiertes ZIP-Archiv, in der die Programminformationen stecken. Neben dem eigentlichen Programm (Class-Dateien) können das z.B. auch Metadaten sein. Als Dekompiler habe ich für das Beispiel JAD verwendet. Er erwartet über Kommandozeile die CLASS-Datei. Also wird folgendes ausgeführt:

jad c:\ws\MeinGeheimesProgramm.class

JAD erstellt nun daraus eine JAD-Datei, die den dekompilierten Javacode enthält. Wir schauen uns im nächsten Schritt die Datei an, um zu prüfen ob der Code auch wirklich dem entspricht den wir kompiliert haben.

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.

// Jad home page: http://www.kpdus.com/jad.html

// Decompiler options: packimports(3)

// Source File Name:   MeinGeheimesProgramm.java

public class MeinGeheimesProgramm

{

    public MeinGeheimesProgramm()

    {

    }

    public static void main(String args[])

    {

        if(checkLizenz(args[0]))

            execute();

    }

    private static void execute()

    {

    }

    private static boolean checkLizenz(String key)

    {

        return key.length() == 10;

    }

}

In der Tat entspricht das Ergebnis dem Quellcode aus unserer Entwicklungsumgebung. Die Kommentare wurden nicht mitkompiliert und können daher nicht mehr rekonstruiert werden. Durch die Originalbenennung aller Objekte und Methoden ist es für den Angreifer allerdings sehr leicht, Rückschlüsse auf das Programmverhalten zu ziehen.

Obfuskator einsetzen

Damit sich die Codeanalyse schwieriger gestaltet, wird nun ein Obfusktator eingesetzt. Für den Test habe ich mich für einen simplen Open-Source-Obfuskator namens ProGuard entschieden. Er kann JAR-Dateien mit einigen einfachen Techniken ändern (hauptsächlich Umbenennungen) und das Programm komprimieren, in dem toter Code entfernt wird.

Wir lassen also den Obfuskator die JAR-Datei analysieren und bearbeiten und wiederholen den Dekompiliervorgang. Das Ergebnis ist nun folgendes:

public class MeinGeheimesProgramm

{

    public MeinGeheimesProgramm()

    {

    }

    public static void main(String args[])

    {

        if(a(args[0]))

            a();

    }

    private static void a()

    {

    }

    private static boolean a(String s)

    {

        return s.length() == 10;

    }

}

Das Programm gibt nun nicht mehr so leicht den Zweck seiner Routinen preis. Hat das Programm nun einige Millionen Zeilen Code, ist das Reverse Engineering schon etwas erschwert worden. Professionelle Programme gehen mit ihren Verfahren noch weiter und können den Code wesentlich unleserlicher machen. Die Analyse eines großen Programms wird dann zu einer extremen Herausforderung.

Schutz vor Software Reverse Engineering

Auf den ersten Blick scheint es wenige Möglichkeiten zu geben, seine Software gegen eine Analyse zu sichern. In Bezug auf .net und Java-Anwendungen gibt es auch keine Möglichkeiten einer kompletten Unkenntlichmachung des Codes. Da sie, bedingt durch die Portabilität, auf einer relativ hohen Ebene als Bytecode vorliegen, können nur einige Tricks zur Erschwerung der Dekompilierung dienen.

Eines der bekanntesten Tools ist ein Obfuskator. Dieser sorgt automatisiert für eine Verwürfelung des Codes, um ihn möglichst unlesbar zu machen. Für beide Programmiersprachen gibt es viele verschiedene Obfuskatoren im Internet zum freien Download.

Ein Obfuskator bedient sich meistens folgender Methoden:

  • Umbenennung von Methoden, Klassen und auch Variablennamen. Dadurch wird das Verständnis des Codes erschwert, da eindeutige Namen nicht mehr existieren.
  • Entfernen von Debug-Informationen wie z.B. Zeilennummern
  • Anpassungen im Programmablauf. Der Obfuskator fügt z.B. neutrale Blöcke ein und vermischt auch Programmstellen miteinander. so dass das Programm keinerlei Änderungen im Ablauf erfährt. Der Angreifer kann allerdings die Logik viel schwerer nachvollziehen.
  • Kodierung von Strings

Die Gestaltungsspielräume für Obfuskatoren sind beliebig groß und je mehr Methoden angewendet werden, desto sicherer ist dies für das Verschleiern des Codes.

Jedoch haben diese Methoden auch ihre Nachteile. Durch zusätzlichen Code, das Dekodieren von Strings und zusätzliche Funktionsaufrufe etc. wird das Programm langsamer und auch größer.

Außerdem wird der Einsatz von Reflection sehr erschwert, was dazu führen kann, dass einige Programme dadurch komplett nutzlos werden. Der Grund dafür ist die Umbenennung der Methoden bzw. Klassen. Werden diese über einen String referenziert, aber durch den Obfuskator umbenannt, kann die Reflektion keinen Treffer ergeben.

Ein weiterer Nachteil ist die erschwerte Debug bzw. Logmöglichkeit. Viele Produkte nutzen ein detailliertes Logbuch, um Stacktraces bei Fehlern zu protokollieren und dadurch einen besseren Support anzubieten. Auch für die Suche nach Fehlern im Produktivbetrieb ist dies relevant. Wenn das Programm mittels Obfuskator verändert wurde, gestaltet sich so eine Suche als sehr schwierig. Die Zeilennummern stimmen nicht mehr mit dem Quellcode überein, geschweige denn die Klassennamen. Eine gezielte Nachverfolgung des Fehlers ist somit zum Scheitern verurteilt.

Fazit

Gerade im Bereich von .net und Java ist eine Dekompilierung und detaillierte Analyse des Codes sehr leicht durchzuführen. Wir müssen daher unsere Programme, wenn sie ein lohnendes Ziel darstellen, davor schützen. Der Obfuskator ist ein sehr guter Ansatz. Dessen Verwendung sollte aber gut überlegt sein, da sie nicht nur zu Leistungseinbußen führen kann, sondern auch zu defekter Software.

Je nach Softwarearchitektur, kann man auch Softwareteile in eine Cloud auslagern oder Prüfungen, patentierte Verfahren etc. auf den Firmenserver stellen und diese Leistung als Dienst anbieten. Dadurch bleibt Know-How im eigenen Unternehmen und kann nicht so einfach durch Reverse Engineering abgegriffen werden.