首页
社区
课程
招聘
[原创]v8利用初探 2019 StarCTF oob 复现分析
发表于: 2021-8-16 17:00 28464

[原创]v8利用初探 2019 StarCTF oob 复现分析

2021-8-16 17:00
28464

新人第一次分析v8利用,v8的资料在墙内墙外都挺多的,但质量良莠不齐,过程中也是走了不少的弯路,踩了不少的坑,不过最后还是成功复现了一次完整的利用,特此记录一下。

成功弹出计算器

我们仅仅需要关注diff文件的22-42行即可,其它部分的删改只是为了能让v8正确地编译通过。
这个diff文件给Array对象增加了一个oob方法,提供了数组越界读写的功能,不提供参数时为越界读(arr.oob())
提供一个参数时为越界写(arr.oob(val))。

1、辅助输出的js函数,实现浮点数和整数相互转换的功能

2、v8内置的辅助函数

3、不同数据类型的表示方法

做实验:首先创建了一个长度为2的浮点数数组,然后用DebugPrint输出该数组的调试信息。
我们需要关注Array对象的地址和Array对象的map域和element域。
element域可以理解为一个指针,指向的是这个Array具体保存的数据。
map域比较复杂但是很重要,可以简单理解成v8用它来表示这是一个什么样的对象,以及采用什么样的方法来对这个对象进行各种操作。

切换到调试器模式看内存:
map的偏移为0,elements的偏移为0x10

elements指向的区域就在这个Array对象的正前方:
其中0x3ff199999999999a 0x400199999999999a就是1.1和2.2在内存中的64位表示。
再往前的0x00000838ec0414f9 0x0000000200000000则分别代表另一个对象的map域和这个数组的长度(smi类型)

所以题目提供的array.oob()方法恰好能对该array的map域进行越界读写

首先要实现取对象地址功能和在任意地址伪造对象的功能。
刚才已经提到过v8通过对象的map域来决定对象的数据类型以及操作方法。
所以下面我们先定义了一个长度为1的对象数组,其唯一的一个元素是一个对象。
再定义了一个长度为4的浮点数数组。
利用oob方法越界读取出这两个数组的map对象的地址值。
获取任意对象地址:
我们先把需要获取地址的对象放入对象数组中,然后将这个对象数组的map篡改为浮点数组的map域。此时再读取这个对象,由于map域被改成了浮点类型的map域,v8会将这个对象的地址当成一个浮点数返回给我们。
任意地址伪造对象:
与上面功能类似,这里是把浮点数组的index0处的数据改为任意地址,然后把浮点数组的map域篡改成对象数组的map域,然后在获取这个值,v8引擎则会将该地址作为一个对象返回给我们。

接下来实现的是任意地址读取的功能:
先定义一个长度为4的浮点数数组,其index0处放置的是另一个浮点数数组的map域。
然后用上面的fakeobj功能在该map域伪造另一个浮点数数组,那么伪造的这个浮点数数组的element域就对应着原来数组的arb_rw_arr[2],把这个伪造的element域指向要读取地址-0x10,再利用fakeobj完成读取功能即可

任意地址写功能实现起来比较麻烦,不能直接利用fakeobj来任意写(会报错),还需要通过ArrayBuffer和DataView来实现具体的功能,具体的原因和过程我并没深究:

有了任意地址读写功能后的最后一个问题是如何实现任意代码执行。
这里需要提到的是v8内嵌的wasm字节码在内存中是以RWX的权限保存的。
大致的利用思路:
1、分配一个WebAssembly对象。
2、本地调试找到字节码存放的具体地址
3、利用任意写功能把shellcode写到该地址处
4、调用该wasm对象,完成任意代码执行。

1、Exploiting v8: *CTF 2019 oob-v8
2、V8 Exploitation : Star CTF 2019 OOB-v8 | by 0verflowme | Medium

