Reverse Engineering einer obfuskierten Signatur: 90 % sind Standard, entscheidend sind die abweichenden 10 %

Zuletzt aktualisiert: 2026-07-30 10:24:31

Ein obfuskiertes Signature-SDK wirkt beim ersten Öffnen schnell wie schwarze Magie: kryptische Namen, verschachtelte Operationen, und jede Zeile scheint etwas Unverständliches zu berechnen.

Nach einem vollständigen Reverse-Engineering-Durchlauf zeigt sich meist das Gegenteil: 90 % des Codes bestehen aus Standardalgorithmen. Der Hash ist öffentlich dokumentiert, das Encoding ebenfalls, genauso die Stromchiffre. Diese Teile muss man nicht bis ins Detail nachvollziehen. Es reicht, den Algorithmus zu erkennen und ihn aus dem RFC oder einer Referenzimplementierung zu übernehmen – dann stimmt jedes Byte.

Gefährlich sind die verbleibenden 10 %: Stellen, an denen ein Standardalgorithmus minimal verändert wurde. Eine feste Rundenzahl wird plötzlich zur Variablen, eine Basiskonstante durch einen benachbarten Wert ersetzt, Wortindizes in einer Runde vertauscht oder Schlüsselmaterial im Ciphertext versteckt. Jede einzelne Änderung ist klein. Übersieht man nur eine davon, passt die eigene Reimplementierung nicht zum Ziel – obwohl die anderen 90 % korrekt sind.

Beim Reverse Engineering obfuskierter Signaturen geht es daher nicht darum, die 90 % Standardcode zu lesen, sondern die 10 % Abweichungspunkte aufzuspüren. Genau dabei kann ein großes Modell helfen – und gerade in diesem Schritt ist es am schwersten zu ersetzen.

Die Voraussetzung, also das Erkennen der Algorithmusfamilie anhand von Konstanten und Struktur – etwa „das ist ChaCha“ oder „das ist FNV“ –, behandelt der vorherige Beitrag. Hier setze ich voraus, dass das Grundgerüst bereits identifiziert ist, und gehe direkt auf die Abweichungssuche ein.

Warum ausgerechnet diese Details so leicht übersehen werden

Beim Erkennen von Mustern hat das menschliche Gehirn eine unangenehme Eigenschaft: Sobald es das Prinzip erkannt hat, prüft es die Details nicht mehr genau.

Man sieht einen Codeblock, die Konstanten tragen den typischen ChaCha-Fingerabdruck, die Struktur wirkt vertraut – gedanklich ist das Kästchen „ChaCha20“ abgehakt. Danach geht kaum jemand Runde für Runde, Index für Index und Bit für Bit durch den Code. Das Gefühl, den Algorithmus bereits erkannt zu haben, lenkt die Aufmerksamkeit vom Rest weg.

Genau darauf setzen Autoren von Obfuskierung. Den Algorithmus vollständig umzuschreiben wäre aufwendig und fehleranfällig. Stattdessen ändern sie so wenig wie möglich: eine Zahl, einen Index, einen zusätzlichen Verarbeitungsschritt. Das fällt beim Wiedererkennen leicht durch, reicht aber aus, damit eine standardkonforme Reimplementierung komplett scheitert.

Das ist ein asymmetrischer Aufmerksamkeitskampf. Die Gegenseite muss nur eine einzige Abweichung verstecken, man selbst muss jede davon finden. Und Menschen sind von Natur aus schlecht darin, etwas weiter zu misstrauen, das auf den ersten Blick korrekt aussieht.

Hier hat ein Modell einen kontraintuitiven Vorteil: Es kennt nicht dieses befriedigende „hab ich schon erkannt“, das die Prüfung beendet. Wenn der Prompt ausdrücklich nach Abweichungen suchen lässt, kann es Punkt für Punkt kontrollieren – vorausgesetzt, der Prompt ist richtig aufgebaut. Darum geht es in Abschnitt 5. Zuerst aber die vier typischen Formen solcher Abweichungen.

Vier typische Arten von Abweichungen

Für jeden Typ folgt zunächst ein öffentlicher Standardalgorithmus als Referenz, anschließend die Form der jeweiligen Abweichung. In realen Projekten treten diese vier Varianten oft gemeinsam innerhalb desselben Signierers auf.

1. Aus einer Konstante wird eine Variable

Eine Standard-Stromchiffre hat eine feste Rundenzahl. ChaCha20 arbeitet unverrückbar mit 20 Runden; in jeder Standardimplementierung ist dieser Wert fest kodiert. Auch RFC 8439 beschreibt in der Einleitung ausdrücklich nur ChaCha mit 20 Runden; die Varianten mit 8 und 12 Runden sind an anderer Stelle definiert. Die Rundenzahl ist im Standard eine Konstante.

Die Abweichung: Die feste Rundenzahl wird dynamisch aus dem Schlüssel berechnet. Die Quarter-Round-Logik bleibt identisch, aber die Zahl der Durchläufe hängt von bestimmten Bytes des Keys ab. Ändert sich der Schlüssel, ändert sich damit auch die Rundenzahl.

Warum das leicht durchrutscht: Die Quarter-Rounds sind erkannt, die σ-Konstanten ebenfalls, und im Kopf steht bereits „ChaCha20“. Also wird eine Implementierung mit 20 Runden übernommen. Ob die Schleifenvariable tatsächlich eine Konstante oder doch ein Ausdruck ist, wird gar nicht mehr geprüft – weil sie in jeder bekannten ChaCha-Implementierung konstant war.

Das Signal für diese Abweichung lautet: Dort, wo eine Konstante erwartet wird, steht ein eingabeabhängiger Ausdruck. Eine sauber angeleitete Gegenbeweisprüfung kontrolliert gezielt, ob ein im Standard hart codierter Wert hier berechnet wird.

Bestätigen lässt sich das mit Differentialtests: Zwei Schlüssel verwenden, die sich nur in einem Byte unterscheiden. Ist der Unterschied im Ergebnis deutlich größer, als man vom Einfluss eines einzelnen Bytes erwarten würde, speist dieses Byte vermutlich einen globalen Parameter – etwa die Rundenzahl – statt nur in den Keystream einzugehen.

2. Veränderte Konstante: ein fast gleicher Ausgangswert

Der FNV-1a-Hash besitzt zwei öffentliche magische Zahlen: Offset Basis und Prime. Jede korrekte FNV-1a-Implementierung verwendet exakt diese beiden Werte; sie sind im Standard dokumentiert. Die von Mitautor Landon Curt Noll gepflegte FNV-Referenzseite nennt für 32 Bit die Offset Basis 2166136261 und die Prime 16777619 – Ziffer für Ziffer.

Die Abweichung: Eine dieser Basiskonstanten wird durch einen nahen Wert ersetzt, der nur minimal vom Standard abweicht. Die Hash-Struktur bleibt vollständig erhalten: XOR, Multiplikation und Schleife sind korrekt. Nur der anfängliche Basiswert wurde fast unsichtbar verändert.

Warum das leicht übersehen wird: Das ist die tückischste der vier Varianten. Man erkennt die FNV-Struktur, sieht eine große Zahl, die wie die Offset Basis aussieht, und entscheidet auf „Standard-FNV-1a“. Kaum jemand vergleicht eine bereits wiedererkannte magische Zahl Bit für Bit mit dem Referenzwert.

Diese Abweichung lässt sich fast nur mit Differentialtests zuverlässig finden, denn große Zahlen mit bloßem Auge zu vergleichen ist notorisch unzuverlässig. Das Vorgehen: Einen bekannten Input durch die Standardimplementierung und durch die Ziel-Blackbox laufen lassen und die Ergebnisse vergleichen. Ist die Struktur identisch, die Ausgabe aber unterschiedlich, liegt das Problem mit hoher Wahrscheinlichkeit in einer Basiskonstante. Dann gibt man dem Modell die verdächtige Konstante und lässt sie mit dem Standardwert abgleichen – das kann eine Maschine deutlich zuverlässiger als ein Mensch.

