首页
社区
课程
招聘
[原创]强网杯2020线下GooExec
发表于: 2020-9-21 17:38 8210

[原创]强网杯2020线下GooExec

2020-9-21 17:38
8210

原文:https://mem2019.github.io/jekyll/update/2020/09/19/QWB-GooExec.html

前言简单的吐个槽,跟赛题无关。这次单人参加了在郑州的强网杯线下,是第一次但应该也是最后一次了。总体的来讲无论是从赛题质量还是赛制设计都是不错的。比赛一共有两个部分:传统Attack Defense和Real World Jeopardy题目。我在第一天几乎一直在看AD的题,然后从第二天早上开始就几乎完全投入到了这道GooExec的V8题目上面来了,导致最后一道AD我连附件下都没下载。而更可惜的是这道题目我在15:40拿到了一个object faking primitive。我原以为是17:00之前都可以上台演示,那么就还有一个多小时写利用(这完全够了),但是16点就不能申请演示了,我在15:59申请了一次结果16:04就把我叫上去了,而这样的话我就完全没准备好,于是这道题就没有做出来。换句话说他这个本质上RW项目是16:00就结束了,并不是17:00结束。而我则等于第二天所花的精力完全没有拿到分,还不如做第四道AD题。

这道题修改了load-elimination.ccReduce两个Node的代码,把KillMaps函数删除了。这导致一些NodeMap会被错误估计,从而导致CheckMaps会被错误地删除掉。通过这个我们可以把一个unboxed double写到一个FixedArray里面去,于是再访问那个array就可以fake任意object了。由于开启pointer compression的V8并没有把存在GC堆中的低32位地址随机化,我们可以通过分配大Array来fake object,从而获得任意读写。

这道题目的patch点涉及到几个Load Elimination Phase的类,所以在分析patch前我会先分析这几个相关的类。

此类代表在某个node的effect之后的状态,在这里我们只关心他的AbstractMaps const* maps_成员。其中被删除的KillMaps函数代码如下:

此类代表在某个effect状态时所有node所可能有的maps。很明显其ZoneMap<Node*, ZoneHandleSet<Map>> info_for_node_成员变量就是用来存储这个信息的:把Node映射到一个Map的集合。这边注意,假如某个Node没有对应的映射,那么代表这个Node的Map信息JIT是不知道的;假如某个Node存在对应映射,那么这个Node在当前状态的Map必然是这个set中的其中一个。换句话说,假如某个可能的Map没有被包含在这个set里面,那么就有可能造成类型混淆。

接着再来看看AbstractMaps::Kill的实现,即KillMaps所调用的那个函数:

简单地说,MayAlias函数返回是否两个Node可能refer到同一个对象。而AliasStateInfo则代表用来比较的其中一个Node。

MayAlias具体的实现我就不放这里了,稍微有点长,只要知道它在两个Node一定不是同一个对象的时候返回false就好了。

在分析漏洞之前,可能还是得先看看相关函数所reduce的TransitionElementsKind是个什么东西。这是当Element类型改变的时候,用来改变Array的element存储方式的操作,所以同时也会改变其map。比如arr = [1.1, 2.2]的element会是FixedDoubleArray,而经过arr[0]={}操作后,element会变成一个FixedArray,这个时候就需要TransitionElementsKind这个操作来做这个转换。

接下来可以看看他的这个patch了,虽然他patch了两个函数,但实际上似乎我的利用只用到了其中一个函数,不知道这是不是非预期解。所以我这篇Writeup只讲解那个patch,虽然另一个也是一个KillMaps的删除,可能也差不多。但是那个更难触发到,需要通过Array.prototype.map来触发。

很明显,KillMaps的删除导致某些本应该没有map信息的一些node仍然保留了信息,那么假如所保留的Map信息是个错误估计,即某些可能存在的Map没有被存储在其中,那么就可能造成类型混淆。比如说ReduceCheckMaps这里,就有可能导致错误地删除CheckMaps

那么如何让state去保留一个错误估计呢?在TransitionElementsKindobject的Map插入了一个新的target_map,那么假如有另一个Node和object指向同一个对象,它的state就即不会被KillMaps删除也不可能被插入这个新的target_map,于是就可以导致一个错误估计。

