刚开始入门v8,看到这个issue好像难度不是很大,所以就仔细研究一下,算是研究的第一个v8漏洞了
漏洞分析
在v8/src/heap/factory.cc
文件的NewFixedDoubleArray
函数中可以发现开发人员对length
进行了长度的检查,即DCHECK_LE(0, length)
。但由于DCHECK
只在debug
中起作用,而在release
中并不起作用,则该检查对正式版本并没有什么作用。如果length
为负数,则会绕过if(length > FixedDoubleArray::kMaxLength)
的检查,而由于int size=FixedDoubleArray::SizeFor(length)
会使用length
计算出size
,如果我们合理控制length
,则可以让size
计算出来为正数。
1 | // v8/src/heap/factory.cc |
漏洞点就在于NewFixedDoubleArray
函数传入了一个负数的length
,可以绕过检查;然后在sizeFor
函数中,使得计算出的size
为一个正数,那么就会申请一个正数的elements
空间。那么最终就会使得这个数组对象,element
空间是正常的,但是length
是一个极大的负数,也就是造成了这个数组对象可以越界。
漏洞触发
接下来就分析这个漏洞的触发路径。
这里在v8/src/builtins/builtins-array.cc
文件中的ArrayPrototypeFill
函数可以调用触发NewFixedDoubleArray
函数。
1 | BUILTIN(ArrayPrototypeFill) { |
上述函数,有几个需要关注的点。
首先是调用了GetRelativeIndex
函数,来分别获取start
和end
处的值。
1 | // If |index| is Undefined, returns init_if_undefined. |
在GetRelativeIndex
函数中,可以看到调用了Object::ToInteger
函数。该函数与我之前调试的2019数字经济Browser
题目中的Object::ToNumber
函数类似,都是可以去调用用户自定义函数。调用链如下:
1 | // static |
其次是调用了TryFastArrayFill
,其中的accessor->Fill
函数会调用Object FillImpl
,在调用之前需要注意传入的参数end
和start
是32
位无符号数,而其又是由double
类型的start_index
和end_index
两个数转换过来。所以这里就存在一个64
位到32
位是的截断,导致大小出现变化。
1 | V8_WARN_UNUSED_RESULT bool TryFastArrayFill( |
在Object* FillImpl
函数中如果end
大于原数组的capacity
,则会调用GrowCapacityAndConvertImpl
函数来对数组进行扩容。
1 | static Object* FillImpl(Handle<JSObject> receiver, Handle<Object> obj_value, |
最终其会调用漏洞函数NewFixedDoubleArray
,调用链如下
1 | static void GrowCapacityAndConvertImpl(Handle<JSObject> object, |
漏洞点总结
上面大概讲了一下漏洞的触发路径,现在我们全面总结一下漏洞点:
1、 在 ArrayPrototypeFill
函数中,会调用GetRelativeIndex
函数来获取数组的边界;
2、 在GetRelativeIndex
函数中,可以最终调用我们的自定义函数,来修改数组的大小;
3、 当数组的大小被修改后,就使得当前数组的长度与原长度不匹配,所以会调用TryFastArrayFill
来尝试填充数组,传入的参数数组的边界start_index
和end_index
都是double
类型
4、 在TryFastArrayFill
,会调用Object* FillImpl
来填充原数组,传入的参数uint32
类型的start
和end
由原来的double
类型start_index
和end_index
转变而来;
5、 在Object* FillImpl
发现现有数组边界end
大于原数组大小capacity
时,会调用GrowCapacityAndConvertImpl
函数来扩容数组;
6、 最终经过多次调用会进入漏洞函数NewFixedDoubleArray
去创建新数组,传入的参数为uint32
的end
。
7、 在NewFixedDoubleArray
会将传入的参数uint32 end
转换为int length
,将无符号数转换为了整数int
类型,那么则能触发漏洞。
如果我们在第2步中,将数组大小修改为一个0x80000000
;那么经过第4步转换时,此时end=0x80000000
;经过传递进入第7步时,length=0x80000000
,由于length
是一个int
类型,则变成了一个负数,刚好可以绕过前面的检查。随后让计算出来的size=8
,那么就会申请element
空间为8,但是length=0x80000000
,则可以数组越界。
漏洞利用
漏洞触发POC
如下:
1 | array = []; |
当执行了上面的POC
后,内存布局如下:
1 | //arrary |
从上图中可以看到,触发漏洞后array
的elements
的length
为一个巨大的数值,而申请的elements
空间却为0。并且由于arrary.length=0x100
,所以存在一个数组越界。
EXP
1 | var buf =new ArrayBuffer(16); |
参考文献
https://bugs.chromium.org/p/project-zero/issues/detail?id=1793
- 本文作者: A1ex
- 本文链接: http://yoursite.com/2021/09/24/Chrome-issue-1793分析/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!