# 全局vpn
# 下载Google的环境部署工具
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
# 配置环境变量
echo "export PATH=/home/pwn/tools/depot_tools:$PATH" >> ~/.bashrc
# 获取v8源码
fetch v8
cd v8
# 切换至指定的commit版本
git checkout 6dc88c191f5ecc5389dc26efa3ca0907faef3598
# 工具同步
gclient sync
# 应用题目的补丁文件
git apply ../oob.diff
# 编译release版本
./tools/dev/v8gen.py x64.release
ninja -C ./out.gn/x64.release
# 编译debug版本
./tools/dev/v8gen.py x64.debug
ninja -C ./out.gn/x64.debug
# 全局vpn
# 下载Google的环境部署工具
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
# 配置环境变量
echo "export PATH=/home/pwn/tools/depot_tools:$PATH" >> ~/.bashrc
# 获取v8源码
fetch v8
cd v8
# 切换至指定的commit版本
git checkout 6dc88c191f5ecc5389dc26efa3ca0907faef3598
# 工具同步
gclient sync
# 应用题目的补丁文件
git apply ../oob.diff
# 编译release版本
./tools/dev/v8gen.py x64.release
ninja -C ./out.gn/x64.release
# 编译debug版本
./tools/dev/v8gen.py x64.debug
ninja -C ./out.gn/x64.debug
~/Desktop/v8/v8/v8/out.gn/x64.release$ ./d8 /home/srp8ve7ou2/Desktop/v8/starctf2019/wasm_pwn.js
~/Desktop/v8/v8/v8/out.gn/x64.release$ ./d8 /home/srp8ve7ou2/Desktop/v8/starctf2019/wasm_pwn.js
diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc
index b027d36..ef1002f 100644
--- a/src/bootstrapper.cc
+++ b/src/bootstrapper.cc
@@ -1668,6 +1668,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
                           Builtins::kArrayPrototypeCopyWithin, 2, false);
     SimpleInstallFunction(isolate_, proto, "fill",
                           Builtins::kArrayPrototypeFill, 1, false);
+    SimpleInstallFunction(isolate_, proto, "oob",
+                          Builtins::kArrayOob,2,false);
     SimpleInstallFunction(isolate_, proto, "find",
                           Builtins::kArrayPrototypeFind, 1, false);
     SimpleInstallFunction(isolate_, proto, "findIndex",
diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc
index 8df340e..9b828ab 100644
--- a/src/builtins/builtins-array.cc
+++ b/src/builtins/builtins-array.cc
@@ -361,6 +361,27 @@ V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate,
   return *final_length;
 }
 // namespace
