Код, которого нет

в 13:36, , рубрики: javascript, Веб-разработка, КодоБред, магия, ненормальное программирование, обфускация

Привет, читатели!

Около года назад Хабр захлестнула волна постов на тему "%string% в N строчек на JavaScript". Уже и не вспомню, чем все закончилось, но началось все с Excel в 30 строк. Следом появилось много и других интересных вариаций на эту тему, даже игра в ноль строк на JS, но это уже совсем другая история…

Как я ни старался придумать что-то еще более компактное — ничего не выходило. Тогда было принято решение посмотреть на проблему под другим углом. Примерно в этот момент в голове промелькнул вопрос: а можно ли «сколлапсировать» код так, чтобы его не было вообще? И тут мне позвонил Дэвид Блейн.

Я попробовал добавить немного магии и вот что у меня получилось.

image

«Исчезатор» кода

Задача написать код, которого… как бы нет. Еще он должен уметь что-то делать. Очевидно, что какие-либо манипуляции нужно сопроводить некоей функцией, которая бы могла интерпретировать эти манипуляции, а поэтому скрыть код вообще, увы, не выйдет, но сократить последний до пары-тройки строчек — запросто.

Многие знают или слышали, что в компьютерной типографике существуют непечатные символы, т.е. фактически невидимые. Причем это не какой-то баг или фишка, а вполне нормальное поведение — быть невидимыми. В настоящий момент одной из общепринятых и стандартизированных кодировок текста является UTF-8, она используется практически на любом современном сайте. В ней ценно и то, что там присутствует целая куча невидимых символов! Например, один из них — Zero Width Space (U+200B). Вот он: "​". Видите? Нет? А он есть.

Метод Дэвида Блейна

Для желающих потрогать руками привожу ссылку на пример годичной давности: рабочее демо смотреть бесплатно онлайн. Уже потом, спустя несколько месяцев после заметки в песочнице Хабра, я случайно набрел на пост, где просматривалась эта идея (способ номер три), но без изюминки.

В моей версии кодирование производилось наипримитивнейшим образом. Минус — сильно возрастает объем файла, плюс — нужно всего два символа для кодирования. Выглядело примерно так:

var code = '1101101111110111111111111111110101101101111101111';

Спустя довольно длительное время, я вернулся к этой теме в рамках одного проекта, которым занимаюсь. Была предпринята попытка пойти дальше и начал с того, что теперь каждый символ кодируется не единицами и нулями, а четырьмя символами:

"f".charCodeAt(0).toString(16);
// "66"

//Таким образом код символа "f" - 0x0066
String.fromCharCode("0x0066");
// "f"

В итоге, имея набор из 16 символов, можно сократить избыток лишнего кода:

var Symbols = ["й","ц","у","к","е","н","г","ш","щ","з","х","ъ","ф","ы","в","а"];

//Теперь можно закодировать символ "f":
var bar = invisibleJS("f");
// bar = "ййгг";

Увеличение объема, занимаемого кодом в данном примере снизилось до (4 символа, чтобы закодировать один), но по идее, если не нужен русский язык и/или какие-то другие не латинские символы, то можно добиться и .

Примеры в студию

Пускай будет такой код:

alert("Hello world!");

После скармливания кода обфускатору (приводить и разбирать код не буду, в нем нет ничего интересного), на выходе получается что-то вроде:

var helloworld = "⁡⁡‫‌⁡⁡‫⁡⁡‫‪⁡⁡‬‍⁡⁡‬‏⁡⁡‍‭⁡⁡‍‍⁡⁡‏‭⁡⁡‫‪⁡⁡‫⁡⁡‫⁡⁡‫⁡⁡‍⁡⁡⁡‬‬⁡⁡‫⁡⁡‬‍⁡⁡‫⁡⁡‫‏⁡⁡‍‌⁡⁡‍‍⁡⁡‍‮";

Обратите внимание на то, что точка с запятой находится внутри кавычек, хотя на самом деле это не так (можно проверить почти в любом текстовом редакторе, например Sublime). С одной стороны, это добавляет +5 к обфускации, вводит в заблуждение и грозит легким brain-fuck'ом, с другой — «правильный» редактор не будет применять символы влияющие на направление текста (слева-направо, справа-налево).

Вот так выглядит в результате функция декодирования:

var revealJS = function(s){return s.match(/(.{4})/g).map(function(b){return b.split('').map(function(i){return Array.apply(null,{length:10}).map(Number.call,Number).concat('abcdef'.split(''))['⁡‌‍‎‏‪‫‬‭‮'.split('').indexOf(i)]})}).map(function(c){return String.fromCharCode(0+"x"+c.join(''))}).join('')}