接着就需要尝试构造PoC了。首先需要解决的问题是如何构造两个Node指向同一个object的情况,我尝试了使用一些JIT的方法让sea of nodes去生成一些aliasing nodes,但是好像特别难构造。最后我发现了一个最简单的方法,就是给两个不同的函数参数传入同一个对象,那么当JIT访问不同的Parameter时,就可以构成不同Node指向同一个对象的条件。

写成JavaScript大概是这样:声明function test(arr, arr2) {...},调用arr=Array(0);test(arr, arr);

关于PoC的JIT函数,大概思路如下:

接着就按照上面的思路写这么一个函数:

然后b load-elimination.cc:866下断点,--no-enable-slow-asserts关闭slow check后通过p state->Print()查看map_的状态:

执行state = state->SetMaps(object, object_maps, zone());前:

执行state = state->SetMaps(object, object_maps, zone());后:

说明第2步所需要达成的条件确实被成功触发了,arr2仍然被当做是一个double elements的Array。

但是实际上这个并没有触发第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)>,经过尝试,发现可以这么构造:

执行f(true, true, a, a)后,a[0]就已经是unboxed的13.37的低32位了,所以访问a[0]就会导致crash,这就是一个object faking primitive。

因为目前的V8都是有pointer compression的,而在这个模式下指针的低32位是不随机的,所以只需要一个object faking不需要任何泄露就能实现代码执行。具体思路如下:

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);
    // 本质上就是调用maps_的Kill函数
    if (this->maps_ != that_maps) {
      AbstractState* that = zone->New<AbstractState>(*this);
      that->maps_ = that_maps;
      return that; // 如果不一样才返回一个新的
    }
  }
  return this;
}
 
LoadElimination::AbstractState const* LoadElimination::AbstractState::KillMaps(
    Node* object, Zone* zone) const {
  AliasStateInfo alias_info(this, object);
  return KillMaps(alias_info, zone);
}
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);
    // 本质上就是调用maps_的Kill函数
    if (this->maps_ != that_maps) {
      AbstractState* that = zone->New<AbstractState>(*this);
      that->maps_ = that_maps;
      return that; // 如果不一样才返回一个新的
    }
  }
  return this;
}
 
LoadElimination::AbstractState const* LoadElimination::AbstractState::KillMaps(
    Node* object, Zone* zone) const {
  AliasStateInfo alias_info(this, object);
  return KillMaps(alias_info, zone);
}
 
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)) { // if one of nodes may alias
      AbstractMaps* that = zone->New<AbstractMaps>(zone);
      for (auto pair : this->info_for_node_) {
        if (!alias_info.MayAlias(pair.first)) that->info_for_node_.insert(pair);
      } // keep all except the ones that may alias
      return that;
    }
  }
  return this;
}
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)) { // if one of nodes may alias
      AbstractMaps* that = zone->New<AbstractMaps>(zone);
      for (auto pair : this->info_for_node_) {
        if (!alias_info.MayAlias(pair.first)) that->info_for_node_.insert(pair);
      } // keep all except the ones that may alias
      return that;
    }
  }
  return this;
}
class LoadElimination::AliasStateInfo {
 public:
  AliasStateInfo(const AbstractState* state, Node* object, Handle<Map> map)
      : state_(state), object_(object), map_(map) {}
  AliasStateInfo(const AbstractState* state, Node* object)
      : state_(state), object_(object) {}
 
  bool MayAlias(Node* other) const;
 
 private:
  const AbstractState* state_;
  Node* object_; // 用来比较的Node
  MaybeHandle<Map> map_;
};
class LoadElimination::AliasStateInfo {
 public:
  AliasStateInfo(const AbstractState* state, Node* object, Handle<Map> map)
      : state_(state), object_(object), map_(map) {}
  AliasStateInfo(const AbstractState* state, Node* object)
      : state_(state), object_(object) {}
 
  bool MayAlias(Node* other) const;
 
 private:
  const AbstractState* state_;
  Node* object_; // 用来比较的Node
  MaybeHandle<Map> map_;
};
// LoadElimination::ReduceTransitionElementsKind
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());
}
// LoadElimination::ReduceTransitionElementsKind
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());
}
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);
    // TODO(turbofan): Compute the intersection.
  }
  state = state->SetMaps(object, maps, zone());
  return UpdateState(node, state);
}
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;

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2020-9-21 17:39 被holing编辑 ,原因:
收藏
免费 1
支持
分享
最新回复 (1)
雪    币: 27
活跃值: (622)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
二大爷66666
2020-9-21 17:44
0
游客
登录 | 注册 方可回帖
返回
//