Hacker News new | past | comments | ask | show | jobs | submit login
JSFuck – Write any JavaScript with 6 Characters: []()!+ (jsfuck.com)
157 points by lukashed on Sept 13, 2013 | hide | past | favorite | 71 comments



I saw a very cool security talk a few years ago about how you can use the browser to do all kinds of evil things (did you know that the Same Origin Policy does not prevent you from making the request, but just seeing the response? And even then, you can guess at what kind of response you got).

One of the great points in the talk was JS obfuscation. Now, there are many techniques for doing this, but I really like this one as it just looks cool. Since you can translate most functional JS into ASCII, you simply encode every character into a binary coding using spaces for 0 and tabs for 1. Then you write a very simple converter + put an eval() around it, and viola, you are running arbitrary code. To a casual observer it would look like you have delivered a mostly empty file, while in fact you are delivering perfectly valid JS. Not unbreakable, but certainly fun.


Was it Billy Hoffman's 'JavaScript the Evil Parts'? http://blip.tv/jsconf/billy-hoffman-javascript-the-evil-part...


Yes, that's the one.


I recently did this exact thing for Ruby: https://github.com/dzucconi/negative


It is kinda irrelevant that the Same Origin Policy doesn't prevent requests since, you don't even need to use a javascript request, a simple image tag does the same


I am worried about the file size.


Don't worry about it? :)

Realistically, how big is your exploit? 30KB? So it'll be 30*7 = 210 KB (JS is actually 7 bit ASCII). That's plenty of code to do something malicious. Nothing is preventing you from minifying the code before converting it to this whitespace encoding.

I suppose you could make your encoding include other whitespace chars, like newlines, carriage returns, etc. Then you could use base 4 instead of base 2.


gzip and a file that contains sequences of only two different characters should compress pretty well.


Currently, there is only 5 characters in the HN headline: "[]()+". Maybe a mod removed the "!" because they thought it was being used for emphasis. It could be placed before the "+".


A human would have probably noticed the "6 characters" part. I bet this was done automatically.


That's a pretty awesome hack of JS's (rather insane) semantics. It's probably worth understanding at least the primative examples though if you're a JS programmer, because this actually happens sometimes to expressions you write.

For a little bit of insight into why []+[] === '', for example, check this out: http://www.2ality.com/2012/01/object-plus-object.html


I was interested in how this might be shrunk to something more reasonable... I got this far before getting bored: http://pastie.org/8324713

That code, run as-is in your browser, should alert 'hi'.

It's constructed with the intent of replacing the variables a-e and various named parameters with short sequences in the given character set that can be cast to strings. For legibility, I shortened many of the encodable sequences to strings (such as []["slice"])

The table holds 5^3 characters, which is 125; you could start the loop at 2 instead of 0 to get 2-127 as target characters, or use 6 input arguments. The 5 I chose were ![], [], +[], +{}, ~[] -- this should let you encode anything after overhead at a ratio of 12:1 with high repetition that gzip can take advantage of. This could be awful, I don't know; but maybe somebody will find this interesting.


Here's an article [0] explaining more about how this works from the author of Hieroglyphy.

[0] - http://patriciopalladino.com/blog/2012/08/09/non-alphanumeri...


I put in alert('hello') and it worked. That's awesome. But how? I searched the code it made and didn't see 'hello.' I understand the stuff below, how it uses JS's weird properties to the basic types... but how does it encode characters?


Try it letter by letter.

For instance, to get the string "a":

    (![]+[])[+[[+!+[]]]]
Take the first part, `(![]+[])`. `![]` evaluates to `false`. Then `+[]` coerces false into a string, so the expression is `"false"`.

The rest of the expression (more complicated) evaluates to `[[1]]`, which will grab the `"a"` from `"false"`. Now why there is the extra surrounding brackets, I'm not sure, because `[1]` would have worked as well.


It looks like the generator is suboptimal, but still really cool.

(![]+[])[+!+[]] evaluates to "a" as well. It looks like it turns it into an array twice.

It goes +!+[] === 1 then [+!+[]] === [1] then +[[+!+[]]] === 1 then [+[[+!+[]]]] === [1]


Maybe one could run the output through Closure Compiler to get a bit better code ;-)