Более чем уверен, что код мог быть лучше, меньше и элегантнее, но такой задачи не стоит в данном посте. Обращаю внимание, что в конце строки код опять идет справа-налево. В общем-то этот нюанс можно исключить, если подобрать немного другие невидимые символы.

Теперь можно «проявлять» невидимый код:

var helloworld = "⁡⁡‫‌⁡⁡‫⁡⁡‫‪⁡⁡‬‍⁡⁡‬‏⁡⁡‍‭⁡⁡‍‍⁡⁡‏‭⁡⁡‫‪⁡⁡‫⁡⁡‫⁡⁡‫⁡⁡‍⁡⁡⁡‬‬⁡⁡‫⁡⁡‬‍⁡⁡‫⁡⁡‫‏⁡⁡‍‌⁡⁡‍‍⁡⁡‍‮";

revealJS(helloworld);
// "alert("Hello world!")"

eval(revealJS(helloworld));
// Оп!

Можно прямо отсюда скопировать в консоль или посмотреть здесь.

(Приведенный в качестве примера код «проявлятора» заточен конкретно под определенные символы, использующиеся в функции «исчезатора». Меняя эти символы местами и/или используя другие, кол-во вариантов «кодовой таблицы» взлетают далеко в бесконечность.)

У всего этого есть один большой минус: можно подглядеть выполненный с помощью eval() код. Более того, консоль даже укажет файл/строчку откуда этот код запущен:
Код, которого нет - 2

Можно исправить это недоразумение. Если использовать все четыре символа для кодирования (о чем я говорил выше), то появляется возможность обфускации внутри обфускации. Xzibit в восторге:

// Брюки превращаются...
alert("Hello world!");
// Превращаются...
window["alert"]("Hello world!");
// Брюки...
window[revealJS("⁡⁡‫‌⁡⁡‫⁡⁡‫‪⁡⁡‬‍⁡⁡‬‏")](revealJS("⁡⁡‏‭⁡⁡‫‪⁡⁡‫⁡⁡‫⁡⁡‫⁡⁡‍⁡⁡⁡‬‬⁡⁡‫⁡⁡‬‍⁡⁡‫⁡⁡‫‏⁡⁡‍‌"))
// ...превращаются в невидимый код:
"⁡⁡‬‬⁡⁡‫‮⁡⁡‫⁡⁡‫‏⁡⁡‫⁡⁡‬‬⁡⁡‪⁡⁡‫‌⁡⁡‬‎⁡⁡‫⁡⁡‍‭⁡⁡‍‍‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡⁡‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡‫‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡‍‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡⁡‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡⁡⁡⁡‍‍⁡⁡‍‮⁡⁡‪⁡⁡‍‭⁡⁡‫‌⁡⁡‬‎⁡⁡‫⁡⁡‍‭⁡⁡‍‍‍⁡‫‌‍⁡‫‌‍⁡⁡‍⁡‍‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡‍‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡‫‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡‫‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡‫‍⁡‫‌‍⁡‫‌‍⁡⁡‍⁡‫‌‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡‍‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡‫‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡⁡‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡‫‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡⁡‍⁡‫‌‍⁡‫‌‍⁡⁡‍⁡⁡⁡⁡‍‍⁡⁡‍‮⁡⁡‍‮"

Смотреть можно здесь. Кстати, никто не мешает обфусцировать код хоть трижды, хоть четырежды.

Теперь результирующий код выглядит так:
Код, которого нет - 3

Уже лучше, но можно сделать еще кое-что. Вначале статьи я обозначил задачу скрыть код так, будто его нет. Сейчас же, можно просто открыть консоль и все сразу видно, что нехорошо. Устранить этот нюанс оказалось довольно просто: вместо eval() надо использовать <script></script>:

var script = document.createElement("script");
script.innerHTML = revealJS("⁡⁡‬‬⁡⁡‫‮⁡⁡‫⁡⁡‫‏⁡⁡‫⁡⁡‬‬⁡⁡‪⁡⁡‬‍⁡⁡‫‪⁡⁡‬‫⁡⁡‫‪⁡⁡‫‌⁡⁡‫⁡⁡‏⁡⁡‪‎⁡⁡‍‭⁡⁡‍‍‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡⁡‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡‫‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡‍‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡⁡‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡⁡⁡⁡‍‍⁡⁡‍‮⁡⁡‪⁡⁡‍‭⁡⁡‬‍⁡⁡‫‪⁡⁡‬‫⁡⁡‫‪⁡⁡‫‌⁡⁡‫⁡⁡‏⁡⁡‪‎⁡⁡‍‭⁡⁡‍‍‍⁡‫‌‍⁡‫‌‍⁡⁡‍⁡‍‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡‍‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡‫‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡‫‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡‫‍⁡‫‌‍⁡‫‌‍⁡⁡‍⁡‫‌‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡‍‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡‫‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡⁡‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡‫‍⁡‫‌‍⁡‫‌‍⁡‍‍⁡⁡‍⁡‫‌‍⁡‫‌‍⁡⁡‍⁡⁡⁡⁡‍‍⁡⁡‍‮⁡⁡‍‮");
document.getElementsByTagName('body')[0].appendChild(script);
// А потом сразу же удаляем тэг, чтобы не маячил перед глазами
document.getElementsByTagName('body')[0].removeChild(script);

