
Im Rahmen dieses Artikels werden wir den Prozess des Schreibens eines Exploits für eine Sicherheitsanfälligkeit in Microsoft Edge mit dem anschließenden Beenden der Sandbox ausreichend detailliert betrachten. Wenn Sie wissen möchten, wie dieser Prozess aussieht, begrüßen Sie unter cat!
Einführung
Spätestens Pwn2Own 2019
in Montreal wurde in der Kategorie Browser ein Exploit zum Hacken von Microsoft Edge
demonstriert . Hierfür wurden zwei Sicherheitsanfälligkeiten verwendet: double free
im Renderer und eine logische Sicherheitsanfälligkeit beim Beenden der Sandbox. Diese beiden Sicherheitsanfälligkeiten wurden kürzlich geschlossen und mit dem entsprechenden CVE
: CVE-2019-0940
und CVE-2019-0938
. Weitere Informationen zu Sicherheitslücken finden Sie im Blog: Pwn2Own 2019: Microsoft Edge Renderer Exploitation (CVE-2019-0940). Teil 1 und Pwn2Own 2019: Microsoft Eedge Sandbox Escape (CVE-2019-0938). Teil 2 .
Als Teil unseres Artikels möchten wir am Beispiel eines Microsoft Edge
unter Windows 10
Verwendung von CVE-2017-0240
und CVE-2016-3309
zeigen, wie ein solcher Exploit geschrieben wird und wie viel Zeit und Ressourcen dafür benötigt werden. Einer der Unterschiede besteht darin, dass in unserem Szenario die Sicherheitsanfälligkeit im Windows 10
Kernel zum Beenden der Sandbox verwendet wird, wenn der in Pwn2Own
demonstrierte Exploit eine logische Sicherheitsanfälligkeit zum Beenden der Sandbox verwendet hat. Wie Patches von Microsoft
zeigen, gibt es im Kernel viel mehr Schwachstellen als Schwachstellen bei der Implementierung der Sandbox. Infolgedessen ist es viel wahrscheinlicher, dass eine solche Kette von Schwachstellen auftritt, und es ist nützlich, dies für IS-Mitarbeiter in Unternehmen zu wissen.
Ausgangsdaten
Dieser Artikel behandelt den Prozess des Schreibens eines eintägigen Exploits für den Microsoft Edge
Browser. CVE-2017-0240
wird betrieben. Die erste Phase des Betriebs basiert auf Materialien aus der Quelle [1], wir erhalten ein arbitrary address read/write
und lernen verschiedene Techniken kennen, die bei der Ausnutzung solcher Schwachstellen hilfreich sein können. Als Nächstes stellen wir Ihnen das Tool pwn.js
, mit dem Sie beliebige Funktionen aufrufen können, die auf willkürlichem Lesen und Schreiben basieren, und die verschiedene mitigations
und Möglichkeiten zur Umgehung dieser Funktionen berücksichtigen. In der letzten Phase wird die Windows CVE-2016-3309
Kernel-Sicherheitsanfälligkeit ausgenutzt, um die Berechtigungen zu erhöhen, AppContainer
Einschränkungen zu umgehen und die vollständige Kontrolle über den angegriffenen Computer zu erlangen.
Der Betrieb wird am Stand mit Microsoft Windows 10 Pro 1703 (10.0.15063)
und dem Microsoft Edge (40.15063.0.0)
Browser Microsoft Edge (40.15063.0.0)
.
Schritt 1. Erhalten eines arbitrary address read/write
eine arbitrary address read/write
Beschreibung der Sicherheitsanfälligkeit und des Erhaltens von OOB
In der copyFromChannel- Methode des Audio Buffer- Objekts use-after-free
eine Sicherheitsanfälligkeit vom Typ use-after-free
vorhanden.
AudioBuffer ist die Schnittstelle eines kurzen Audio-Assets im Speicher, das aus einer Audiodatei mit der Methode AudioContext.decodeAudioData () oder aus den Quelldaten mit der Methode AudioContext.createBuffer () erstellt wurde. In AudioBuffer platzierte Audiodaten können in AudioBufferSourceNode wiedergegeben werden.
Die Präsentation von The Advanced Exploitation of 64-bit Edge Browser Use-After-Free Vulnerability on Windows 10
bietet eine detaillierte Analyse der Sicherheitsanfälligkeit und des Patches. Wenn die copyFromChannel
Methode copyFromChannel
, wird der Inhalt des copyFromChannel
in den durch das erste Argument angegebenen copyFromChannel
kopiert. Die Methode akzeptiert auch die Kanalnummer ( channelNumber
) und den Offset im startInChannel
( startInChannel
), ab dem kopiert werden muss. Vor dem direkten Kopieren von Daten zum destination
in der Funktion CDOMAudioBuffer::Var_copyFromChannel
der CDOMAudioBuffer::Var_copyFromChannel
zwischengespeichert (die Adresse und Größe des Puffers werden in lokalen Funktionsvariablen auf dem Stapel gespeichert) und die channelNumber
und startInChannel
werden in den Typ Int
startInChannel
, für den die valueOf
Methode der konvertierten Objekte aufgerufen wird. Die Sicherheitsanfälligkeit besteht darin, dass ein zwischengespeicherter Puffer zum Zeitpunkt der Typkonvertierung in der überschriebenen Methode des valueOf
Objekts valueOf
kann. Zur Überprüfung verwenden wir den folgenden Code:
Dieser Code verwendet die Web Workers
Technologie, um den Puffer freizugeben. Nachdem wir einen leeren Worker
, können wir ihm mithilfe der postMessage
Methode eine Nachricht senden. Das zweite optionale transfer
dieser Methode verwendet ein Array Transferable
Objekte ( ArrayBuffer
, MessagePost
oder ImageBitmap
). Die Rechte an dem Objekt werden an Worker
und das Objekt ist im aktuellen Kontext nicht mehr verfügbar, sodass es gelöscht werden kann. Danach erfolgt ein Aufruf zum sleep
- eine Funktion, die die Ausführung eines Programms vorübergehend stoppt (sie wird unabhängig implementiert). Dies ist erforderlich, damit das Garbage Collection-System ( GC
, Garbage Collector
) den Puffer freigeben kann, dessen Rechte übertragen wurden.
Web Worker bieten eine einfache Möglichkeit, Skripte im Hintergrundthread auszuführen. Worker-Thread kann Aufgaben ausführen, ohne die Benutzeroberfläche zu beeinträchtigen. Darüber hinaus können sie E / A mit XMLHttpRequest ausführen (obwohl die Attribute responseXML und channel immer null sind). Ein vorhandener Worker kann über den in diesem Code angegebenen Ereignishandler JavaScript-Nachrichten an den Erstellercode senden (und umgekehrt).
Wenn Sie diesen Code in Edge unter dem Debugger ausführen, kann der folgende Absturz auftreten.

