Эта идея возникла после того, как я увидел этот ответ на классическую задачу кодирования обхода спиральной матрицы. Тот факт, что вы могли написать функционирующий код Java, который выглядел бы полной ерундой, был захватывающим. Кроме того, недаром все это было странно красиво, как гейгеровская архитектура или один из бруталистских лабиринтов Эшера.



Оказывается, вы можете написать любой Javascript, используя всего четыре символа: !+[]
Это, как ни удивительно, настолько хорошо решаемая задача, что у нее есть своя страница в Википедии и онлайн-конвертеры JS. Он называется JSF*ck. Вы должны любить Интернет. Классический JSF*ck использует шесть символов, включая (), но, честно говоря, я просто думаю, что это лень. Мы определенно можем сделать лучше.
Есть много причудливых поведений JS, которые мы должны использовать, чтобы заставить это работать, включая перехват ошибок и преобразование их в строки, а также неразборчивое отношение JS к выполнению операций над элементами разных типов данных. Тем не менее, хорошо иметь в виду конечную цель, прежде чем начать. Чтобы иметь возможность написать любой JS, нам просто нужно получить доступ ко всем возможным символам Unicode, используя только ранее упомянутые шесть символов. (Легко, правда?)
Вот выражение, которое сделает это возможным:
Function ("return RegExp('\u####')") //where # represents a number
Такова конечная цель, условие победы. Теперь, как, черт возьми, мы доберемся туда?
Мы начнем с унарного оператора, который принимает выражение JS и пытается преобразовать это значение в число. Например:
+true; // Evaluates to 1 +false; // Evaluates to 0 +["a string"]; // Evaluates to 1 +[]; // Evaluates to 0
Последнее — это первое выражение, которое нам нужно. У нас есть 0, теперь нам нужен 1. Чтобы получить его, мы могли бы использовать оператор отрицания, но я хочу отложить использование большего количества символов в наших решениях как можно дольше, поэтому мы собираемся использовать оператор приращения для 0. Проблема в том, что вы не можете использовать его непосредственно для целого числа, вы должны сделать это для представления целого числа, такого как переменная.
++0 // SyntaxError: Invalid left-hand side expression in prefix operation let zero = 0; ++zero // Evaluates to 1
Способ обойти это — поместить 0 в массив, а затем сослаться на первый элемент в массиве.
[+[]][+[]] //The equivalent of [0][0], which is an indirect represntation of 0 ++[+[]][+[]] // Evaluates to 1 ++[+[]][+[]] + ++[+[]][+[]] // Evaluates to 2 //etc
Теперь мы можем добавить к себе единицу и получить любое число, но потребуется очень много времени, чтобы добраться до таких чисел, как 4000, то есть ключевого символа 䀀.
RegExp('\u4000') //䀀, which is the mandarin character for small cups
Это много копий и вставок, и, поскольку очевидно, что целью этого упражнения является эффективность, нам нужен короткий путь. К счастью, мы можем просто использовать странные свойства преобразования типов JS.
Таким образом, в JavaScript, когда вы пытаетесь добавить число и объект (например, массив), механизм JavaScript попытается преобразовать оба операнда в примитивное значение, которое можно использовать с оператором плюс. Вот почему 1 + [] оценивается как "1".
Вот примерные шаги, которым следуют компиляторы JS:
- Число
1уже является примитивом, поэтому его не нужно преобразовывать. - Массив
[]необходимо преобразовать в примитив, прежде чем его можно будет использовать с оператором плюс. - JavaScript сначала вызывает метод
valueOf()для массива, чтобы попытаться преобразовать его в примитив. Однако методvalueOf()массива просто возвращает сам массив, поэтому он не дает нам примитива. - Поскольку
valueOf()не сработало, JavaScript затем вызывает методtoString()для массива. МетодtoString()массива объединяет все элементы массива запятыми и возвращает результат. В случае пустого массиваtoString()возвращает пустую строку. - Итак,
[]преобразуется в""(пустая строка). - Теперь JavaScript пытается добавить
1и"". Поскольку один из операндов является строкой, JavaScript преобразует другой операнд в строку, а затем объединяет их оба. Итак,1 + ""становится"1" + "", что приводит к"1".
Теперь давайте посмотрим, как мы можем использовать эту функциональность:
[] + 4 + 0 + 0 + 0 // "4000" [] + 1+1+1+1 + 0 + 0 + 0 // "1111000" (The order of operands matters) 1+1+1+1 + [] + 0 + 0 + 0 // "4000" ++[+[]][+[]] + ++[+[]][+[]] + ++[+[]][+[]] + ++[+[]][+[]] + [] + +[] + +[] + +[] // "4000"
Мы можем использовать эту логику для получения почти любого положительного целого числа без особых проблем, что нам и нужно. Если вы хотите узнать больше о том, как получить отрицательные и десятичные числа, используя только эти три символа, посмотрите это видео.
На этом этапе мы должны начать использовать !.
[][[]] //undefined (Expression takes the empty array value of an empty array) !+[] //true !![] //true, as an alternative ![] //false
Теперь, используя преобразование строк, о котором мы говорили ранее, мы можем получить строковые значения каждого из них.
![] +[] //"false" [![]+[]][+[]][+[]] //"f"
В последнем выражении выше вы можете видеть, как теперь мы можем получить доступ к нескольким символам алфавита. Мы берем строку "false" и заключаем ее в массив, чтобы мы могли получить доступ к ее значениям, используя обозначение в квадратных скобках, как мы делали, когда пытались использовать ++ в 0 раньше.
Итак, у нас есть доступ к adefilnrstu. Что это дает нам?
Оказывается много. Мы можем получить доступ к методам прототипа объекта, используя нотацию скобок, например []["find"] или []["fill"] в качестве замены для [].find или [].fill . Мы можем превратить полученные функции в строки, используя наш старый хак +[]. Чтобы сэкономить время и избежать путаницы, я напишу не [![]+[]][+[]][+[]] + [[][[]] + []][+[]][++[+[]][+[]] + ++[+[]][+[]] + ++[+[]][+[]] + ++[+[]][+[]] + ++[+[]][+[]]] + [[][[]] + []][+[]][++[+[]][+[]]] + [[][[]] + []][+[]][++[+[]][+[]] + ++[+[]][+[]]], а "find”, но знайте, что это одно и то же. (Серьезно, бросьте это в VSCode. Это дико).
[]["find"] +[] // "function find() { [native code] }"
[]["find"] +[] // "function find() {\n [native code]\n}"
Приведенное выше выражение дает два разных результата в зависимости от вашего интерпретатора, но оба дают вам доступ к function и (){}.
Теперь, чтобы выполнить наше условие победы и построить фразу Святого Грааля Function(return RegExp(‘\u####’)), нам осталось получить только символы F r R g E x p ‘ \.
Чтобы получить F, мы будем использовать метод конструктора в нашем методе поиска. Для ясности и экономии времени я буду использовать короткие стрелки для слов и чисел.
[]["flat"]["constructor"] +[] //"function Function() { [native code] }"
Теперь у нас есть F, но и доступ к слову Function дает нам многое. Теперь мы можем использовать метод Function()constructor для создания исполняемых блоков кода. Теперь
[[]+[]][+[]]["constructor"]["name"] //String "to"+[[]+[]][+[]]["constructor"]["name"] //toString 10["toString"](36) //a 11["toString"](36) //b 12["toString"](36) //c //... 35["toString"](36) //z
Метод Number.toString принимает необязательный аргумент Radix, который позволяет вам установить основание числа. Предсказуемо по умолчанию используется 10, но основание 36 позволяет вам получить доступ ко всем символам нижнего регистра, когда метод используется для чисел 10-35.
Теперь мы можем построить try/catchstatement для захвата ошибок и, как вы уже догадались, превратить их в строки для доступа к их символам. Самая простая ошибка для построения, которая дает нам доступ к R E (forRegExp), — это ошибка ссылки.
Function("try{f+f}catch(f){return f}")()+[] //ReferenceError: f is not defined
Мы начнем использовать RegExp и наш новый хак конструктора Function(), чтобы получить доступ к нескольким последним оставшимся символам. Function("return RegExp()") +[]returns/(?:)/и если мы возьмем /песок и передадим его вFunction("return RegExp("/")") +[], то получим /\//, что даст нам наш \ символ. Нам просто нужны кавычки, и все готово.
Последнее выражение, которое мы используем, это doozy:
//SyntaxError: Unexpected token ','
Function("try{Function([]+[[]].concat([[]]))}catch(f){return f}")()+[]
Давайте немного разберемся. []+[[]].concat([[]]) дает вам ,, потому что вы объединяете два пустых массива, что дает вам [[], []]. Оператор + при использовании с массивами запускает преобразование этих массивов в строки перед выполнением конкатенации строк. Пустой массив [] при преобразовании в строку дает пустую строку "". Когда массив преобразуется в строку, все его элементы также преобразуются в строки, а затем объединяются вместе с запятой , между ними. Но в этом случае оба элемента массива являются пустыми массивами. Таким образом, при преобразовании они становятся пустыми строками. И результирующая строка представляет собой две пустые строки, разделенные запятой, в результате получается только запятая: ','.
Если мы передаем запятую в конструктор функции, тогда Function(",") выдает SyntaxError о неожиданном ','. Чтобы взять эту ошибку и превратить ее в строку, нам просто нужно обернуть ее в блок try/catch, а затем вызвать ее с помощью второго конструктора функции. И бац, у нас есть '. Божество воссоединилось, приветствую всемогущего!
Function("return RegExp('\u<insert number here>')")+[]
Источники:
https://youtu.be/5wnlYIQKPXM
https://www.youtube.com/watch?v=TdoejUAAQ2o
https://en.wikipedia .org/wiki/JSFuck
Вот немного дурацких JS в стиле удаленных сцен для вашего удовольствия:
{} + [] //0
[] + {} // '[object Object]'
{} + {} // '[object Object][object Object]'
[] + [] // ''
0 == [] //true
0 == "0" //true
"0" == [] //false
typeof NaN //number