+BUILTIN(ArrayOob){
+    uint32_t len = args.length();
+    if(len > 2) return ReadOnlyRoots(isolate).undefined_value();
+    Handle<JSReceiver> receiver;
+    ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+            isolate, receiver, Object::ToObject(isolate, args.receiver()));
+    Handle<JSArray> array = Handle<JSArray>::cast(receiver);
+    FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
+    uint32_t length = static_cast<uint32_t>(array->length()->Number());
+    if(len == 1){
+        //read
+        return *(isolate->factory()->NewNumber(elements.get_scalar(length)));
+    }else{
+        //write
+        Handle<Object> value;
+        ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+                isolate, value, Object::ToNumber(isolate, args.at<Object>(1)));
+        elements.set(length,value->Number());
+        return ReadOnlyRoots(isolate).undefined_value();
+    }
+}
 
 BUILTIN(ArrayPush) {
   HandleScope scope(isolate);
diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h
index 0447230..f113a81 100644
--- a/src/builtins/builtins-definitions.h
+++ b/src/builtins/builtins-definitions.h
@@ -368,6 +368,7 @@ namespace internal {
   TFJ(ArrayPrototypeFlat, SharedFunctionInfo::kDontAdaptArgumentsSentinel)     \
   /* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap */   \
   TFJ(ArrayPrototypeFlatMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel)  \
+  CPP(ArrayOob)                                                                \
                                                                                \
   /* ArrayBuffer */                                                            \
   /* ES #sec-arraybuffer-constructor */                                        \
diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
index ed1e4a5..c199e3a 100644
--- a/src/compiler/typer.cc
+++ b/src/compiler/typer.cc
@@ -1680,6 +1680,8 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) {
       return Type::Receiver();
     case Builtins::kArrayUnshift:
       return t->cache_->kPositiveSafeInteger;
+    case Builtins::kArrayOob:
+      return Type::Receiver();
 
     // ArrayBuffer functions.
     case Builtins::kArrayBufferIsView:
diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc
index b027d36..ef1002f 100644
--- a/src/bootstrapper.cc
+++ b/src/bootstrapper.cc
@@ -1668,6 +1668,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
                           Builtins::kArrayPrototypeCopyWithin, 2, false);
     SimpleInstallFunction(isolate_, proto, "fill",
                           Builtins::kArrayPrototypeFill, 1, false);
+    SimpleInstallFunction(isolate_, proto, "oob",
+                          Builtins::kArrayOob,2,false);
     SimpleInstallFunction(isolate_, proto, "find",
                           Builtins::kArrayPrototypeFind, 1, false);
     SimpleInstallFunction(isolate_, proto, "findIndex",
diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc
index 8df340e..9b828ab 100644
--- a/src/builtins/builtins-array.cc
+++ b/src/builtins/builtins-array.cc
@@ -361,6 +361,27 @@ V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate,
   return *final_length;
 }
 // namespace
+BUILTIN(ArrayOob){
+    uint32_t len = args.length();
+    if(len > 2) return ReadOnlyRoots(isolate).undefined_value();
+    Handle<JSReceiver> receiver;
+    ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+            isolate, receiver, Object::ToObject(isolate, args.receiver()));
+    Handle<JSArray> array = Handle<JSArray>::cast(receiver);
+    FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
+    uint32_t length = static_cast<uint32_t>(array->length()->Number());
+    if(len == 1){
+        //read
+        return *(isolate->factory()->NewNumber(elements.get_scalar(length)));
+    }else{
+        //write
+        Handle<Object> value;
+        ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+                isolate, value, Object::ToNumber(isolate, args.at<Object>(1)));
+        elements.set(length,value->Number());
+        return ReadOnlyRoots(isolate).undefined_value();
+    }
+}
 
 BUILTIN(ArrayPush) {
   HandleScope scope(isolate);
diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h
index 0447230..f113a81 100644
--- a/src/builtins/builtins-definitions.h
+++ b/src/builtins/builtins-definitions.h
@@ -368,6 +368,7 @@ namespace internal {
   TFJ(ArrayPrototypeFlat, SharedFunctionInfo::kDontAdaptArgumentsSentinel)     \
   /* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap */   \
   TFJ(ArrayPrototypeFlatMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel)  \
+  CPP(ArrayOob)                                                                \
                                                                                \
   /* ArrayBuffer */                                                            \
   /* ES #sec-arraybuffer-constructor */                                        \
diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
index ed1e4a5..c199e3a 100644
--- a/src/compiler/typer.cc
+++ b/src/compiler/typer.cc
@@ -1680,6 +1680,8 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) {
       return Type::Receiver();
     case Builtins::kArrayUnshift:
       return t->cache_->kPositiveSafeInteger;
+    case Builtins::kArrayOob:
+      return Type::Receiver();
 
     // ArrayBuffer functions.
     case Builtins::kArrayBufferIsView:
var buf = new ArrayBuffer(8); // 8 byte array buffer
var f64_buf = new Float64Array(buf);
var u64_buf = new Uint32Array(buf);
 
function ftoi(val) { // typeof(val) = float
    f64_buf[0] = val;
    return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n); // Watch for little endianness
}
 
function itof(val) { // typeof(val) = BigInt
    u64_buf[0] = Number(val & 0xffffffffn);
    u64_buf[1] = Number(val >> 32n);
    return f64_buf[0];
}
var buf = new ArrayBuffer(8); // 8 byte array buffer
var f64_buf = new Float64Array(buf);
var u64_buf = new Uint32Array(buf);
 
function ftoi(val) { // typeof(val) = float
    f64_buf[0] = val;
    return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n); // Watch for little endianness
}
 
