Xchars.js

 

What is the smallest subset of characters

than can run any possible JavaScript code ?

 

by Sylvain Pollet-Villard @SylvainPV

An old story

Many people at different places and times
tried to answer this question

At least how many different characters are required

to run any possible JavaScript program ?


The generally accepted answer to this question is

6

Trying to find a subset below 6 characters

that can run any JavaScript
is a major challenge known as

The Wall of Six

Let's see how we get there

Everything starts with these 3 characters:

[ ] +

 

[ ] is used for property access

and array litteral declaration

 

+ operator has superpowers

Cast to Number,  String concat, ++ increment...

 

Also + has a great synergy with arrays 

Some reminders about type coercion in JS

 

+x casts to Number

++x casts to Number and increment by one

x+[] casts to String

 

Let's start with Numbers

 

0 === +[]

 

Zero is easy to get

 

1 === +[]++
Does not work
We can only use one operator at a time

 

But here's the trick :

[x][0] === x

++ [ x ] [ 0 ] === +x + 1

++[ [] ] [ 0 ] === +[] + 1

 

1 === ++[[]][+[]]  

 

By repeating [x][0]===x trick,
we can get all numbers

 

2 === ++[++[[]][+[]]][+[]]

3 === ++[++[++[[]][+[]]][+[]]][+[]]

 

and so on ...

 

This trick is used every time we need to wrap

a group or an operation, just like parenthesis.

0 1 2 3 4 5 6 7 8 9

a b c d e f g h i

j k l m n o p q

r s t u v w x y z

A B C D E F G H I

J K L M N O P Q

R S T U V W X Y Z

Let's get some letters now

[][0] === undefined
undefined+[] === "undefined"

"undefined"[0] === "u"

"undefined"[1] === "n"

...

 

When replacing numbers by [ ] +, we get:

"u" === [[][+[]]+[]][+[]][+[]]

"n" === [[][+[]]+[]][+[]][++[[]][+[]]]

0 1 2 3 4 5 6 7 8 9

a b c d e f g h i

j k l m n o p q

r s t u v w x y z

A B C D E F G H I

J K L M N O P Q

R S T U V W X Y Z

u n d e f i n e d

0 1 2 3 4 5 6 7 8 9

a b c d e f g h i

j k l m n o p q

r s t u v w x y z

A B C D E F G H I

J K L M N O P Q

R S T U V W X Y Z

+undefined === NaN

+[][0]+[] === "NaN"

We got letter e
So we have exponential number notation !

 

+("1e308") === 1e+308

+("1e309") === Infinity

0 1 2 3 4 5 6 7 8 9

a b c d e f g h i

j k l m n o p q

r s t u v w x y z

A B C D E F G H I

J K L M N O P Q

R S T U V W X Y Z

We got letter e
So we have exponential number notation !

 

+("1e308") === 1e+308

+("1e309") === Infinity

+("1"+"e"+"1"+"0"+"0"+"0")+[] === "Infinity"

"I" ===

[+[++[+[]][+[]]+[]+[[]+[][+[]]][+[]][++[++[++[+[]][+[]]][+[]]][+[]]]+[++[+[]][+[]]+[]][+[]]+[+[]]+[+[]]+[+[]]][+[]]+[]][+[]][+[]]

 

 

Still valid JS code ;-)

0 1 2 3 4 5 6 7 8 9

a b c d e f g h i

j k l m n o p q

r s t u v w x y z

A B C D E F G H I

J K L M N O P Q

R S T U V W X Y Z

With the letters we have, no property is accessible

neither from Strings, Numbers or Arrays

... until ES6 comes to the rescue !

 

[]["find"] === function find() { [native code] } *

 

* String representations of native functions may slightly differ between browsers

function getAllPropertyNames(o){ 
   return o ? Object.getOwnPropertyNames(o).concat(
        getAllPropertyNames(Object.getPrototypeOf(o))
      ) : [] 
}

getAllPropertyNames([])
      .filter(name => name.split('').every(
           char => 'acdefinotuvyIN'.includes(char))
       )

> ["find", "concat", "at"] // these are not helping to go further...

Looking for the next piece...