(Ok, tried it, it evaluates plenty of numbers on its own already which increases the character set. Not nice).


Ah! You're right. Confusing.

Also confusing that "false"[[1]] works.


Look at the source.

It uses various techniques to get at e.g. type names, and then uses string indexing to access characters in the type name. It has enough characters like this to be able to call fromCharCode.


It converts certain things into strings. Like [][[]] => undefined, and then gets the characters by index from that string and puts your original string together.


It takes advantage of JS's complicated type coercion semantics and the overloading of the [] and + operators.

We start with [] and [[]].

convert [] to a boolean with !:

    ![] --> false
    !![] --> true
convert [] to undefined by subscripting it:

    [][[]] --> undefined
convert [] [[]] and true to numbers by prefixing them with + and adding them with +:

    +[]   --> 0
    +[[]] --> NaN
    +!![] --> +true --> 1
    +!![]+(+!![]) --> +true+(+true) --> 1+(1) --> 2
    etc.
convert any of these to strings by prefixing them with []+

    []+[]     --> ""
    []+![]    --> []+false     --> "false"
    []+[][[]] --> []+undefined --> "undefined"
    []+(+[])  --> []+0 --> "0"
get individual characters with the array subscript operator:

    ([]+![])[+[]] --> ([]+false)[0] --> "false"[0] --> "f"
    ([]+!![])[+!![]+(+!![])+(+!![])] --> ([]+true)[1+(1)+(1)] --> "true"[3] --> "e"
    etc...
So now we can obtain a limited number of characters:

    "a" "d" "e" "f" "i" "l" "n" "r" "s" "t" "u" "N" "0" "1" "2" "3" "4" "5" "6" "7" "8" "9"
which we can combine into strings with the + operator:

    "f"+"i"+"l"+"t"+"e"+"r" --> "filter"
    "1"+"e"+"1"+"0"+"0"+"0" --> "1e1000"
It's not much, but it's enough to obtain large numbers:

    +("1"+"2"+"3"+"4") --> +("1234") --> 1234
    +("1"+"e"+"1"+"0"+"0"+"0") --> +("1e1000") --> Infinity
and, most importantly, to access a property of the array object:

    []["filter"]  --> function filter() { [native code] }
and by converting these back to a string:

    []+[]["filter"] --> "function filter() { [native code] }"
    []+Infinity     --> "Infinity"
we can expand our alphabet still further, and access some even more exciting properties:

    []["constructor"]           --> function Array() { [native code] }
    ([]+[])["constructor"]      --> function String() { [native code] }
    (![])["constructor"]        --> function Boolean() { [native code] }
    (+[])["constructor"]        --> function Number() { [native code] }
    []["filter"]["constructor"] --> function Function() { [native code] }
Almost there now.

By converting these back to strings we can access even more characters, and by passing the strings to the Function() constructor we can can construct functions and evaluate them! In other words, we have "eval". Let's use it to access the window object:

    []["filter"]["constructor"]("return this") --> function anonymous() {return this}
    []["filter"]["constructor"]("return this")() --> window
So now we have access to the global context and eval. We don't quite have access to the full range of letters, but we have enough letters to call toString, and use it's base conversion ability to get the full lowercase alphabet:

    10["toString"](36) --> "a"
    11["toString"](36) --> "b"
    ...
    25["toString"](36) --> "p"
    ...
    35["toString"](36) --> "z"
And now we have "p", we can use escape and unescape to get most of the rest:

    unescape(escape(" ")[0]+4+0) --> "@"
So there you have it!

The source code essentially runs this process backwards: it repeatedly uses regular expressions to convert the code back into "()[]!+" one step at a time.


i imagine it is using bit shifts similar to brainfuck http://en.wikipedia.org/wiki/Brainfuck


Im not sure but once you have 0, 1, 2 and 10 as numbers you could add and subtract those to get any number you want, then express those numbers as strings (casting).


While funny looking, what is the ingenuity behind it?


Many people here on HN never took compsci in school, so strictly speaking it's just a translator - http://en.wikipedia.org/wiki/Translator_(computing) .

It's more a showoff of a js idiosyncrasy - they found an ugly looking subset of characters that is Turing complete and wrote a translator to it.

