Grundlegende Javascript Malware Analyse: Ein Beispiel

Grundlegende Javascript Malware Analyse

Ein Beispiel

Stefan Friedli
von Stefan Friedli
Lesezeit: 13 Minuten

Für die Sicherheit der eigenen Web Applikation zu sorgen, ist die Verantwortung jedes Webseitenbetreibers. Doch wird eine Seite kompromittiert, so muss adäquat reagiert werden. Gerade wenn eine Webpräsenz dazu missbraucht wird, deren Benutzer mit Schadsoftware oder Exploits zu attackieren, ist eine Klärung der Umstände unabdinglich, um eine professionelle Reaktion zu gewährleisten und den schier unabwendbaren Reputationsschaden möglichst gering zu halten.

Wie soll man jedoch im Detail herausfinden, was der auf der eigenen Seite befindliche Schadcode tut und dies auch noch in einer nachvollziehbaren und sicheren Weise? Der Ansatz, der hier in der Regel gewählt wird, ist eine strukturierte Analyse des Schadcodes, den ich im Folgenden an einem konkreten Beispiel erklären möchte.

Achtung: Es handelt sich hierbei um einen realen Fall mit realem Schadcode der unter Umständen ebenso realen Schaden auf Ihrem System anrichten kann. Bitte besuchen Sie die genannten URIs nicht und betrachten Sie sämtliche Scripts ausschliesslich in einem Texteditor und Rhino, nicht in einem Webbrowser. Es besteht kein Haftungsanspruch.

Ausgangslage

Es wurde beobachtet, dass die Datei counter.js von einem Server mit russischer Domänenendung überdurchschnittlich oft in eine kompromittierte Seite eingebunden wird – teils mehrmals auf der selben Seite (Danke an Andreas für den Hinweis). Alle Dateien, die im Rahmen dieses Beitrages verwendet werden, stehen unter http://www.scip.ch/labs/files/20091027_jsmalware.zip zum Download bereit. Auch hier gilt die oben genannten Warnung.

Ansicht der Ursprungsdatei in einem Texteditor

Schritt 1: Re-Formatierung

Was im ersten Moment verwirrend aussieht, entsteht in erster Linie durch die (bewusste) Abwesenheit sämtlicher, der Übersicht dienlichen Massnahmen der Codeformatierungen, wie zum Beispiel Zeilenumbrüche oder Einrückungen von Codeblöcken. Javascript schert sich herzlich wenig um diesen Mangel an Sauberkeit, wir dagegen schon. Allerdings geht es dabei nicht um die Ästhetik, sondern die logische Verteilung des Codes auf einzelne, klar identifizierbare Linien. Das erleichtert das exakte Setzen der Breakpoints später enorm und erleichtert natürlich das Lesen des Codes.

Ansicht der bereinigten Datei in einem Texteditor

Schritt 2: Analyse

Nachdem der Code allein durch eine etwas sauberere Formatierung bereits viel lesbarer ist, ist die weitere Bearbeitung geschmackssache. Während einige die “Trockenanalyse” im Texteditor vorziehen (Deadlisting), werden wir in an dieser Stelle den freien Javascript Debugger Rhino des Mozilla Projektes nutzen, um das File weiter zu analysieren.

Rhino wird im Hinblick auf derartige Use-Cases in der Regel missverstanden. Rhino ist eine komplett eigenständige, in Java gehaltene, Javascript Implementierung. Das heisst konkret: Die Syntax, wie sie unser Script verwendet, wird komplett verstanden. Rhino ist aber kein Firefox-Emulator, wodurch gewisse DOM-Objekte wie document, window oder ähnliches nicht von Haus aus verfügbar sind.

So kommt es denn auch, dass Rhino bereits in der ersten Zeile unseres Scripts seinen Dienst verwehrt, weil ihm document.cookie unbekannt ist.

