qwb growupjs & wctf independence_day writeup
qwb growupjs
漏洞分析
1 | diff --git a/src/compiler/machine-operator-reducer.cc b/src/compiler/machine-operator-reducer.cc |
patch如上,实际上是在MachineOperatorReducer的这个case中
1 | case IrOpcode::kUint32LessThan: { |
首先这个patch很简单,就是本来如果是1<1这样的kUint32LessThan比较,应该替换成false节点,而这里变成1<2(m.right().Value()+1)
),于是就替换成了true节点。
这个bug非常明显,但是如何利用呢?实际上对array边界的检查可以lower到Uint32LessThan节点,所以这实际上可以转化成一个array的off-by-one漏洞。
然后后续利用和*ctf 2019 OOB中使用的方法一致。
IR分析
我做了几组case,先看一个比较简单的case
case 1
1 | function main() { |
typer phase
在取arr[idx]之前会进行CheckBounds,然后在Simplified lower之后
1 | void VisitCheckBounds(Node* node, SimplifiedLowering* lowering) { |
然后在Effect linearization中被Lower成Uint32LessThan。
1 | Node* EffectControlLinearizer::LowerCheckedUint32Bounds(Node* node, |
case 2
那么是不是把idx直接改成4,就可以越界读写一个element呢?
事实上没那么简单,它们生成的IR完全不一样。
1 | function main() { |
typer phase
我们得到的IR是这样的。
代码在JSNativeContextSpecialization::BuildElementAccess
里
首先判断是否是load_mode=LOAD_IGNORE_OUT_OF_BOUNDS
比较简单的一种情况就是array的index超出了array的length。
这样我们需要对index进行check,看是否超出了Smi::kMaxValue,引入了上面的CheckBounds节点。
1 | // Check if we might need to grow the {elements} backing store. |
然后还需要对index进行实际的check,也就是比较index是否小于array length,引入了一个NumberLessThan节点。
1 | // Check if we can return undefined for out-of-bounds loads. |
然后这个节点在LoadElimination进行TyperNarrowingReducer的时候。
1 | switch (node->opcode()) { |
由于left_type即index的type信息被分析为(4,4),right_type即array length的type信息被分析为(4,4)
满足else if (left_type.Min() >= right_type.Max())
所以kNumberLessThan的类型会被更新成false,然后在ConstantFoldingReducer时候
1 | Reduction ConstantFoldingReducer::Reduce(Node* node) { |
被直接折叠成了false节点。
最后只剩下了对Smi::kMaxValue的CheckBounds。
然而这对我们来说毫无意义。
所以我们的第一步就是构造PoC,bypass掉ConstantFoldingReducer,这一步其实非常简单,只要让NumberLessThan在TyperNarrowingReducer的时候,不被类型更新成false就可以了。
case3
1 | function main() { |
idx的range取决于20和16号节点,如下。
1 | #21:SpeculativeNumberBitwiseAnd[SignedSmall](#16:NumberConstant, #20:NumberConstant, #17:Checkpoint, #12:JSStackCheck) [Type: Range(0, 4)] |
经过以下的typer分析得到range为(0,4)
1 | SPECULATIVE_NUMBER_BINOP(NumberBitwiseAnd) |
然后checkbounds的range也被分析成(0,4)
即取index和length的range的交集。
1 | Type OperationTyper::CheckBounds(Type index, Type length) { |
于是NumberLessThan的left_type即CheckBounds(实际上当成index也可以理解)的范围不再是(4,4),而是被分析成了(0,4)
不再满足left_type.Min() >= right_type.Max())
也就不会被折叠了。
于是最终的PoC就可以给出
1 | function main() { |
漏洞利用
有了越界读写一个element的原语,接下来就是构建完整的漏洞利用。
思路是:
首先分配两个array,一个double array,一个object array
然后通过覆盖object array的map为double map,就可以将其中的用户空间对象leak出来。
然后在array的elments去fake一个arraybuffer。
然后通过将double array的map覆盖成object array,就可以将fake好的arraybuffer给当成object给取出来。
而这个fake的arraybuffer的内容是我们可控的,于是就可以任意地址读写了。
接下来就是找到wasm_func里rwx的地址,将shellcode写入执行即可。
详细的思路参考我写的*ctf 2019 OOB exp。
wctf independence_day
漏洞分析
1 | diff --git a/src/objects/code.cc b/src/objects/code.cc |
1 | commit 3794e5f0eeee3d421cc0d2a8d8b84ac82d37f10d |
题目给了两个patch,第一个patch是禁用了code dependencies,第二个patch应该是禁用了wasm这种利用方法。
要理解这个patch,就要知道v8中不止有
实际上注册对arr的type的dependencies的地方在ReduceElementAccess的BuildCheckMaps中,换句话说,如果我们要check的map是stableMap,就直接注册一个 compilation dependencies的回调到map中。
如果不是,就插入一个checkMap节点到effect chain中。
可以学习一下这个漏洞,很有趣。
1 | Reduction JSNativeContextSpecialization::ReduceElementAccess( |
而这个patch就是把install compile dependency的代码给禁用了,所以如果我们使用一个stable map的arr,将不会有任何的类型检查,于是就有了一个type confusion。
IR分析
case1
非stable map
case2
stable map
所以给出poc如下:
1 | arr = [1.1, 2.2, 3.3,4.4]; |
漏洞利用
stephen给出了一种非常精巧的漏洞利用方法,而不是使用wasm rwx内存,实际上这个迟早要被禁用。
通过poc我们很容易就可以得到任意地址读写的原语。
为了构建rop链,我们可以使用如下的方法,来自stephen,非常感谢。
- leak a binary pointer from the heap
- read pointer to kernel32 from IAT
- read kernelbase pointer from IAT of kernel32
- There’s a stack pointer stored in a struct at KERNELBASE!BasepCurrentTopLevelFilter+8
- ROP
另外如果challenge只给了v8 binary,而是给了一个chromium的话,也可以参考我博客上关于bug-906043的漏洞利用。