If you're new to the concept of Turing tarpits, then this should blow your mind. On the other hand, this is a sufficiently advanced bit of CS thinking that no future employer should question the author's basic competency. I certainly have never written a translator.


I'm not sure it's a translator by that definition. The input and output are legal Javascript. It's moreso a demonstration of a functionally complete subset of the language.

You can get all the primitive values by taking advantage of unary plus, binary +, empty arrays, array dereferencing, function calls, and the standard strings returned by some basic expressions. The numbers are straightforward. The strings are dereferenced with numbers to get some individual letters. You can get methods by using array dereference on objects with strings. You get the rest of the letters with btoa and atob. Then you get eval, and you're off to the races!

I'm sure, but I think the encoder just goes token by token, eval'ing string conversions to get identifiers. You'll notice that for pretty simple expressions you get absurdly long strings out.


>I'm not sure it's a translator by that definition. The input and output are legal Javascript. It's moreso a demonstration of a functionally complete subset of the language.

Well, okay, so it's still strictly speaking javascript but if we consider the functionally complete subset to be our 'target' language we end up in the same place, methinks :).


Also worth mentioning the inspiration (and why the name has "fuck" in it): http://en.wikipedia.org/wiki/Brainfuck


It means it can bypass any automatic "javascript filters" which try and filter out malicious js code while allowing legitimate code.


so does any eval'd code


Unless eval is special-cased to get the evil bit set.


> While funny looking, what is the ingenuity behind it?

Is there a requirement that HN articles be ingenious? It's a novelty, that's all. And sometimes that's enough.


I guess I was looking for a respond like https://news.ycombinator.com/item?id=6385199, making it more than just a mere novelty.


"JSFuck is an esoteric and educational programming style"

How exactly is this educational?


It teaches you the funky behavior of JavaScript. For instance, `[]["filter"]` returns an empty function called filter.

In order to get particular characters (for example: f), the script uses "false"[0], where "false" is derived from adding ![] + [], and 0 is derived from +[].

Putting all of that together ($ node):

> (![]+[])[+[]]

'f'


Not sure that []["filter"] is all that funky of an example as it's just grabbing the arrays prototype method "filter" using bracket notation.

Here's some better examples of JS oddities: http://stackoverflow.com/q/9032856/1538708


The funkiness is really that []["filter"]["constructor"] is a function that behaves like eval(), or rather, it returns a function that when called executes the string that was passed to the constructor function. That's how they're actually running the code.

> []["filter"]["constructor"]("alert(1)")()


By demonstrating creative abuse of a language, one can better understand what the language constructs do and the scope of their capability.

The comparable IOCCC.org contest is a great place to learn C: sure the primary focus is making obfuscated code, and the audience marvels at the bizarreness thereof, but taking the time to pick a submission apart and figure out why it works (despite a host of reasons why you'd think it shouldn't) expands your understanding of the language.

It's a great way to improve your language skills when you've reached the initial "I know language X" confidence.


I took the 'educational' to mean 'this should never be used in a real application' :)


If someone cogitates to use this for real... it shouldn't be doing it anything at all.


Unless you consider cross-site scripting attacks to be applications.


How are the performance impacts?


Huge.

Tested a simple "for" loop: http://jsperf.com/jsfuck-perf-testa

Vanilla I got 225k ops/sec. JSFuck, 4.5 ops/sec.

So about 50000 times slower.


This is interestingly reminiscent of swearjure[1], clojure without alphanumerics. The IRC log in that post is very enlightening to see how the idea evolved into something absolutely frightening. I would like to see the history of JSFuck.

1. http://hypirion.com/musings/swearjure



I don't think this was written with the output size of the code much in mind. In one example, there was: [+[[!+[]+!+[]]]]

Which breaks down to: [!+[]+!+[]] === [2] +[[!+[]+!+[]]] === 2 [+[[!+[]+!+[]]]] === [2] //again

So I think there might be quite a bit of scope for compression even within the parameters it's built in.

Highly impressive, in any case.


Here are two more javascript encoders that are useful for getting around filters:

http://utf-8.jp/public/jjencode.html http://utf-8.jp/public/aaencode.html


This is quite funny. I learned about JSFuck in 2012 when I took some of my fellow students to a JavaScript meetup in Hamburg. JSFuck has since developed to be a kind of running gag among the students at our computer science department when it comes to things with a high WTF factor.