var $a = document.cookie;		// Lese Cookie
  var $b = $a.indexOf("opt=");	// Prüfe ob das Cookie String "opt=" enthält
  if ($b != -1) {				// Prüfe, ob Suche erfolglos war
  } else {						
      var $c = new Date;		// Generiere Datum
      $c.setTime($c.getTime() + 3600000);	// Generiere Timeout
      document.cookie = "opt=update;expires=" + $c.toGMTString(); // Cookie

Ansicht der Arbeitsdatei im Rhino-Debugger

Der erste Teil des Skripts ist sehr einfach verständlich. Der Autor liest die Cookiedaten der geladenen Seite aus und prüft, ob die Variable opt gesetzt ist. Wenn dies der Fall ist, so gibt die Funktion indexOf in der zweiten Zeile einen positiven Integer Wert, nämlich die Position des Strings opt= im Cookiestring aus. Der Autor des Scripts war aber darum bemüht, dass der weitere Verlauf des Scripts nur zur Ausführung kommt, wenn opt nicht bereits gesetzt wurde. Ist dies der Fall, so setzt er in den nächsten Zeilen des Cookie selber, versehen mit einem Timeout von einer Stunde.

Dieser erste Codeblock erfüllt mehrere Zwecke:

  1. Das Script wird auch bei mehreren Injektionen nur einmal binnen einer gewissen Zeitspanne ausgeführt, nämlich bei der ersten Instanzierung wenn kein Cookie existiert.
  2. Dadurch, dass das Objekt document.cookie im Regelfall in Sandbox Umgebung, wir der von uns angestrebten, nicht existiert, wird die automatisierte Analyse z.B. durch Web Gateways erschwert. Es existieren zwar weitaus perfidere Methoden, derartige Fallen zu implementieren, weshalb wir hier nicht im Detail darauf eingehen wollen.

Stattdessen ist unser Ziel, die Ausführung des Codes zu ermöglichen. Das erreichen wir sehr einfach, indem wir das gewünschte Objekt selber bauen. document.cookie ist ein String Wert, der im Falle eines Webbrowsers sowohl lesend wie auch schreibend genutzt werden kann. Wir fügen daher folgende Definition zu Beginn des Scripts ein:

// Emulate document.cookie behaviour
function dummyStruct() {
	this.cookie = "Foo";
}
document = new dummyStruct();

Durch diese Änderung wird unser Script weitgehend lauffähig. $a wird der Wert Foo aus unserer Dummy-Struktur zugewiesen. Darin ist der String opt= natürlich nicht enthalten, weshalb b den Wert -1 erhält und das Script weiter ausgeführt wird. $c wird anschliessend als Datumsobjekt erstellt und document.cookie wird neu gesetzt.

Nach der Ausführung des initialen Codeblocks folgt der Hauptteil des Scripts, der in einem try-Block enkapsuliert ist, um keine auffälligen Meldungen im Falle eines Fehlers während der Ausführung zu generieren. Die zahlreichen Funktionsdefinitionen, die folgen, sind für uns im Moment nur reduziert relevant, wie widmen uns daher direkt der nächsten Zeile, die direkt ausgeführt werden soll:

document["w4151r4772i8362t4773e89191269".replace(/[0-9]/g, "")](SPyDgNA("CgTm"),
	cues("YwUqxSVvI"));

Auch hier bedient sich der Autor wieder mehrerer Tricks, um seinen Code möglichst immun gegenüber automatisierten Analysen zu machen. Verschiedene Punkte sind auffällig:

  1. Die übliche, allgemein verbreitete Syntax, um eine Funktion eines Objektes aufzurufen, verwendet Punkte als Delimiter. Zum Beispiel wird mit window.close() die Funktion zum Schliessen eines Browserfensters aufgerufen. Alternativ könnte diese Funktion aber auch über die weitaus seltenere Syntax window["close"] aufgerufen werden.
  2. Der Autor will also eine Funktion des DOM Objektes document ansteuern, aber der Name der Funktion ("w4151r4772i8362t4773e89191269") erscheint seltsam. Allerdings wird auf diesen String direkt die Funktion replace(/[0-9]/g angewendet, die alle numerischen Zeichen aus dem String entfernt. Das ergibt, was der eine oder andere mittlerweile bereits selber entdeckt hat: document["write"] – die übliche Funktion, die verwendet wird, um direkt in das geladene HTML File zu schreiben.

Wie vorgängig erwähnt, dienen auch diese Mechanismen dem Aushebeln von automatisierten Filtermechanismen. Im Regelfall suchen selbige nach auffälligen Patterns, wie eben document.write oder ähnlich. Durch das Nutzen der sprachlichen Logik, wird eine musterbasierte Erkennung nahezu verunmöglicht, der Code muss interpretiert werden, um den verdächtigen Code zu identifizieren. Diese Funktionalität ist aber bislang nur sehr wenigen Content Filtern und vergleichbaren Gerätschaften vergönnt, was den Einsatz an dieser Stelle erklärt.

Wir wissen mittlerweile, dass es offensichtlich das primäre Ziel der Applikation ist, Daten zur Laufzeit in die geladene Seite zu injizieren. Die Frage ist nun, um was für Daten es sich handelt. Schauen wir uns noch einmal die Funktion an, wie wir sie vorgängig aufgeschlüsselt haben:

document["write"](SPyDgNA("CgTm"), cues("YwUqxSVvI"));

Es sollen die Rückgabewerte der Funktionen SPyDgNA und cues, jeweils mit einem vordefinierten String als Parameter, ausgegeben werden. Die beiden Funktionen werden im Script definiert und beinhalten – beide – eine liebevoll verkomplizierte Reihe von Umwandlungen, die letzten Endes zu den gewünschten Endresultaten führen, die ausgegeben werden sollen.

Während ich dem geneigten Leser nicht vorenthalten möchte, diese beiden Funktionen im Detail auseinanderzupflücken, um die genaue Funktion nachzuvollziehen, können wir es uns an dieser Stelle durch den Einsatz unseres Debuggers deutlich bequemer machen. Wir könnten in der oben dargestellten Zeile des document.write Aufrufes einfach einen Breakpoint setzen und danach über die Evaluate-Funktion in Rhino die beiden Funktionen manuell aufrufen. Diese Methode ist adäquat, um die Ausgabe von document.write hier zu eruieren, allerdings hat sie den entscheidenden Nachteil, dass wir den weiteren Verlauf des Scripts nicht mehr nachvollziehen können, weil Rhino mangels Kenntnis der Funktion document.write gegen die Wand läuft.

Wir entscheiden uns daher für die nachhaltigere Methode und implementieren in unserer bereits existenten Dummy-Klasse die Funktion write.

// Emulate document.cookie behaviour
function dummyStruct() {
	this.cookie = "Foo";
	this.write = function(str, str2) {
		var output = ""
		for (var n = 0; n < arguments.length; n++) {
			output += arguments[n];
		}
		print(output);
	}
}
document = new dummyStruct();

Die Funktion write emuliert die Ausgabe im Browser, schreibt aber die jeweiligen Strings statt in ein Browserfenster in die Funktionsvariable output und anschliessend mittels der Funktion print() in die Debugging Konsole (Window -> Console) von Rhino. Der Umweg über die output-Variable ist nötig, weil print() standardmässig einen Zeilenumbruch (\n) anfügt, was die Ausgabe bei Verwendung mehrerer Argumente unerwünschterweise verändert.

Da wir nun alle Fallstricke umgangen und unseren Ausgabekanal definiert haben, lassen wir Rhino durch das Script laufen. Es empfiehlt sich, am Ende der Funktion cues() einen Breakpoint zu setzen, um das Verhalten nach dem initialen document.write zu beobachten, falls weitere Aktionen angestrebt werden. Im vorliegenden Fall scheint sich die Funktionalität aber, zumindest zum Zeitpunkt des ersten Ladevorgangs, auf den analysierten Funktionsaufruf zu beschränken.

Nachdem das Script vollständig durchgelaufen ist, können wir über die Konsole den HTML Code betrachten, den das Script via document.write in die betroffene Webseite zu schreiben versucht:

Ausgabe des Schadcodes durch den neu definierten Ausgabekanal

Schlussfolgerung

Betrachten wir nun die Ausgabe, so wird schnell klar, dass es sich beim Script um einen klassischen Loader für Drive-by Angriffe handelt. Die geladene Seite enthält weitere Scripts, die durch das geschriebene iFrame nachgeladen und ausgeführt werden. Die Domäne yahoosite . ru, die wir an dieser Stelle bewusst weder korrekt ausschreiben noch verlinken möchte, ist übrigens eine bekannte FastFlux Ressource und scheint, zumindest den 1,6 Millionen Google Resultaten zu Folge, schon länger verwendet zu werden.

Über den Autor

Stefan Friedli

Stefan Friedli gehört zu den bekannten Gesichtern der Infosec Community. Als Referent an internationalen Konferenzen, Mitbegründer des Penetration Testing Execution Standard (PTES) und Vorstandsmitglied des Schweizer DEFCON Group Chapters trägt er aktiv zum Fortschritt des Segmentes bei.

Links

Sie wollen die Resistenz Ihres Unternehmens auf Malware prüfen?

Unsere Spezialisten kontaktieren Sie gern!

×
Active Directory-Zertifikatsdienste

Active Directory-Zertifikatsdienste

Eric Maurer

Konkrete Kritik an CVSS4

Konkrete Kritik an CVSS4

Marc Ruef

Das neue NIST Cybersecurity Framework

Das neue NIST Cybersecurity Framework

Tomaso Vasella

Angriffsmöglichkeiten gegen Generative AI

Angriffsmöglichkeiten gegen Generative AI

Andrea Hauser

Sie wollen mehr?

Weitere Artikel im Archiv

Sie brauchen Unterstützung bei einem solchen Projekt?

Unsere Spezialisten kontaktieren Sie gern!

Sie wollen mehr?

Weitere Artikel im Archiv