Der Nutzen des Modells ist hier sehr konkret: Es kennt die Standard-Offset-Basis von FNV-1a bis auf das letzte Bit, man selbst meist nicht. Die Frage „Ist diese Konstante exakt gleich der Standard-Offset-Basis von FNV-1a?“ genügt, und die Differenz fällt sofort auf.

3. Lokaler Umbau: ein Standardschritt wird verändert

Eine ChaCha-Doppelrunde besteht aus 8 Quarter-Rounds: Die ersten 4 bearbeiten Spalten, die folgenden 4 Diagonalen. Welche Wörter jede Quarter-Round verarbeitet, ist im Standard festgelegt. RFC 8439 §2.3 beschreibt die Blockfunktion und die jeweils verwendeten State-Wörter exakt.

Die Abweichung: In einer Runde werden unauffällig ein oder zwei Wortindizes vertauscht. Der überwiegende Teil der Runden bleibt standardkonform; nur irgendwo mittendrin wird ein eigentlich vorgesehenes Wort durch ein anderes ersetzt. Der Code sieht weiterhin nach ChaCha aus und läuft fehlerfrei, erzeugt aber einen vollständig anderen Keystream als Standard-ChaCha.

Warum das leicht übersehen wird: Die Quarter-Round-Indizes bilden eine lange Zahlenfolge – acht Gruppen wie (0,4,8,12)(1,5,9,13)…. Beim Überfliegen bestätigt das Auge nur „Spaltenrunden, dann Diagonalrunden“, ohne zu kontrollieren, ob alle vier Zahlen jeder Gruppe tatsächlich an ihrer Standardposition stehen. In einer solchen ohnehin ermüdenden Indexfolge lässt sich eine Änderung hervorragend verstecken.

Auch für ein Modell ist das kein Fall für einen schnellen Blick. Es muss dazu angeleitet werden, die Indizes Runde für Runde aufzulisten und mit Standard-ChaCha zu vergleichen. Das ist eine mechanische Prüfaufgabe – genau die Art Arbeit, bei der Menschen abschweifen und ein Modell nicht. Eine Tabelle „Standardindex versus tatsächlicher Index“ macht die Abweichung sichtbar.

Der Differentialtest zur Bestätigung: Wenn die ersten beiden Typen ausgeschlossen sind – Rundenzahl und Konstanten stimmen –, das Ergebnis aber weiterhin abweicht, ist das Problem strukturell. Den Zwischenzustand nach jeder Runde ausgeben; die erste Runde, die von Standard-ChaCha abweicht, ist die veränderte Runde.

4. Eingebettetes Material: Schlüsselbytes stecken in der Ausgabe

Die ersten drei Varianten verändern den Algorithmus selbst. Diese hier verändert die Organisation der Daten.

Eine verbreitete Technik: Der Verschlüsselungsschlüssel wird nicht über einen separaten Kanal übertragen, sondern aufgeteilt und direkt in den Ciphertext eingefügt. Der Empfänger extrahiert ihn nach derselben Regel wieder. Die raffiniertere Variante verwendet keine feste Einfügeposition, sondern berechnet sie aus dem Dateninhalt. Ändert sich der Ciphertext, verschiebt sich auch die Stelle, an der der Schlüssel verborgen ist. Außen herum liegen dann noch Base64 mit einem benutzerdefinierten Alphabet und ein Marker-Präfixbyte.

Warum das leicht übersehen wird: Man kämpft mit dem Verschlüsselungsalgorithmus und bemerkt nicht, dass der Schlüssel gar nicht gebrochen werden muss. Er steckt bereits im vorliegenden Ciphertext; nur sein Abschnitt ist noch unbekannt. Gerade Einsteiger bleiben hier oft hängen und versuchen, etwas zu „knacken“, dessen Klartext praktisch vor ihnen liegt.

Das Signal dafür ist ein Bereich innerhalb eines Datenblocks, dessen statistische Eigenschaften nicht zum Rest passen. Ein Schlüssel besteht üblicherweise aus hochentropischen Zufallsbytes und bildet, mitten in einen Ciphertext eingesetzt, ein erkennbares Fremdsegment. Ein Modell kann dabei helfen zu analysieren, welcher Ausgabeabschnitt eine Byteverteilung aufweist, die vom Umfeld abweicht – und so die Grenzen des eingebetteten Materials zu bestimmen.

Zur Bestätigung: Wenn sich die Einfügeregel ermitteln lässt, etwa ein Modulo-Ausdruck über eine Bytesumme, nimmt man einige bekannte Input/Output-Paare, leitet die Formel für die Einfügeposition rückwärts her und prüft sie anschließend vorwärts. Das Modell kann helfen, die Regel aus wenigen Beispielen zu induzieren. Ob sie letztlich stimmt, entscheidet aber weiterhin assert.

Die Gegenbeweisprüfung ist das eigentliche Spielfeld für Reasoning-Modelle

Über alle vier Typen hinweg zeigt sich dasselbe Muster: Die Algorithmusfamilie zu erkennen – der Kandidat – ist einfach; die Abweichung zu finden – der Gegenbeweis – ist schwierig.

Den Kandidatenschritt schafft fast jedes Modell. ChaChas σ-Konstanten und die FNV-Struktur sind bekannte Muster, die jedes auf den Trainingsdaten basierende Modell wiedererkennt. Die eigentliche Trennlinie zwischen Modellen liegt in der Gegenbeweisprüfung: Kann und will das Modell einen gerade erkannten Algorithmus weiter hinterfragen und sagen: „Dieser Teil entspricht aber nicht dem Standard“?

Ein schwächeres Modell verliert hier den Mut. Hat es „ChaCha20“ erkannt, degeneriert die Gegenbeweisprüfung oft zu einer Umschreibung des Kandidaten: „Die Implementierung folgt der Standardstruktur von ChaCha20 und verwendet die klassische Quarter Round …“ Das wiederholt nur die Hypothese, ohne eine einzige Abweichung zu prüfen. Für den nächsten Arbeitsschritt hilft das nicht weiter.

Die Gegenbeweisprüfung eines starken Reasoning-Modells sieht anders aus. Sie lautet etwa: „Der Kandidat ist ChaCha20, aber es gibt drei Abweichungen von der Standardimplementierung: Erstens wird die Rundenzahl durch einen schlüsselabhängigen Ausdruck gesteuert, während Standard-ChaCha20 immer 20 Runden nutzt. Zweitens entsprechen die Wortindizes in Runde N nicht der Standard-Diagonalrunde. Drittens …“ Jeder Punkt benennt eine konkrete, überprüfbare Abweichungsstelle. So eine Gegenbeweisprüfung ist bereits die Checkliste für die Differentialtests in Stage 3.

Deshalb lohnt es sich, die Identifikation der Algorithmusfamilie auf die Reasoning-Stufe zu legen – und die Wahl vor dem Festlegen selbst zu testen. Die Qualität der Gegenbeweise entscheidet unmittelbar darüber, wie viele nutzlose Tests und Umwege entstehen.

Für diesen Workflow nutze ich vier Stufen:

Stage

Benötigte Fähigkeit

Empfehlung

model id

Strukturelles Mapping nach dem Split

Langer Kontext, liest ein ganzes Modul in einem Durchgang

Kimi K3

kimi-k3

Identifikation der Algorithmusfamilie und Gegenbeweise

Starkes Reasoning; kann der eigenen Hypothese widersprechen

Claude Opus 5

claude-opus-5

Umbenennung vieler Symbole

Günstig, hohe Parallelität

Claude Sonnet 5

claude-sonnet-5

Zuordnung von Abweichungen

Mittleres Reasoning, erklärt anhand konkreter Bytes

GPT-5.6 Sol

gpt-5.6-sol