К сожалению, при выполнении этого кода на jsFiddle код все равно проявляется. Есть подозрение, что это каким-то образом связано с тем, что код в окне JavaScript так же оборачивается в eval() или тот же <script></script>. При тестах на локальном проекте, где нет «чудес», все работает как надо, выполняемая функция себя не проявляет:
image

Области применения

1. For fun.
2. Средство обфускации кода.
3. Использование совместно с другими способами минификации/обфускации кода. Например Google Closure Compiler или UglifyJS.
4. Возможность скрытного общения на открытых площадках.
5. «Спящие» скрипты, «закладки» в статьях, сообщениях на форумах, досках объявлений, в контекстной рекламе, да вообще где угодно, где дают что-нибудь написать и это потом попадает в браузеры пользователей.

С последними двумя пунктами не все так однозначно, но не мог их не упомянуть. Немного раскрою мысль. При беглом просмотре, было обнаружено, что, например, Gmail и Яндекс.Почта не удаляют такие символы. Некоторые трансформируются в вид &zwj; ‪ &lrm;, но часть остается таки невидимой. Думаю, что в почтовых клиентах ситуация аналогичная (я проверил Thunderbird) — ничего не видно. Значит в письме можно послать скрытое сообщение, которое при «визуальном осмотре» не выдаст себя никак и при всем при этом даже в случае обнаружения непонятных скрытых символов расшифровать это сообщение сможет только тот, у кого есть алгоритм дешифрации (который, в общем-то, можно хранить в голове и написать код прямо в консоли браузера):

Привет, как дела⁡⁡‍⁡⁡‍⁡⁡‏‏‌⁡‏‎‪⁡‏‎‎⁡‏‎⁡‏‎‏⁡‏‎⁡‏‏⁡⁡‍⁡⁡‏‎‍⁡⁡‍⁡⁡‏‎⁡‏‏⁡‏‏‍⁡‏‏⁡⁡‍⁡⁡‍⁡⁡‏‎⁡‏‏⁡⁡‏‎‭⁡‏‏‪⁡‏‎⁡‏‎‏⁡‏‎‭⁡⁡‍⁡⁡‏‎⁡‏‎‏⁡‏‎‭⁡‏‎?

А на самом деле (надо применить ф-цию revealJS() к той части, что находится между буквой «а» и знаком вопроса):

Привет как дела/*, сегодня в пять, приходи один*/?

image

Таким образом есть возможность скрытного общения на открытых площадках. При этом никто ничего и не заподозрит. (Разве что целенаправлено будут искать, но это другой разговор). В общем, одно из главных преимуществ (а может и единственное) заключается в том, что содержание проходит «визуальный контроль» (но и тут не все так гладко: можно сменить кодировку и все обнажится). Это как человек-невидимка и система видеонаблюдения. Кто знает, может быть весь интернет уже давно напичкан такими сообщениями? :)

Что касается спрятанных скриптов, то тут все очевидно: если в ваш браузер каким-либо образом попадет вредоносный js-код, содержащий «проявлятор» и «запускатор» (например, многие подгружают библиотеки извне, взять тот же самый jQuery, который не так давно взламывали, или какой-нибудь распространенный плагин, например AdBlock), то спрятанный на странице код с определенной степенью вероятности будет запущен. То есть в данном случае схема такая: множество разных «векторов», каждый из которых направлен по-своему и один единый «активатор», который совсем крохотный.

Спасибо за внимание.

П.С. Маленький «ништячок»: если в скрипте вначале строки поставить символ U+202E (Right-To-Left Override) в кавычках, то будет веселье. Работоспособность кода при этом сохраняется:

"‮";var revealJS = function(s){return s.match(/(.{4})/g).map(function(b){return b.split('').map(function(i){return Array.apply(null,{length:10}).map(Number.call,Number).concat('abcdef'.split(''))['⁡‌‍‎‏‪‫‬‭‮'.split('').indexOf(i)]})}).map(function(c){return String.fromCharCode(0+"x"+c.join(''))}).join('')}

Автор: NikitaKA

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js