IE 10 gives me "alertr1(" clicking on the "run this" link (eval source deactivated). But interesting nevertheless!


This 'code' reminds me of the gobbledy-gook exchanged between the supercomputers in Colossus - The Forbin Project.


I dont get it :/ if it says: 1 => +!+[]

Then why does it still translate to 1128 characters? :/


Uncheck "Eval source".


Seems like it would be interesting to use as a cheap form of JS obfuscation.


Even 'alert(1)' produces over 500 chars. Imagine running a proper app trough this. It's hardly cheap.


cheap as in stupidly simple, relatively computationally inexpensive and largely ineffective. Not as in having a low storage overhead.


It's probably easier (and much smaller) to put all js in a file, convert it to a data-URI and add it into the html.


That isn't obsfucation though.


I don't know if the stuff mentioned here isn't reversable. I never tried it (just with images), wouldn't this not just show a big blob in the HTML file?


It wouldn't be a good way to obfuscate at all! The step before the eval would be completely decipherable.

Also, not cheap!


Here's a quick explanation:

  > []
  []
  > +[]
  0
  > !+[]
  true
  > +!+[]
  1
Lesson: + as a prepend coerces things into numbers

  > 123
  123
  > 123+[]
  "123"
  > ![]
  false
  > ![]+[]
  "false"
Lesson: + as an infix operator between incompatible types coerces everything into strings

  > Function("alert(123)")
  function anonymous() {
  alert(123) 
  }
  > Function("alert(123)")()
  [alerts 123]
Lesson: the Function object can be called with a string to make a function that evaluates that string

  > [].constructor
  function Array() { [native code] }
  > ({}).constructor
  function Object() { [native code] }
  > (function(){}).constructor
  function Function() { [native code] }
  > (function(){}).constructor("alert(123)")()
  [alerts 123]
Lesson: the constructor property returns the type of an object, and this gives you access to the Function object

  > [].filter.constructor
  function Function() { [native code] }
  > []["filter"]["constructor"]
  function Function() { [native code] }
Lesson: an array's "filter" method is a function and has Function as its constructor. Well, duh.

  > [][[]]
  undefined
  > [][[]]+[]
  "undefined"
  > !![]+[]
  "true"
  > ![]+[]
  "false"
  > (!![]+[])[1]
  "r"
  > (!![]+[])[+!+[]]
  "r"
Lesson: we now have the letters adefilnrstu, so we can construct []["filter"].

  > [].filter+[]
  "function filter() { [native code] }"
  > ({})+[]
  "[object Object]"
Lesson: we now have acdefijlnrstuv, so we can write []["filter"]["constructor"] and thus call our pseudo-eval on anything we can make as well.

  > Function("return console")()+[]
  "[object Console]"
  > Function("")+[]
  "function anonymous() {

  }"
  > 0["constructor"]+[]
  "function Number() { [native code] }"
  > '0'["constructor"]+[]
  "function String() { [native code] }"
  > Function("return assert")()+[]
  "function assert(condition, opt_message) {
    'use strict';
    if (!condition) {
      var msg = 'Assertion failed';
      if (opt_message)
        msg = msg + ': ' + opt_message;
      throw new Error(msg);
    }
  }"
Lesson: we now have all the letters we need to make ""["constructor"]["fromCharCode"].

  > String.fromCharCode(74)
  'J'
Lesson: Enjoy the rest of the alphabet. We can now construct arbitrary programs and eval them.


How long will it take till we see a decompiler? :D


Essentially it just builds a new function of a given body text, then eval's it. You could easily check out that source code before eval'ing with a log statement.


It creates a string of code. so it already ecodes.


That's ridiculous. Gotta give you props....


The woe that is Atwood's Law.


IMPORTANT QUESTION:

Why aren't the six special characters defined as follows?

  fuck()


Because that would not be a Turing-complete subset of JavaScript, and this is.


Brainfuck flavor


ihihihih... neat how it deals with gzip?


I just tried it with ZIP format using WinRAR. "alert(1)" encoded and zipped ("best" compression) is 216 bytes vs. "alert(1)" zipped is 162 bytes.


and unzipped it's 8 bytes ... ;)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: