JavaScript-Deobfuskierung: Wie man verschleierten Code entschlüsselt

CyberSecureFox 🦊

Im vorherigen Artikel haben wir untersucht, was JavaScript-Code-Obfuskation ist und warum sie verwendet wird. Obfuskierter Code ist schwer zu lesen und zu analysieren, was sowohl ein Vorteil für das Verbergen von Logik als auch ein Nachteil für das Debugging und die Wartung von Code ist. Aber was, wenn wir verstehen müssen, wie ein obfuskiertes Skript funktioniert? Hier kommt die Deobfuskation – der Prozess der Wiederherstellung des Codes in eine lesbare Form – zur Rettung.

Warum JavaScript-Deobfuskation notwendig ist

Deobfuskation ist in mehreren Fällen erforderlich:

  1. Analyse von bösartigen Skripten. Hacker und Malware-Autoren obfuskieren oft ihren Code, um bösartige Funktionalität zu verbergen. Um zu verstehen, wie Malware funktioniert, ist es notwendig, den Code wiederherzustellen.

  2. Finden und Beheben von Fehlern. Obfuskation erschwert das Debugging, da es schwierig ist, Haltepunkte zu setzen und Variablenwerte in minifiziertem Code zu betrachten. Deobfuskation vereinfacht die Fehlererkennung.

  3. Untersuchung versteckter Funktionalitäten. Manchmal obfuskieren Entwickler Code mit undokumentierten Fähigkeiten. Durch Deobfuskation können Sie diese Funktionen kennenlernen.

  4. Modifizierung proprietären Codes. In geschlossener Software ist der Code normalerweise obfuskiert. Deobfuskation eröffnet Möglichkeiten, die Logik nach Ihren Bedürfnissen zu ändern.

Es ist wichtig zu bedenken, dass die Deobfuskierung von proprietärem Code möglicherweise gegen die Lizenzvereinbarung verstößt. Überprüfen Sie immer die Nutzungsbedingungen, bevor Sie Reverse Engineering betreiben.

Grundlegende Deobfuskationsmethoden

Es gibt mehrere Hauptansätze zur Wiederherstellung lesbaren Codes:

1. Formatierung (Beautify)

Der erste Schritt bei der Deobfuskation ist die Formatierung minifizierten Codes. Dies wird kein vollständig lesbares Ergebnis liefern, aber es wird die weitere Analyse vereinfachen. Für die Formatierung können Sie verwenden:

  • Browser-Tools, wie „Pretty print“ in Chrome DevTools
  • Online-Dienste wie Prettier oder JS Nice
  • Plugins für beliebte Code-Editoren.

2. Umbenennen von Variablen

Die nächste Phase ist die Vergabe aussagekräftiger Namen für Variablen und Funktionen anstelle von einbuchstabigen. Gute Deobfuskatoren tun dies automatisch basierend auf dem Kontext der Namensverwendung.
Zum Beispiel würde die Variable „a“ in „userName“ umbenannt werden, wenn sie in der Zeichenkette „Hello “ + a verwendet wird.

3. Entfernen von totem Code

Obfuskatoren fügen oft unerreichbaren Code ein, der nie ausgeführt wird, aber die Analyse stark beeinträchtigt. Während der Deobfuskation muss er entfernt werden. Zum Beispiel:

if (false) {
   x = 10;
}

4. Wiederherstellung des Kontrollflusses

Obfuskation verwirrt die Sequenz der Codeausführung mit bedeutungslosen Bedingungen und Sprüngen. Die Aufgabe des Deobfuskators ist es, die normale Struktur mit linearen Codeblöcken und minimalen Bedingungen wiederherzustellen.

Beispiel mit überflüssiger Bedingung:

// Obfuskiert  
function check(x) {
  if (x >= 0) {
    if (x > 0) {
      console.log('Positiv');
    } else {
      console.log('Null');  
    }
  } 
}

// Deobfuskiert
function check(x) {
  if (x > 0) {
    console.log('Positiv');
  } else if (x === 0) {  
    console.log('Null');
  }
}

5. Wertsubstitution

Viele Obfuskatoren verschieben Zeichenketten und Zahlen in separate Arrays und greifen dann über den Index darauf zu. Während der Deobfuskation müssen diese Werte wieder in den Code eingebettet werden.

// Obfuskiert
const strings = ['Hallo', 'Welt', '!'];
console.log(strings[0] + ' ' + strings[1] + strings[2]);
// Deobfuskiert
console.log('Hallo' + ' ' + 'Welt' + '!');