function itof(val) { // typeof(val) = BigInt
    u64_buf[0] = Number(val & 0xffffffffn);
    u64_buf[1] = Number(val >> 32n);
    return f64_buf[0];
}
# 输出obj对象的内存信息
1%DebugPrint(obj);
# 控制权从d8转交给gdb调试器,方便观察内存数据
2%SystemBreak();
# 输出obj对象的内存信息
1%DebugPrint(obj);
# 控制权从d8转交给gdb调试器,方便观察内存数据
2%SystemBreak();
double浮点数类型:为64位浮点数的正常表示
smi整数类型:低32位为0,高32位为该整数
指针类型:最低位永远为1
double浮点数类型:为64位浮点数的正常表示
smi整数类型:低32位为0,高32位为该整数
指针类型:最低位永远为1
srp8ve7ou2@vm:~/Desktop/v8/v8/v8/out.gn/x64.debug$ gdb d8
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.
 
For help, type "help".
Type "apropos word" to search for commands related to "word"...
GEF for linux ready, type `gef' to start, `gef config' to configure
93 commands loaded for GDB 9.2 using Python engine 3.8
[*] 3 commands could not be loaded, run `gef missing` to know why.
Reading symbols from d8...
gef➤  run --allow-natives-syntax
Starting program: /home/srp8ve7ou2/Desktop/v8/v8/v8/out.gn/x64.debug/d8 --allow-natives-syntax
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff3a87700 (LWP 15179)]
V8 version 7.5.0 (candidate)
d8> var a = [1.1,2.2]
undefined
d8> %DebugPrint(a)
DebugPrint: 0x2013f4c4dd79: [JSArray]
 - map: 0x1f8bf2202ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x20ef3d611111 <JSArray[0]>
 - elements: 0x2013f4c4dd59 <FixedDoubleArray[2]> [PACKED_DOUBLE_ELEMENTS]
 - length: 2
 - properties: 0x0838ec040c71 <FixedArray[0]> {
    #length: 0x2c439fb801a9 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x2013f4c4dd59 <FixedDoubleArray[2]> {
           0: 1.1
           1: 2.2
 }
0x1f8bf2202ed9: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 32
 - inobject properties: 0
 - elements kind: PACKED_DOUBLE_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - back pointer: 0x1f8bf2202e89 <Map(HOLEY_SMI_ELEMENTS)>
 - prototype_validity cell: 0x2c439fb80609 <Cell value= 1>
 - instance descriptors #1: 0x20ef3d611f49 <DescriptorArray[1]>
 - layout descriptor: (nil)
 - transitions #1: 0x20ef3d611eb9 <TransitionArray[4]>Transition array #1:
     0x0838ec044ba1 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x1f8bf2202f29 <Map(HOLEY_DOUBLE_ELEMENTS)>
 
 - prototype: 0x20ef3d611111 <JSArray[0]>
 - constructor: 0x20ef3d610ec1 <JSFunction Array (sfi = 0x2c439fb8aca1)>
 - dependent code: 0x0838ec0402c1 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0
 
[1.1, 2.2]
d8>
srp8ve7ou2@vm:~/Desktop/v8/v8/v8/out.gn/x64.debug$ gdb d8
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.
 
