Alternative way to get "C" letter in jsfuck
The fact that escape
is semi-deprecated kept bothering me, so I took another stab at it. Let's rebuild JSFuck from scratch.
Level 0
You can get the following values as primitives:
false ![]
true !![]
undefined [][[]]
NaN +[![]]
"" []+[]
0 +[]
1 +!+[]
2 +!+[]+!+[]
3 +!+[]+!+[]+!+[]
4 +!+[]+!+[]+!+[]+!+[]
5 +!+[]+!+[]+!+[]+!+[]+!+[]
6 +!+[]+!+[]+!+[]+!+[]+!+[]+!+[]
7 +!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]
8 +!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]
9 +!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]
Level 1
With the above values and the fact that value+[]
converts to string, we can set up the following character substitution rules:
"0" 0+[]
"1" 1+[]
"2" 2+[]
"3" 3+[]
"4" 4+[]
"5" 5+[]
"6" 6+[]
"7" 7+[]
"8" 8+[]
"9" 9+[]
"a" (false+[])[1]
"d" (undefined+[])[2]
"e" (true+[])[3]
"f" (false+[])[0]
"i" ([false]+undefined)[1+[0]]
"l" (false+[])[2]
"n" (undefined+[])[1]
"r" (true+[])[1]
"s" (false+[])[3]
"t" (true+[])[0]
"u" (undefined+[])[0]
"N" (NaN+[])[0]
Level 2
With the above characters, we can construct these four strings:
"11e100" +!+[]+[+!+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]
"1e1000" +!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]
"flat" (![]+[])[+[]]+(![]+[])[+!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]
"entries" (true+[])[3]+(undefined+[])[1]+(true+[])[0]+(true+[])[1]+([false]+undefined)[1+[0]]+(true+[])[3]+(false+[])[3]
With which we can get three more values:
1.1e+101 +("11e100")
Infinity +("1e1000")
Array Iterator []["entries"]()
Array.prototype.flat []["flat"]
The last one is particularly useful, because when converted to a string, it yields this:
"function flat() {\n [native code]\n}"
Or this:
"function flat() { [native code] }"
This is a bit wonky to work with, but the characters up to and including the {
are always the same, as is the last character.
Array Iterator
will convert to something more stable:
"[object Array Iterator]"
This gives us more characters to work with:
" " ([false]+[]["flat"])[2+[0]]
"(" ([]+[]["flat"])[1+[3]]
")" ([]+[]["flat"])[1+[4]]
"+" (+("11e100")+[])[4]
"." (+("11e100")+[])[1]
"[" ([]+[]["entries"]())[0]
"]" ([]+[]["entries"]())[2+[2]]
"{" ([true]+[]["flat"])[2+[0]]
"c" ([]["flat"]+[])[3]
"j" ([]+[]["entries"]())[3]
"o" ([true]+[]["flat"])[1+[0]]
"y" (true+[Infinity])[1+[1]]
"A" ([NaN]+([]+[]["entries"]()))[1+[1]]
"I" (Infinity+[])[0]
Level 3
Combining level 1 and 2 characters and values, we can now build three new strings:
".0000001" (+("11e100")+[])[1]+[0]+[0]+[0]+[0]+[0]+[0]+[1]
"constructor" ([]["flat"]+[])[3]+([true]+[]["flat"])[1+[0]]+(undefined+[])[1]+(false+[])[3]+(true+[])[0]+(true+[])[1]+(undefined+[])[0]+([]["flat"]+[])[3]+(true+[])[0]+([true]+[]["flat"])[1+[0]]+(true+[])[1]
And this gives us access to a bunch of further values:
1e-7 +(".0000001")
Boolean (![])["constructor"]
Number (+[])["constructor"]
String ([]+[])["constructor"]
Function []["flat"]["constructor"]
By converting to strings, we get yet more characters:
"-" (+(".0000001")+[])[2]
"b" ([]+(+[])["constructor"])[1+[2]]
"g" (false+[0]+([]+[])["constructor"])[2+[0]]
"m" ([]+(+[])["constructor"])[1+[1]]
"B" ([NaN]+(![])["constructor"])[1+[2]]
"F" ([NaN]+[]["flat"]["constructor"])[1+[2]]
"S" ([NaN]+([]+[])["constructor"])[1+[2]]
Level 4
Given the uppercase S
, we could now build the string "toString
manually. However, if we first build the string "name"
, we can achieve an overall shorter code:
"name" (undefined+[])[1]+(false+[])[1]+([]+(+[])["constructor"])[1+[1]]+(true+[])[3]
"toString" (true+[])[0]+([true]+[]["flat"])[1+[0]]+([]+[])["constructor"]["name"]
And with that we can call Number.toString()
, giving us all remaining lowercase letters:
"h" (+(1+[0]+[1]))["toString"](2+[1])[1]
"k" (+(2+[0]))["toString"](2+[1])
"p" (+(2+[1]+[1]))["toString"](3+[1])[1]
"q" (+(2+[1]+[2]))["toString"](3+[1])[1]
"v" (+(3+[1]))["toString"](3+[2])
"w" (+(3+[2]))["toString"](3+[3])
"x" (+(1+[0]+[1]))["toString"](3+[4])[1]
"z" (+(3+[5]))["toString"](3+[6])
At the same time, we can construct two more strings:
"slice" (false+[])[3]+(false+[])[2]+([false]+undefined)[1+[0]]+([]["flat"]+[])[3]+(true+[])[3]
"-1" (+(".0000001")+[])[2]+[+!+[]]
And that gives us one last character that we need for the next level:
"}" ([true]+[]["flat"])["slice"]("-1")
Level 5
At this point, there's one primitive we obtained that we haven't used yet: using Function
as an eval primitive:
[]["flat"]["constructor"](...)()
Since we have all lowercase letter now as well as space, +
, .
, [
, ]
, {
and }
, we can build:
"try{String().normalize(false)}catch(f){return f}"
By means of:
(true+[])[0]+(true+[])[1]+(true+[Infinity])[1+[1]]+([true]+[]["flat"])[2+[0]]+([]+[])["constructor"]["name"]+([]+[]["flat"])[1+[3]]+([]+[]["flat"])[1+[4]]+(+("11e100")+[])[1]+(undefined+[])[1]+([true]+[]["flat"])[1+[0]]+(true+[])[1]+([]+(+[])["constructor"])[1+[1]]+(false+[])[1]+(false+[])[2]+([false]+undefined)[1+[0]]+(+(3+[5]))["toString"](3+[6])+(true+[])[3]+([]+[]["flat"])[1+[3]]+![]+([]+[]["flat"])[1+[4]]+([true]+[]["flat"])["slice"]("-1")+([]["flat"]+[])[3]+(false+[])[1]+(true+[])[0]+([]["flat"]+[])[3]+(+(1+[0]+[1]))["toString"](2+[1])[1]+([]+[]["flat"])[1+[3]]+(false+[])[0]+([]+[]["flat"])[1+[4]]+([true]+[]["flat"])[2+[0]]+(true+[])[1]+(true+[])[3]+(true+[])[0]+(undefined+[])[0]+(true+[])[1]+(undefined+[])[1]+([false]+[]["flat"])[2+[0]]+(false+[])[0]+([true]+[]["flat"])["slice"]("-1")
Calling String.prototype.normalize()
with a value that isn't a valid Unicode Normalization Form will throw a RangeError
, which we catch and return to the caller. We thus have:
RangeError []["flat"]["constructor"]("try{String().normalize(false)}catch(f){return f}")()
Note that the above is an instance - we'd have to use ["constructor"]
to get the function/constructor, but we can just convert it to string as-is, giving us two more uppercase letters:
"E" ([false]+[]["flat"]["constructor"]("try{String().normalize(false)}catch(f){return f}")())[1+[0]]
"R" ([]+[]["flat"]["constructor"]("try{String().normalize(false)}catch(f){return f}")())[0]
Level 6
With two more characters unlocked, we can now construct this string:
"return RegExp" (true+[])[1]+(true+[])[3]+(true+[])[0]+(undefined+[])[0]+(true+[])[1]+(undefined+[])[1]+([false]+[]["flat"])[2+[0]]+([]+[]["flat"]["constructor"]("try{String().normalize(false)}catch(f){return f}")())[0]+(true+[])[3]+(false+[0]+([]+[])["constructor"])[2+[0]]+([false]+[]["flat"]["constructor"]("try{String().normalize(false)}catch(f){return f}")())[1+[0]]+(+(1+[0]+[1]))["toString"](3+[4])[1]+(+(2+[1]+[1]))["toString"](3+[1])[1]
And that gives us a new value/function:
RegExp []["flat"]["constructor"]("return RegExp")()
When invoked with no arguments and converting the resulting RegExp
to string, we get:
"/(?:)/" []+[]["flat"]["constructor"]("return RegExp")()()
So we have a bunch of new special characters:
"/" ([]+[]["flat"]["constructor"]("return RegExp")()())[0]
":" ([]+[]["flat"]["constructor"]("return RegExp")()())[3]
"?" ([]+[]["flat"]["constructor"]("return RegExp")()())[2]
Level 7
Now we feed one of those characters back into the regex to get a new string:
"/\\//" []+RegExp("/")
This gives us access to a single new character:
"\\" ([]+RegExp("/"))[1]
Level 8
Let's build a new string:
"try{Function([]+[[]].concat([[]]))()}catch(f){return f}"
By:
(true+[])[0]+(true+[])[1]+(true+[Infinity])[1+[1]]+([true]+[]["flat"])[2+[0]]+[]["flat"]["constructor"]["name"]+([]+[]["flat"])[1+[3]]+([]+[]["entries"]())[0]+([]+[]["entries"]())[2+[2]]+(+("11e100")+[])[4]+([]+[]["entries"]())[0]+([]+[]["entries"]())[0]+([]+[]["entries"]())[2+[2]]+([]+[]["entries"]())[2+[2]]+(+("11e100")+[])[1]+([]["flat"]+[])[3]+([true]+[]["flat"])[1+[0]]+(undefined+[])[1]+([]["flat"]+[])[3]+(false+[])[1]+(true+[])[0]+([]+[]["flat"])[1+[3]]+([]+[]["entries"]())[0]+([]+[]["entries"]())[0]+([]+[]["entries"]())[2+[2]]+([]+[]["entries"]())[2+[2]]+([]+[]["flat"])[1+[4]]+([]+[]["flat"])[1+[4]]+([]+[]["flat"])[1+[3]]+([]+[]["flat"])[1+[4]]+([true]+[]["flat"])["slice"]("-1")+([]["flat"]+[])[3]+(false+[])[1]+(true+[])[0]+([]["flat"]+[])[3]+(+(1+[0]+[1]))["toString"](2+[1])[1]+([]+[]["flat"])[1+[3]]+(false+[])[0]+([]+[]["flat"])[1+[4]]+([true]+[]["flat"])[2+[0]]+(true+[])[1]+(true+[])[3]+(true+[])[0]+(undefined+[])[0]+(true+[])[1]+(undefined+[])[1]+([false]+[]["flat"])[2+[0]]+(false+[])[0]+([true]+[]["flat"])["slice"]("-1")
This is equivalent to:
"try{Function(',')()}catch(f){return f}"
Except for the fact that we can't write ','
(yet). Evaluating that will return a SyntaxError
object, which, when converted to string, will yield:
"SyntaxError: Unexpected token ','"
We can then feed that string into RegExp("[\u0027]").exec(...)[0]
to extract the single quote.
So we wanna run:
RegExp("[\u0027]").exec(Function("try{Function([]+[[]].concat([[]]))()}catch(f){return f}")())[0]
Applying a whole bunch of substitutions from above, we get one final character:
"'" RegExp(([]+[]["entries"]())[0]+([]+RegExp("/"))[1]+(undefined+[])[0]+[+[]]+[+[]]+[+!+[]+!+[]]+[+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+([]+[]["entries"]())[2+[2]])[(true+[])[3]+(+(1+[0]+[1]))["toString"](3+[4])[1]+(true+[])[3]+([]["flat"]+[])[3]]([]["flat"]["constructor"]((true+[])[0]+(true+[])[1]+(true+[Infinity])[1+[1]]+([true]+[]["flat"])[2+[0]]+[]["flat"]["constructor"]["name"]+([]+[]["flat"])[1+[3]]+([]+[]["entries"]())[0]+([]+[]["entries"]())[2+[2]]+(+("11e100")+[])[4]+([]+[]["entries"]())[0]+([]+[]["entries"]())[0]+([]+[]["entries"]())[2+[2]]+([]+[]["entries"]())[2+[2]]+(+("11e100")+[])[1]+([]["flat"]+[])[3]+([true]+[]["flat"])[1+[0]]+(undefined+[])[1]+([]["flat"]+[])[3]+(false+[])[1]+(true+[])[0]+([]+[]["flat"])[1+[3]]+([]+[]["entries"]())[0]+([]+[]["entries"]())[0]+([]+[]["entries"]())[2+[2]]+([]+[]["entries"]())[2+[2]]+([]+[]["flat"])[1+[4]]+([]+[]["flat"])[1+[4]]+([]+[]["flat"])[1+[3]]+([]+[]["flat"])[1+[4]]+([true]+[]["flat"])["slice"]("-1")+([]["flat"]+[])[3]+(false+[])[1]+(true+[])[0]+([]["flat"]+[])[3]+(+(1+[0]+[1]))["toString"](2+[1])[1]+([]+[]["flat"])[1+[3]]+(false+[])[0]+([]+[]["flat"])[1+[4]]+([true]+[]["flat"])[2+[0]]+(true+[])[1]+(true+[])[3]+(true+[])[0]+(undefined+[])[0]+(true+[])[1]+(undefined+[])[1]+([false]+[]["flat"])[2+[0]]+(false+[])[0]+([true]+[]["flat"])["slice"]("-1"))())[0]
Level 9
At this point, we can return every character we want simply by doing:
Function("return '\uXXXX'")()
Demo
Let's take the character "C"
from your question:
Function("return '\u0043'")()
Running this through all the substitutions above produces an absolute nightmare of 167'060 bytes. This exceeds the maximum post length on SO, but I pasted it into a gist, so feel free to try it. Though you might want to run it by means other than manually pasting it into your console...
Here I develop Siguza answer if we use non deprecated de/encodeURI (level 5) we have
console.log(
decodeURI(encodeURI(" ")[0]+"43"),
(NaN+[]["entries"]()["to"+String["name"]]["call"]())[11] // here is for U came from
)