6. Vereinfachen von Ausdrücken

Obfuskatoren komplizieren arithmetische und logische Ausdrücke. Zum Beispiel x * 1 anstelle von x oder !!x anstelle von x. Bei der Wiederherstellung des Codes müssen solche Überschüsse entfernt werden.

Vorher:

if (!!x && y !== undefined) {
  result = x * 1 + y / 2;
}

Nachher:

if (x && y !== undefined) {
  result = x + y / 2; 
}

Tools für die Deobfuskation

Manuelle Deobfuskation ist ein mühsamer Prozess, der viel Zeit in Anspruch nehmen kann. Glücklicherweise gibt es Tools zur Automatisierung der Code-Entwirrung:

  • JS Nice – ein Online-Service, der nicht nur formatiert, sondern auch Variablen umbenennt, toten Code entfernt und den Kontrollfluss wiederherstellt.

  • de4js – ein weiterer Online-Deobfuskator mit Unterstützung für Entpacken, String-Substitution und Kontrollvereinfachung.

  • JavaScript Deobfuscator – ein Desktop-Deobfuskator mit umfangreichen Fähigkeiten zur AST-Analyse (Abstract Syntax Tree).

  • Browser-Erweiterungen, wie JavaScript Deobfuscator für Chrome, die Deobfuskation direkt in DevTools ermöglichen.

Allerdings liefern selbst die fortschrittlichsten Tools keinen 100% lesbaren Code, besonders bei nicht-standardmäßigen Obfuskationstechniken. Daher ist oft manuelle Analyse für die vollständige Logikwiederherstellung erforderlich.

Schritt-für-Schritt Deobfuskationsbeispiel

Lassen Sie uns die praktische Wiederherstellung lesbaren Codes aus einem obfuskierten Skript untersuchen.
Angenommen, wir haben den folgenden Code:

(function() {
  var _0x5d2a = ['log', 'Hello\x20World'];
  (function(_0x25a336, _0x5d2afa) {
    var _0x41fb0f = function(_0x9fdc11) {
      while (--_0x9fdc11) {
        _0x25a336['push'](_0x25a336['shift']());
      }
    };
    _0x41fb0f(++_0x5d2afa);
  }(_0x5d2a, 0x78));
  var _0x41fb = function(_0x25a336, _0x5d2afa) {
    _0x25a336 = _0x25a336 - 0x0;
    var _0x41fb0f = _0x5d2a[_0x25a336];
    return _0x41fb0f;
  };
  console[_0x41fb('0x0')](_0x41fb('0x1'));
})();

Schritt 1. Formatierung

Formatieren wir den minifizierten Code, um ihn etwas strukturierter zu machen:

(function() {
  var _0x5d2a = ['log', 'Hello\x20World'];
  (function(_0x25a336, _0x5d2afa) {
    var _0x41fb0f = function(_0x9fdc11) {
      while (--_0x9fdc11) {
        _0x25a336['push'](_0x25a336['shift']());
      }
    };
    _0x41fb0f(++_0x5d2afa);
  }(_0x5d2a, 0x78));
  var _0x41fb = function(_0x25a336, _0x5d2afa) {
    _0x25a336 = _0x25a336 - 0x0;
    var _0x41fb0f = _0x5d2a[_0x25a336];
    return _0x41fb0f;
  };
  console[_0x41fb('0x0')](_0x41fb('0x1'));
})();

Schritt 2. Analyse des String-Arrays

In der ersten Codezeile wird ein Array _0x5d2a mit zwei Elementen deklariert: ‚log‘ und ‚Hello\x20World‘.
Merken wir uns das und fahren mit der Analyse fort.

Schritt 3. Analyse der IIFE

Der nächste Block ist ein sofort aufgerufener Funktionsausdruck (IIFE).
Er nimmt das Array _0x5d2a und die Zahl 0x78 (120 im Dezimalsystem), die dann um 1 erhöht wird.

Innerhalb der IIFE wird eine Funktion _0x41fb0f deklariert, die eine Zahl _0x9fdc11 entgegennimmt und in einer Schleife push und shift auf dem Array _0x25a336 aufruft. Im Wesentlichen ist dies eine Mischung des Arrays.

Nach der Deklaration von _0x41fb0f wird sie sofort mit dem Argument 0x79 (121 im Dezimalsystem) aufgerufen.

Schritt 4. Analyse der Getter-Funktion

Als Nächstes wird eine Funktion _0x41fb deklariert, die 2 Argumente entgegennimmt.

Das erste Argument _0x25a336 wird um 0x0 (0 im Dezimalsystem) verringert, was bedeutet, dass es unverändert bleibt.

Dann wird ein Element aus dem Array _0x5d2a am Index _0x25a336 entnommen und zurückgegeben. Dies ist also ein Wrapper für den Zugriff auf das Array über den Index.

Schritt 5. Wertsubstitution

In der letzten Zeile wird eine Methode mit dem Index ‚0x0‘ (0) auf dem Konsolenobject aufgerufen mit dem Argument ‚0x1‚ (1).

Im Array _0x5d2a ist ‚log‘ am Index 0 und ‚Hello\x20World‘ am Index 1 gespeichert.

Wenn wir diese Werte einsetzen, erhalten wir:

console['log']('Hello\x20World');

Oder vereinfacht:

console.log('Hello World');

Ergebnis

Der ursprüngliche obfuskierte Code gab einfach ‚Hello World‚ auf der Konsole aus, wenn auch auf sehr umständliche Weise.

Wenn wir alle Deobfuskationsschritte zusammenfassen, erhalten wir diesen Code:

(function() {
  console.log('Hello World');
})();

Tipps zur Analyse von obfuskiertem Code

  • Beginnen Sie mit der Formatierung und Analyse von Variablendeklarationen. Dies gibt Ihnen einen allgemeinen Überblick über die Codestruktur.

  • Wenn Sie unverständliche Zahlen im Code sehen, versuchen Sie, sie in andere Zahlensysteme umzuwandeln (dezimal, ASCII).

  • Achten Sie auf anfällige eingebaute Funktionen wie eval() und Function(). Andere Teile des obfuskierten Codes könnten durch sie ausgeführt werden.

  • Verwenden Sie einen Debugger und schrittweise Ausführung, um Variablenwerte und die Abfolge von Operationen zu betrachten.

  • Wenn Sie die Logik eines Codeabschnitts nicht verstehen, versuchen Sie, ihn vorübergehend zu entfernen oder durch einen Platzhalter zu ersetzen. Es könnte die Hauptfunktionalität nicht beeinflussen.

  • Suchen Sie nach Mustern und Wiederholungen. Obfuskatoren generieren schablonenhaften Code, sodass die gleichen Konstruktionen in verschiedenen Teilen verwendet werden können.

  • Seien Sie geduldig. Deobfuskation kann zeitaufwendig sein, besonders bei der Analyse von bösartigen Skripten.

Fazit

JavaScript-Code-Deobfuskation ist eine wertvolle Fähigkeit, die sich bei der Analyse von bösartigen Skripten, dem Finden von Fehlern, versteckten Funktionen und der Anpassung von closed-source Code als nützlich erweist. Die Hauptmethoden der Deobfuskation umfassen Formatierung, Umbenennen von Variablen, Entfernen von totem Code, Wiederherstellung des Kontrollflusses, Wertsubstitution und Vereinfachung von Ausdrücken.

Es gibt Online-Dienste wie JS Nice und de4js sowie Desktop-Utilities und Browser-Erweiterungen zur Automatisierung von Deobfuskationsschritten. Bei komplexer Obfuskation ist jedoch oft manuelle Analyse erforderlich. Der Deobfuskationsprozess selbst besteht aus der sequentiellen Analyse von Codeabschnitten, der Substitution von Variablenwerten und der Vereinfachung der Logik. Er erfordert Geduld, JavaScript-Kenntnisse und deduktive Fähigkeiten.

Um obfuskierten Code effektiver zu analysieren, beginnen Sie mit der Formatierung und dem Parsen von Variablendeklarationen. Achten Sie auf verdächtige eingebaute Funktionen, verwenden Sie einen Debugger für die schrittweise Ausführung. Suchen Sie nach Mustern und Wiederholungen in vom Obfuskator generiertem Code. Das Studium der Deobfuskation entwickelt Fähigkeiten im sorgfältigen Codelesen, Debugging und Reverse Engineering. Es ist eine Superkraft für einen Programmierer, die sich definitiv in Ihrer Arbeit oder Teilnahme an CTF-Wettbewerben als nützlich erweisen wird.

Ich wünsche Ihnen viel Erfolg bei dem nicht-trivialen, aber spannenden Prozess des Entwirrens von obfuskiertem JavaScript-Code! Denken Sie daran, dass hinter den Komplexitäten immer die Eleganz der ursprünglichen Absicht des Autors steht.

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.