Are we stuck ?

 

Well, it looks like so.

 

unless someone gets another briliant idea,

3 chars subset is not feasible

 

#saddisillusionment

 

We have Strings, Numbers and Arrays, but no Booleans

and letters r l s from true and false could help

 

So we need either

! for Boolean cast

or

= or > or < for conditions tests

 

false ===   ![]  ===   []==[]
true  ===  !![]  ===  +[]==+[]

= is the best choice here
because it also gives us assignments
Okay, let's add the  =  character to our subset

 

[ ] + =

 

4 is not so bad I guess

false === []==[]
true === +[]==+[]

 

"l" === [[[]==[]][+[]]+[]][+[]][++[++[[]][+[]]][+[]]]
"s" === [[[]==[]][+[]]+[]][+[]][++[++[++[[]][+[]]][+[]]][+[]]]
"r" === [[+[]==+[]][+[]]+[]][+[]][++[+[]][+[]]]

0 1 2 3 4 5 6 7 8 9

a b c d e f g h i

j k l m n o p q

r s t u v w x y z

A B C D E F G H I

J K L M N O P Q

R S T U V W X Y Z

getAllPropertyNames([])
   .filter(name => name.split('').every(
      char => 'acdefilnorstuvyIN'.includes(char))
   )

["constructor", "reverse", "slice", "sort", "filter", "every",

"reduce", "find", "fill", "includes", "entries", "concat"]

 

getAllPropertyNames(new String())
   .filter(name => name.split('').every(
      char => 'acdefilnorstuvyIN'.includes(char))
   )

["constructor", "concat", "includes", "slice", "fontcolor", "italics"]

 

Much more stuff available once we have l r s letters

Note that we have all the letters for "constructor"

"constructor"

 