For help, type "help".
Type "apropos word" to search for commands related to "word"...
GEF for linux ready, type `gef' to start, `gef config' to configure
93 commands loaded for GDB 9.2 using Python engine 3.8
[*] 3 commands could not be loaded, run `gef missing` to know why.
Reading symbols from d8...
gef➤  run --allow-natives-syntax
Starting program: /home/srp8ve7ou2/Desktop/v8/v8/v8/out.gn/x64.debug/d8 --allow-natives-syntax
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff3a87700 (LWP 15179)]
V8 version 7.5.0 (candidate)
d8> var a = [1.1,2.2]
undefined
d8> %DebugPrint(a)
DebugPrint: 0x2013f4c4dd79: [JSArray]
 - map: 0x1f8bf2202ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x20ef3d611111 <JSArray[0]>
 - elements: 0x2013f4c4dd59 <FixedDoubleArray[2]> [PACKED_DOUBLE_ELEMENTS]
 - length: 2
 - properties: 0x0838ec040c71 <FixedArray[0]> {
    #length: 0x2c439fb801a9 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x2013f4c4dd59 <FixedDoubleArray[2]> {
           0: 1.1
           1: 2.2
 }
0x1f8bf2202ed9: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 32
 - inobject properties: 0
 - elements kind: PACKED_DOUBLE_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - back pointer: 0x1f8bf2202e89 <Map(HOLEY_SMI_ELEMENTS)>
 - prototype_validity cell: 0x2c439fb80609 <Cell value= 1>
 - instance descriptors #1: 0x20ef3d611f49 <DescriptorArray[1]>
 - layout descriptor: (nil)
 - transitions #1: 0x20ef3d611eb9 <TransitionArray[4]>Transition array #1:
     0x0838ec044ba1 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x1f8bf2202f29 <Map(HOLEY_DOUBLE_ELEMENTS)>
 
 - prototype: 0x20ef3d611111 <JSArray[0]>
 - constructor: 0x20ef3d610ec1 <JSFunction Array (sfi = 0x2c439fb8aca1)>
 - dependent code: 0x0838ec0402c1 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0
 
[1.1, 2.2]
d8>
gef➤  x/4gx 0x2013f4c4dd79-1
0x2013f4c4dd78:    0x00001f8bf2202ed9    0x00000838ec040c71
0x2013f4c4dd88:    0x00002013f4c4dd59    0x0000000200000000
gef➤  x/4gx 0x2013f4c4dd79-1
0x2013f4c4dd78:    0x00001f8bf2202ed9    0x00000838ec040c71
0x2013f4c4dd88:    0x00002013f4c4dd59    0x0000000200000000
gef➤  x/10gx 0x2013f4c4dd59-1
0x2013f4c4dd58:    0x00000838ec0414f9    0x0000000200000000
0x2013f4c4dd68:    0x3ff199999999999a    0x400199999999999a
0x2013f4c4dd78:    0x00001f8bf2202ed9    0x00000838ec040c71
0x2013f4c4dd88:    0x00002013f4c4dd59    0x0000000200000000
0x2013f4c4dd98:    0x00000838ec040941    0x00000adce0993e5a
gef➤  x/10gx 0x2013f4c4dd59-1

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2021-8-16 17:02 被afa9040e编辑 ,原因:
上传的附件:
收藏
免费 5
支持
分享
最新回复 (5)
雪    币: 4168
活跃值: (15932)
能力值: ( LV9,RANK:710 )
在线值:
发帖
回帖
粉丝
2
支持
2021-8-17 13:09
0
雪    币: 4168
活跃值: (15932)
能力值: ( LV9,RANK:710 )
在线值:
发帖
回帖
粉丝
3
目前板块内浏览器相关的比较少,可以多分享一些~
2021-8-17 13:15
0
雪    币: 235
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
头像好评
2021-8-18 12:19
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
"然后用上面的fakeobj功能在该map域伪造另一个浮点数数组"

应该是在element域伪造吧
2021-11-8 11:01
0
雪    币: 264
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
师傅,v8环境可以分享一下不
2022-10-12 12:32
0
游客
登录 | 注册 方可回帖
返回
//