Infolgedessen versucht der Aufruf von copyFromChannel
, den Inhalt des copyFromChannel
in den nicht zugewiesenen Speicherbereich zu kopieren. Um die Sicherheitsanfälligkeit auszunutzen, müssen alle Objekte in diesem Speicherbereich zugeordnet werden. In diesem Fall ist das Array-Segment perfekt.
Arrays in Chakra
(die im Edge
Browser verwendete JS
Engine) sind wie folgt angeordnet: Das Array-Objekt hat eine feste Größe, die Zeiger auf die Array-Objekte (oder Werte im Fall von IntArray
) werden in einem separaten Speicherbereich gespeichert - dem Segment, dessen Zeiger im Objekt enthalten ist Array. Der Segmentheader enthält verschiedene Informationen, einschließlich der Größe des Segments, die der Größe des Arrays entspricht. Die Größe des Arrays ist auch im Array-Objekt selbst vorhanden. Schematisch sieht es so aus:

Wenn es uns also gelingt, das Array-Segment im zuvor freigegebenen Bereich auszuwählen, können wir den Header des Array-Segments mit dem Inhalt des Audiopuffers überschreiben. Dazu ändern wir den obigen Code, indem wir nach dem sleep(1000);
die folgenden Zeilen hinzufügen sleep(1000);
::
... arr = new Array(128); for(var i = 0; i < arr.length; i++) { arr[i] = new Array(0x3ff0); for(var j = 0; j < arr[i].length; j++) arr[i][j] = 0x30303030; } ...
Die Größe der Arrays wird so ausgewählt, dass die Größe des Arraysegments das gesamte Heap-Segment einnimmt (das minimale unteilbare Stück Heap-Speicher, dessen Größe 0x10000 Byte beträgt). Führen Sie diesen Code aus und geben Sie die memcpy
Funktion als Haltepunkt an (es werden viele memcpy
Aufrufe ausgeführt, daher ist es sinnvoll, zuerst bei edgehtml!WebCore::AudioBufferData::copyBufferData
), in dem der Absturz aufgetreten ist. Wir erhalten folgendes Ergebnis:

Großartig! Jetzt können wir den Header des Array-Segments mit unseren eigenen Werten überschreiben. Die interessantesten Werte in diesem Fall sind die Größe des Arrays, deren Versatz wir im obigen Screenshot sehen können. Ändern Sie den Inhalt des Audiopuffers wie folgt:
... var t = audioBuf.getChannelData(0); var ta2 = new Uint32Array(t.buffer); ta2[0] = 0; ta2[1] = 0; ta2[2] = 0xffe0; ta2[3] = 0; ta2[4] = 0; ta2[5] = 0; ta2[6] = 0xfba6; ta2[7] = 0; ta2[8] = 0; ta2[9] = 0x7fffffff - 2; ta2[10] = 0x7fffffff; ta2[11] = 0; ta2[12] = 0; ta2[13] = 0; ta2[14] = 0x40404040; ta2[15] = 0x50505050; ...
ta2[14]
auf die Werte von ta2[14]
und ta2[15]
- sie beziehen sich bereits nicht auf den ta2[15]
, sondern auf die Array-Werte selbst. Damit können wir das Array, das wir im globalen arr
Array benötigen, wie folgt bestimmen:
... for(var i = 0; i < arr.length; i++) { if(arr[i][0] == 0x40404040 && arr[i][1] == 0x50505050) { alert('Target array idx: ' + i); target_idx = i; target_arr = arr[i]; break; } }
Wenn als Ergebnis ein Array gefunden wurde, dessen erste beiden Elemente auf bestimmte Weise geändert wurden, ist alles in Ordnung. Jetzt haben wir ein Array, dessen Segmentgröße größer ist als es tatsächlich ist. Die restlichen Arrays können freigegeben werden.
Hierbei ist zu beachten, dass die Größe des Arrays in zwei Entitäten vorhanden ist: im Array-Objekt, in dem es unverändert blieb, und im Array-Segment, in dem wir es vergrößert haben. Es stellt sich heraus, dass die Größe im Array-Objekt ignoriert wird, wenn der Code im JIT
Modus ausgeführt und optimiert wurde. Dies ist beispielsweise wie folgt leicht zu erreichen:
function arr_get(idx) { return target_arr[idx]; } function arr_set(idx, val) { target_arr[idx] = val; } for(var i = 0; i < 0x3ff0; i++) { arr_set(i, arr_get(i)); }
Danach können Sie mit den arr_set
arr_get
und arr_set
die Grenzen des Arrays überschreiten ( OOB
, out-of-bound
).
Verwenden von OOB
, um das Grundelement Lesen und Schreiben an eine beliebige Adresse zu übertragen
In diesem Abschnitt betrachten wir eine Technik, mit der Sie mit OOB
eine beliebige Adresse lesen und schreiben können. Die Methode, mit der wir dies erhalten, wird der in der Quelle [1] verwendeten ähnlich sein, es wird jedoch auch signifikante Änderungen geben.
In der verwendeten Version von Edge
Speicherblöcke für den Heap nacheinander zugewiesen, wodurch sie beim Zuweisen einer großen Anzahl von Objekten früher oder später nach dem Arraysegment angezeigt werden, über das wir hinausgehen können.
Erstens können wir einen Zeiger auf eine virtuelle Tabelle von Objektmethoden ( vftable
) vftable
, um die Randomisierung des Prozessadressraums ( ASLR
) zu umgehen. Aber der Zugriff auf welche Objekte hilft uns, willkürliches Lesen und Schreiben zu erreichen? Einige DataView
Objekte eignen sich hervorragend dafür.
Die DataView bietet eine einfache Schnittstelle zum Lesen und Schreiben mehrerer numerischer Typen in einem binären ArrayBuffer, unabhängig von der Reihenfolge der Plattformbytes.
Die interne Struktur der DataView
enthält einen Zeiger auf einen Puffer. Zum Lesen und Schreiben an eine beliebige Adresse können wir beispielsweise eine Kette von zwei DataView
( dv1
und dv2
) wie folgt dv1
: dv1
als dv1
Puffer die Adresse dv2
indem Sie auf das Array zugreifen. Mit dv1
wir nun die Adresse des dv2
Puffers dv2
, wodurch willkürliches Lesen und Schreiben erreicht wird. Schematisch kann dies wie folgt dargestellt werden:

Um diese Methode verwenden zu können, müssen Sie lernen, wie Sie die Adressen von Objekten im Speicher ermitteln. Dazu gibt es die folgende Technik: Sie müssen ein neues Array
erstellen, mit OOB
dessen vftable
und typeId
(die ersten beiden 64-Bit-Felder der Struktur) vftable
und das Objekt, für das die Adresse von Interesse ist, dem ersten Element des Arrays zuweisen. Anschließend müssen Sie die zuvor gespeicherten typeId
vftable
und typeId
wiederherstellen. Nun kann das Junior- und Senior-Doppelwort der Objektadresse erhalten werden, indem auf das erste und zweite Element des Arrays Bezug genommen wird. Tatsache ist, dass das neue Array standardmäßig IntArray
ist und 4-Byte-Werte des Arrays IntArray
in seinem Segment gespeichert werden. Wenn Sie einem Array ein Objekt zuweisen, wird das Array in ein ObjectArray
konvertiert und in seinem Segment werden die Adressen der Objekte gespeichert. Die Konvertierung ändert vftable
und typeId
. Wenn wir also die ursprünglichen Werte vftable
und typeId
wiederherstellen, können wir über die Elemente dieses Arrays direkt auf das Segment zugreifen. Der schematisch beschriebene Prozess kann wie folgt dargestellt werden:

Die Funktion zum Abrufen der Adresse sieht folgendermaßen aus:
function addressOf(obj) { var hdr_backup = new Array(4);
Eine offene Frage bleibt die Erstellung der notwendigen Objekte und deren Suche mit OOB
. Wie bereits erwähnt, werden sie beim Zuweisen einer großen Anzahl von Objekten früher oder später nach dem Array-Segment hervorstechen, über das wir hinausgehen können. Um die erforderlichen Objekte zu finden, müssen Sie nur die Indizes außerhalb des Arrays durchsuchen, um nach den erforderlichen Objekten zu suchen. Weil Alle Objekte desselben Typs befinden sich in einem Segment des Heapspeichers. Sie können die Suche optimieren und die Segmente des 0x10000
in Schritten von 0x10000
und nur die ersten Werte ab dem Beginn jedes Segments des 0x10000
überprüfen. Um Objekte zu identifizieren, können Sie ihnen eindeutige Werte für einige Parameter festlegen (z. B. für DataView
kann dies byteOffset
) oder mithilfe der bereits bekannten Konstanten in der Struktur des Objekts (in der verwendeten Version von Edge
in IntArray
wird der Wert 0x10005
immer bei 0x18
).
Durch Kombinieren aller oben genannten Techniken können Sie lesen und an eine beliebige Adresse schreiben. Unten finden Sie einen Screenshot zum Lesen von DataView
Speicherobjekten.

Schritt 2. Ausführen beliebiger API-Funktionen
Zu diesem Zeitpunkt konnten wir bei der Anzeige von Edge
Inhalten an eine beliebige Adresse lesen und schreiben. Berücksichtigen Sie die wichtigsten Technologien, die den weiteren Betrieb der Anwendung und ihre Problemumgehungen beeinträchtigen sollten. Wir haben bereits eine kurze Reihe von Artikeln über app specific security mitigation
( Teil 1, Einführung , Teil 2, Internet Explorer und Edge , Teil 3, Google Chrome ). Beachten Sie jedoch, dass Entwickler nicht stillstehen und ihren Produkten neue Tools hinzufügen Schutz.
Adressraum-Randomisierung ( ASLR
)
ASLR (English Address Space Layout Randomization) ist eine Technologie, die in Betriebssystemen verwendet wird und die Position wichtiger Datenstrukturen im Prozessadressraum zufällig ändert, nämlich: ausführbare Dateibilder, geladene Bibliotheken, Heaps und stapeln.
Oben haben wir gelernt, die Adressen von virtuellen Klassentabellen zu lesen. Mit ihnen können wir die Basisadresse des Chakra.dll
Moduls leicht berechnen, sodass ASLR
keine Probleme für den weiteren Betrieb darstellt.
Datenausführungsschutz ( DEP
, NX
)
Data Execution Prevention (DEP) ist eine in Linux, Mac OS X, Android und Windows integrierte Sicherheitsfunktion, die verhindert, dass eine Anwendung Code aus einem Speicherbereich ausführt, der als "Nur Daten" gekennzeichnet ist. Dadurch werden einige Angriffe verhindert, bei denen beispielsweise Code in einem solchen Bereich mithilfe von Pufferüberläufen gespeichert wird.
Eine Möglichkeit, diesen Schutz zu VirtualAlloc
, besteht darin, VirtualAlloc
mithilfe von ROP
Ketten VirtualAlloc
. Im Fall von Edge
funktioniert diese Methode jedoch aufgrund von ACG
(siehe unten).
Kontrollflussschutz ( CFG
)
CFG
ist ein Schutzmechanismus, der die Ausnutzung binärer Schwachstellen in Anwendungen im Benutzer- und Kernelmodus erschweren soll. Die Arbeit dieses Mechanismus besteht in der Validierung indirekter Aufrufe, die verhindern, dass ein Angreifer den Ausführungsthread abfängt (z. B. durch Überschreiben der Tabelle der virtuellen Funktionen).
Diese Technologie steuert nur indirekte Aufrufe, z. B. Methodenaufrufe aus der virtuellen Tabelle der Objektfunktionen. Rücksprungadressen auf dem Stapel werden nicht gesteuert, und dies kann zum Erstellen von ROP
Ketten verwendet werden. Die zukünftige Verwendung von ROP/JOP/COP
Ketten kann durch Intel
neue Technologie von Intel
behindert werden: die Control-flow Enforcement Technology
( CET
). Diese Technologie besteht aus zwei Teilen:
Shadow Stack
(Schattenstapel) - wird zur Steuerung von Rücksprungadressen und zum Schutz vor ROP
Ketten verwendet.Indirect Branch Tracking
ist eine Methode zum Schutz vor JOP/COP
Ketten. Es ist eine neue ENDBRANCH
Anweisung, die alle gültigen Übergangsadressen für call
und jmp
Anweisungen jmp
.
Arbitrary Code Guard ( ACG
)
ACG
ist eine Technologie, die die dynamische Codegenerierung verhindert (es ist verboten, rwx
Speicherbereiche mit VirtaulAlloc
) und deren Änderungen (es ist unmöglich, VirtaulAlloc
verfügbaren Speicherbereich als ausführbare Datei VirtaulAlloc
).
Dieser Schutz verhindert wie CFG
nicht die Verwendung von ROP
Ketten.
AppContainer-Isolierung
AppContainer ist eine Microsoft-Technologie, mit der Sie einen Prozess isolieren können, indem Sie ihn in einer Sandbox-Umgebung ausführen. Diese Technologie beschränkt den Zugriff des Prozesses auf Anmeldeinformationen, Geräte, das Dateisystem, das Netzwerk, andere Prozesse und Fenster und zielt darauf ab, das Potenzial von schädlicher Software zu minimieren, die in der Lage ist, beliebigen Code im Prozess auszuführen.
Dieser Schutz verkompliziert den Betriebsprozess erheblich. Aus diesem Grund können wir keine ausführbaren Dateien von Drittanbietern aufrufen oder auf vertrauliche Benutzerinformationen im Speicher oder auf Datenträgern zugreifen. Dieser Schutz kann jedoch überwunden werden, indem Schwachstellen bei der Implementierung der AppContainer-Sandbox verwendet werden oder indem die Berechtigungen durch Ausnutzen von Schwachstellen im Kernel des Betriebssystems erhöht werden.
Es ist erwähnenswert, dass Microsoft
ein separates Belohnungsprogramm für Techniken zur Umgehung von security mitigation
hat. Das Programm gibt an, dass die Wiederverwendung von ausführbarem Code (das ROP
Ketten ist eine Variation dieser Technik) nicht unter das Programm fällt, da ist ein architektonisches Problem.
Verwenden von pwn.js
Aus einer Analyse aller Sicherheitstechnologien folgt, dass Sie die AppContainer
Sandbox umgehen müssen, um beliebigen Code ausführen zu können. In diesem Artikel beschreiben wir eine Methode, die eine Sicherheitsanfälligkeit im Windows
Kernel verwendet. In diesem Fall können wir nur JS
Code und ROP
Ketten verwenden. Das Schreiben eines Exploits für den Kernel mit nur ROP
Ketten kann sehr schwierig sein. Um diese Aufgabe zu vereinfachen, finden Sie eine Reihe von Gadgets, mit denen wir die erforderlichen WinAPI
Methoden aufrufen können. Glücklicherweise ist dies bereits in der Bibliothek pwn.js
implementiert. Wenn Sie nur die read
und write
für beliebiges Lesen und Schreiben beschrieben haben, erhalten Sie eine praktische API
um die erforderlichen WinAPI
Funktionen zu finden und aufzurufen. pwn.js
bietet auch ein praktisches Tool zum Arbeiten mit 64-Bit-Werten und Zeigern sowie Tools zum Arbeiten mit Strukturen.
Betrachten Sie ein einfaches Beispiel. Im vorherigen Schritt haben wir eine Kette von zwei verwandten DataView
. Um den Exploit vorzubereiten, müssen Sie die folgende Klasse erstellen:
var Exploit = (function() { var ChakraExploit = pwnjs.ChakraExploit; var Integer = pwnjs.Integer; function Exploit() { ChakraExploit.call(this); ...
, MessageBoxA
:
function run() { with (new Exploit()) {
:

3.
WinAPI
. . CVE-2016-3309
. [7] [8], pwn.js
[2] , GDI
-. [9], [10] [11]. . , . , AppContainer
, pwn.js
. cmd.exe
SYSTEM
.
GDI — Windows , , .
, . JS
- 64- kernel_read_64
kernel_write_64
, . Windows. BITMAP
, . pwn.js
. BITMAP
, , :
var BITMAP = new StructType([ ['poolHeader', new ArrayType(Uint32, 4)],
Tid
KTHREAD
, , , EmpCheckErrataList
, . , :
... var nt_EmpCheckErrataList_ptr = worker_bitmap_obj.Tid.add(0x2a8); var nt_EmpCheckErrataList = kernel_read_64(nt_EmpCheckErrataList_ptr); var ntoskrnl_base_address = nt_EmpCheckErrataList.sub( g_config.nt_empCheckErrataList_offset); ...
, AppContainer
. AppContainer
IsPackagedProcess
( Process Environment Block
, PEB
), . Access Token
, AppContainer
. Access Token
, . Access Token
, . EPROCESS
ActiveProcessLinks
, . PEB
EPROCESS
. PsInitialSystemProcess
, , ActiveProcessLinks
.
Edge
: , Edge
. SYSTEM
. , , winlogon.exe
.
pwn.js
:
:

YouTube , Microsoft Edge.
Zusammenfassung
:
- ,
Edge
Windows
, 13 , CVE-2017-0240
, . CVE-2016-3309
. JS
- 666
JS
- :
cmd.exe
SYSTEM
,
, , . , , . .
Material
- Liu Jin — The Advanced Exploitation of 64-bit Edge Browser Use-After-Free Vulnerability on Windows 10
- Andrew Wesie, Brian Pak — 1-Day Browser & Kernel
Exploitation - Natalie Silvanovich — The ECMA and the Chakra. Hunting bugs in the Microsoft Edge Script Engine
- Natalie Silvanovich — Your Chakra Is Not Aligned. Hunting bugs in the Microsoft Edge Script Engine
- phoenhex team — cve-2018-8629-chakra.js
- Quarkslab — Exploiting MS16-145: MS Edge TypedArray.sort Use-After-Free (CVE-2016-7288)
- Exploiting MS16-098 RGNOBJ Integer Overflow on Windows 8.1 x64 bit by abusing GDI objects
- Siberas — Kernel Exploitation Case Study — "Wild" Pool Overflow on Win10 x64 RS2 (CVE-2016-3309 Reloaded)
- Saif El-Sherei — Demystifying Windows Kernel Exploitation by Abusing GDI Objects
- Diego Juarez — Abusing GDI for ring0 exploit primitives
- Nicolas A. Economou — Abusing GDI for ring0 exploit
primitives: Evolution - pwn.js