[[][[[]+[][+[]]][+[]][++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[[]][+[]]]+[[]+[][+[]]][+[]][++[++[[]][+[]]][+[]]]]+[]][+[]][++[++[++[[]][+[]]][+[]]][+[]]]+[[][[[]+[][+[]]][+[]][++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[[]][+[]]]+[[]+[][+[]]][+[]][++[++[[]][+[]]][+[]]]]+[]][+[]][++[++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[[]][+[]]]+[[[]==[]][+[]]+[]][+[]][++[++[++[[]][+[]]][+[]]][+[]]]+[+[++[+[]][+[]]+[]+[[]+[][+[]]][+[]][++[++[++[+[]][+[]]][+[]]][+[]]]+[++[+[]][+[]]+[]][+[]]+[+[]]+[+[]]+[+[]]][+[]]+[]][+[]][++[++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[+[]==+[]][+[]]+[]][+[]][++[+[]][+[]]]+[[]+[][+[]]][+[]][+[]]+[[][[[]+[][+[]]][+[]][++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[[]][+[]]]+[[]+[][+[]]][+[]][++[++[[]][+[]]][+[]]]]+[]][+[]][++[++[++[[]][+[]]][+[]]][+[]]]+[+[++[+[]][+[]]+[]+[[]+[][+[]]][+[]][++[++[++[+[]][+[]]][+[]]][+[]]]+[++[+[]][+[]]+[]][+[]]+[+[]]+[+[]]+[+[]]][+[]]+[]][+[]][++[++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[][[[]+[][+[]]][+[]][++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[[]][+[]]]+[[]+[][+[]]][+[]][++[++[[]][+[]]][+[]]]]+[]][+[]][++[++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[+[]==+[]][+[]]+[]][+[]][++[+[]][+[]]]

Starting from here,

your screen is too small

to display all the subset code

 

#nowweretalking

0["constructor"]+[] === "function Number() { ..."
""["constructor"]+[] === "function String() { ..."
false["constructor"]+[] === "function Boolean() { ..."
[]["constructor"]+[] === "function Array() { ..."
[].find.constructor+[] === "function Function..."

 

0 1 2 3 4 5 6 7 8 9

a b c d e f g h i

j k l m n o p q

r s t u v w x y z

A B C D E F G H I

J K L M N O P Q

R S T U V W X Y Z

[]["find"]["constructor"] === Function

Function("some code")()

 

Function is the master key

It lets you evaluate any code as String.

This is like eval, without the need for

a reference to the global scope (a.k.a. window)

 

Problem is, we still need parenthesis here.

 

 

alert`hello`

Function`some code```

 

We could use backticks and tagged template strings

Backticks only require to introduce 1 character `

contrary to parenthesis which need 2 ( )

 

Unfortunately, content in backticks must be literal and cannot be evaluated without interpolation,
which would require introducing
$ { } characters

No other choice than adding parenthesis to our subset

 

Now, with this 6 characters subset

[ ] + = ( )

we can do everything

 

"to" + ""["constructor"]["name"] === "toString"

 

toString can receive a base as argument

so we can retrieve any lowercase letter from base 36

 

"h" === 17["toString"](20)

"x" === 33["toString"](34)

...

 

 

0 1 2 3 4 5 6 7 8 9

a b c d e f g h i j k l m n o p q r s t u v w x y z

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

window === Function("return self")()
       === []["find"]["constructor"]("return self")()

Finally a reference to the global scope !

Last walls are falling down

"C" === window["atob"]("20N")[1]

 

which is the last letter required for

String.fromCharCode

~9800 characters when written in ([+=])

 

which gives us any remaining character

"Z" === String.fromCharCode(90)

0 1 2 3 4 5 6 7 8 9

a b c d e f g h i j k l m n o p q r s t u v w x y z

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

Now that we can retrieve all the characters,

we can convert any JS code into [ ] + = ( )

by reconstituting a string containing the source code,

one character after another,

then evaluating it through Function() or eval()

 

Mission complete !

 

 

Here is a compiler proof of concept :

http://syllab.fr/projets/experiments/xcharsjs/

using [ ] + ! ( ) subset

 

This the same subset chosen by JSFuck

but our implementations are slightly different

mine uses an optimisation based on String.fromCharCode

that reduces the output size for larger codebases.

 

Demo time:

Óscar Toledo G. Nanochess

 

Initially 1K (2nd place at JS1K 2010)

 

compiled in 6chars subset:

181 825 chars 

 

Play here and look at the source !

 

Analysis of nanochess 6chars output

Chars frequency

! 153

( 23

) 23

+ 49828

[ 65899

] 65899

! ()

are used only when absolutely necessary

Crazy idea #67239

 

+ [ ] represents 99.9% of 6chars.js output

ECMAScript source text is assumed

to be a sequence of 16-bit code units

 

What if we could convert +[] string pieces

into Unicode-16 characters ?

WTF Why ?

First, congratulations for being still here

 

You can see this as an experiment to learn more about type coercion, operators priority and various type casting rules in JS.

 

Also, for fun.

 

A possible usecase could be extreme obfuscation, although the impact on performance and code size makes it impractical

Can we break the
Wall of 6 ?

 

We are still working on it

Crazy Idea #47263:

 

If we could eval the whole thing at the top,

everything else could be a giant String of code

since we already have ( ) as characters

 

element.outerHTML 
+=" <img src='fail' onerror='eval(`code !!!`)'/>"

 

On browsers environments, outerHTML and  onload/onerror

 can be used for code evaluation without requiring parenthesis

 

Maybe we can use this trick and get rid of ( )  ?

element.innerHTML 
+=" <img src='xxx' onerror='alert(`code !!!`)'/>"

 

What do we need to be able to do this ?

 

1) an HTMLElement reference

2) an affectation operation, so the = character

3) some missing chars needed for the setup

 

The biggest problem here is 1)

How do we get an Element ref without window ?

 

A not totally legit solution

If we could use a one-char identifier to retrieve a reference to a DOM Element, it could be our 5th character to replace ( )

 

Advantages:

- DOM API are so large we find everything we need

- the 5th char can be every valid variable name of your choice

 

Disadvantages :
- will only work on environments with DOM, not Node.js

- depends on an external, HTML, requirement

- considered cheating

 

Proof of concept

A working 5chars compiler

use subset [ ] + = _

need to be run inside

<script id="_"></script>

What's next ?

 

Can you break the Wall of 6 ?

Have you thought of another usecase ?

Do you want to join the hunting team ?

 

Feel free to contact me
@SylvainPV

 

or join us on JSFuck Gitter channel:

https://gitter.im/aemkei/jsfuck

 

[+[++[+[]][+[]]+[]+[[]+[][+[]]][+[]][++[++[++[+[]][+[]]][+[]]][+[]]]+[++[+[]][+[]]+[]][+[]]+[+[]]+[+[]]+[+[]]][+[]]+[]][+[]][++[++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[][[[]+[][+[]]][+[]][++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[[]][+[]]]+[[]+[][+[]]][+[]][++[++[[]][+[]]][+[]]]]+[]][+[]][++[++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[][[[]+[][+[]]][+[]][++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[[]][+[]]]+[[]+[][+[]]][+[]][++[++[[]][+[]]][+[]]]]+[]][+[]][++[++[++[++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[+[]][+[]][[[][[[]+[][+[]]][+[]][++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[[]][+[]]]+[[]+[][+[]]][+[]][++[++[[]][+[]]][+[]]]]+[]][+[]][++[++[++[[]][+[]]][+[]]][+[]]]+[[][[[]+[][+[]]][+[]][++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[[]][+[]]]+[[]+[][+[]]][+[]][++[++[[]][+[]]][+[]]]]+[]][+[]][++[++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[[]][+[]]]+[[![]][+[]]+[]][+[]][++[++[++[[]][+[]]][+[]]][+[]]]+[+[++[+[]][+[]]+[]+[[]+[][+[]]][+[]][++[++[++[+[]][+[]]][+[]]][+[]]]+[++[+[]][+[]]+[]][+[]]+[+[]]+[+[]]+[+[]]][+[]]+[]][+[]][++[++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[!![]][+[]]+[]][+[]][++[+[]][+[]]]+[[]+[][+[]]][+[]][+[]]+[[][[[]+[][+[]]][+[]][++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[[]][+[]]]+[[]+[][+[]]][+[]][++[++[[]][+[]]][+[]]]]+[]][+[]][++[++[++[[]][+[]]][+[]]][+[]]]+[+[++[+[]][+[]]+[]+[[]+[][+[]]][+[]][++[++[++[+[]][+[]]][+[]]][+[]]]+[++[+[]][+[]]+[]][+[]]+[+[]]+[+[]]+[+[]]][+[]]+[]][+[]][++[++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[][[[]+[][+[]]][+[]][++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[[]][+[]]]+[[]+[][+[]]][+[]][++[++[[]][+[]]][+[]]]]+[]][+[]][++[++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[!![]][+[]]+[]][+[]][++[+[]][+[]]]]+[]][+[]][++[[]][+[]]+[]+[++[++[[]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[++[++[[]][+[]]][+[]]][+[]]]+[[][[[]+[][+[]]][+[]][++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[[]][+[]]]+[[]+[][+[]]][+[]][++[++[[]][+[]]][+[]]]]+[]][+[]][++[++[++[++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[][[[]+[][+[]]][+[]][++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[[]][+[]]]+[[]+[][+[]]][+[]][++[++[[]][+[]]][+[]]]]+[]][+[]][++[++[++[[]][+[]]][+[]]][+[]]]+[[][[[]+[][+[]]][+[]][++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[[]][+[]]]+[[]+[][+[]]][+[]][++[++[[]][+[]]][+[]]]]+[]][+[]][++[++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[[]][+[]]]+[+[++[+[]][+[]]+[]+[[]+[][+[]]][+[]][++[++[++[+[]][+[]]][+[]]][+[]]]+[++[+[]][+[]]+[]][+[]]+[+[]]+[+[]]+[+[]]][+[]]+[]][+[]][++[++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[++[++[++[++[[]][+[]]][+[]]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[[]][+[]]]+[[]+[][[]]][+[]][+[]]+[[]+[][+[]]][+[]][++[++[++[[]][+[]]][+[]]][+[]]]+[[]+[][+[]]][+[]][++[++[[]][+[]]][+[]]]

Xchars.js

By sylvainpv

Xchars.js

What is the smallest subset of characters than can be used to execute any JavaScript code ?

  • 32,718