Was ist ein Cracker?

Cracker sind Personen, die versuchen bei einem Programm die Lizenzierung zu umgehen, damit ein Programm ohne gültige Lizenz benutzt werden kann. Dabei wird entweder die Validierung des Lizenzschlüssels komplett ausgeschaltet oder ein Tool geschrieben, das auf Knopfdruck valide Lizenzschlüssel generieren kann.

Was ist Reverse Engineering und welche Techniken gibt es?

Beim Reverse Engineering wird versucht ausgehend von einem fertigen Programm, die Logik der Applikation zu verstehen – ohne Zugang zum Quelltext zu haben.
Zum Beispiel kann ein Programm beim Starten ein bestimmtes Passwort geschützt sein, bevor die weitere Programmlogik ausgeführt wird. Damit muss ein Teil des Programms für die Prüfung des Passworts verantwortlich sein. Diese Prüfung kann sehr einfach sein, wie zum Beispiel die Eingabe mit einem fix kodierten Wert zu vergleichen. Eine komplizierte Methode könnte die Einzelwerte der Buchstaben summieren und überprüfen, ob die Summe durch "7" teilbar ist und gleichzeitig die Eingabe mit der Zeichenkette "PWD" beginnt.

Es gibt grundsätzlich zwei Arten von Techniken, wie man ein Programm analysieren kann: statische und dynamische Analyse.

Statische Analyse

Bei der statischen Analyse wird versucht die Programmlogik zu verstehen – ohne das Programm zu starten. Dabei werden Techniken wie "disassembly" oder "decompilation" eingesetzt.
„Disassembly” bedeutet, dass das Programm von Maschinencode – eine Reihenfolge von Bytes und Bits – zu einer "low level" Sprache zu konvertieren. Eine solche Low-Level Sprache ist zum Beispiel Assembly und enthält reine CPU-Instruktionen. Ein Beispiel einer Instruktion ist das Speichern von einem bestimmten Wert in einem bestimmten CPU-Register.
Low Level Sprachen können auch von Menschen gelesen und verstanden werden, beinhalten aber keine Abstraktionen – wie in Hochsprachen (C, Python oder Java) – und sind plattformspezifisch. Ein Programm in Assembly für Intel CPUs kann nicht auf einer ARM CPU ausgeführt werden.

Bei der „Decompilation” wird versucht aus dem Maschinencode den Quellcode wiederherzustellen. Dieser Vorgang ist wesentlich komplizierter als disassembly – vor allem für kompilierte Sprachen wie C oder C++. Während des Kompilierens werden jegliche Metadaten (z.B. Variablennamen) entfernt und selbst einfache Hochsprachen Befehle auf mehrere CPU-Instruktionen übersetzt.
Im Gegensatz dazu ist „Decompilation” für Sprachen, die zu Bytecode kompiliert werden, einfacher. Ein Beispiel für so eine Sprache wäre Java.

Folgende einfache C-Funktion errechnet das Quadrat einer Zahl:

int square(int num) {
    return num * num;
}

Die Funktion wird zu 7 CPU-Instruktionen kompiliert. (Die erste Zeile definiert den Funktionsnamen und wird von der CPU ignoriert.)

square(int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     eax, DWORD PTR [rbp-4]
        imul    eax, eax
        pop     rbp
        ret

Im Assembly-Code passiert das Wesentliche ab Zeile 4:

  • "MOV eax, DWORD PTR [rbp-4]" lädt den "num" Parameter in ein CPU-Register (namens EAX).
  • EAX wird mit sich selbst multipliziert. Laut Dokumentation von Intel wird das Ergebnis der „IMUL"-Instruktion wiederum in EAX gespeichert.
  • Letztendlich wird die Funktion mit der "RET"-Instruktion verlassen.

(Die nicht erwähnten Zeilen werden vom Compiler automatisch eingefügt. Diese Zeile entsprechen den Regeln, die von Calling Conventions definiert werden.)

Assembly-Quellcode ist – wie am Beispiel sichtbar – überschaubar und gut dem Originalcode zuzuordnen. Es ist aber wesentlich schwieriger anhand von Assembly Code (ohne Quellcode) auf die ursprüngliche Logik zu schliessen – vor allem, wenn komplexere Operationen durchgeführt werden.

Dynamische Analyse

Bei der dynamischen Analyse wird versucht, während der Laufzeit des Programms so viele Informationen wie möglich zu sammeln. Der Cracker analysiert das Verhalten des Programms:

  • Welche Daten werden in den Arbeitsspeicher geladen?
  • Welche Daten werden für Netzwerkverkehr generiert?
  • Welche Dateien werden erzeugt oder gelesen?
  • Werden Umgebungsvariablen definiert oder gelesen?
  • Welche Bibliotheken werden verwendet?
  • und viele mehr…

Für dynamische Analysen werden oft Debugger eingesetzt. Debugger sind spezialisierte Programme mit denen man „Anhaltepunkte” definieren kann. Wenn so ein Anhaltepunkt erreicht wird, wird die Ausführung pausiert. Man kann nun einerseits den Arbeitsspeicher anschauen oder man kann das Programm in Einzelschritten (single-stepping) ausführen. Beim „Single Stepping” wird die Ausführung nach jeder Instruktion neuerlich aufgehalten – bis das Programm entweder terminiert oder dieser Modus verlassen wird.

Debugger kommen normalerweise während der Softwareentwicklung zum Einsatz. Aber Debugger können auch auf fertige Programme anwendet werden – außer spezielle Schutzmechanismen oder Flags wurden gesetzt. Versierte Angreifer können diese Schutzmechanismen jedoch meist ebenfalls umgehen. Es dauert lediglich länger die richtigen Stellen zu finden.

To be continued...

In einem folgenden Blogeintrag wird ein ganz konkretes Binary sowohl statisch als auch dynamisch analysiert und geknackt.