Die zweite Stufe bildet den Kern dieses Artikels. Die Auswahl sollte man nicht einfach übernehmen, sondern testen. Das Protokoll ist einfach:

  1. 2–3 Leaf-Funktionen aus dem eigenen obfuskierten Bundle auswählen, darunter mindestens eine Funktion, deren Ergebnis bereits bekannt ist, als Kontrolle.

  2. Mit dem dreiteiligen Prompt „Kandidat / Evidenz / Gegenbeweis“ aus dem vorherigen Beitrag denselben Input getrennt an claude-opus-5 und gpt-5.6-sol geben.

  3. Ausschließlich die Gegenbeweisprüfung bewerten: Prüft sie Abweichungen tatsächlich einzeln, oder formuliert sie den Kandidaten nur anders? Wie viele Abweichungen erkennt jedes Modell bei der Kontrollfunktion?

  4. Anzahl und Qualität der gefundenen Abweichungen sind das Auswahlkriterium.

Der Unterschied zeigt sich schon nach einem einzigen Durchlauf – direkter als auf jeder Benchmark-Rangliste.

Das eigentliche Problem sind die Wechselkosten

Vier Modelle von drei Anbietern: Naiv umgesetzt bedeutet das drei SDKs, drei Authentifizierungsschemata und drei Fehlerbehandlungen. Die meisten rechnen den Aufwand durch, entscheiden sich dagegen und verwenden dann ein einziges Modell für alles. Bei der Identifikation der Algorithmusfamilie landet so eine Stufe mit schwacher Gegenbeweisprüfung im Einsatz – und es entstehen zahlreiche Umwege, ohne dass klar ist, warum.

AIReiter nimmt diese Ebene heraus: ein Key, eine OpenAI-kompatible Schnittstelle, alle vier Modelle dahinter. Zum Wechseln muss im Request-Body lediglich das Feld model geändert werden.

# Identifikation der Algorithmusfamilie + Gegenbeweise: die Reasoning-Stufe
curl https://aireiter.com/api/v1/chat/completions \
  -H "Authorization: Bearer $AIREITER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-5",
    "messages": [{"role": "user", "content": "<dreiteiliger Prompt + Leaf-Funktion + Konstanten>"}]
  }'

# Umbenennung vieler Symbole: ein Feld ändern
#   "model": "claude-sonnet-5"
# Zuordnung von Abweichungen:
#   "model": "gpt-5.6-sol"

Wer bereits das OpenAI SDK nutzt, setzt base_url auf https://aireiter.com/api/v1 und ändert sonst nichts. Beim Anthropic SDK genügt mit demselben Key ein Aufruf von POST /api/v1/messages.

Preislich liegen Claude-Modelle 30 % unter dem Listenpreis, GPT-Modelle bei der Hälfte des Preises. Für diesen Workflow trifft der Rabatt genau die kostentreibenden Schritte: Bei der Algorithmusfamilie wird der Prompt immer wieder überarbeitet und dieselbe Funktion über viele Runden abgefragt; das ist der aufrufintensivste Teil des Ablaufs. Die Umbenennung vieler Symbole beginnt bei Hunderten von Aufrufen. Diese beiden Bereiche verursachen den Großteil der Kosten.

Fazit

Die Realität beim Reverse Engineering obfuskierter Signaturen ist einfach: Der meiste Code sind Standardalgorithmen, die sich unverändert übernehmen lassen; die eigentliche Arbeit besteht darin, die still veränderten Stellen eines Standardalgorithmus zu finden.

Die vier Abweichungstypen – aus Konstante wird Variable, veränderte Konstante, lokaler Strukturumbau, eingebettetes Material – haben eine gemeinsame Eigenschaft: Sie sind klein genug, um der menschlichen Mustererkennung durchzurutschen, aber groß genug, damit eine Reimplementierung vollständig scheitert. Menschen bleiben bei etwas, das korrekt aussieht, nur schlecht misstrauisch. Genau darin liegt die Stärke eines gut geprompteten Modells.

Das Modell erzeugt allerdings nur Verdacht, keine Bestätigung. Jede Hypothese zu einem Abweichungspunkt muss am Ende in einen Differentialtest überführt werden – darum geht es im Beitrag zu Differentialtests. Das Modell liefert eine Liste möglicher Änderungen; assert entscheidet, welche davon tatsächlich vorgenommen wurden.