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:
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 ?
- 33,867