[Из песочницы] Код, которого нет
Привет, хабравчане! Около года назад Хабр захлестнула волна постов на тему »%string% в N строчек на JavaScript». Уже и не вспомню, чем все закончилось, но началось все с Excel в 30 строк. Следом появилось много и других интересных вариаций на эту тему, даже игра в ноль строк на JS, но это уже совсем другая история…
Как я ни старался придумать что-то еще более компактное — ничего не выходило. Тогда было принято решение посмотреть на проблему под другим углом. Примерно в этот момент в голове промелькнул вопрос:, а можно ли «сколлапсировать» код так, чтобы его не было вообще? И тут мне позвонил Дэвид Блейн.
Я попробовал добавить немного магии и вот что у меня получилось.
«Исчезатор» кодаЗадача написать код, которого… как бы нет. Еще он должен уметь что-то делать. Очевидно, что какие-либо манипуляции нужно сопроводить некоей функцией, которая бы могла интерпретировать эти манипуляции, а поэтому скрыть код вообще, увы, не выйдет, но сократить последний до пары-тройки строчек — запросто.Многие знают или слышали, что в компьютерной типографике существуют непечатные символы, т.е. фактически невидимые. Причем это не какой-то баг или фишка, а вполне нормальное поведение — быть невидимыми. В настоящий момент одной из общепринятых и стандартизированных кодировок текста является UTF-8, она используется практически на любом современном сайте. В ней ценно и то, что там присутствует целая куча невидимых символов! Например, один из них — Zero Width Space (U+200B). Вот он:»». Видите? Нет? А он есть.
Метод Дэвида Блейна Для желающих потрогать руками привожу ссылку на пример годичной давности: рабочее демо смотреть бесплатно онлайн. Уже потом, спустя несколько месяцев после заметки в песочнице Хабра, я случайно набрел на пост, где просматривалась эта идея (способ номер три), но без изюминки.В моей версии кодирование производилось наипримитивнейшим образом. Минус — сильно возрастает объем файла, плюс — нужно всего два символа для кодирования. Выглядело примерно так:
var code = '1101101111110111111111111111110101101101111101111'; Спустя довольно длительное время, я вернулся к этой теме в рамках одного проекта, которым занимаюсь. Была предпринята попытка пойти дальше и начал с того, что теперь каждый символ кодируется не единицами и нулями, а четырьмя символами: «f».charCodeAt (0).toString (16); // »66»
//Таким образом код символа «f» — 0×0066 String.fromCharCode (»0×0066»); // «f» В итоге, имея набор из 16 символов, можно сократить избыток лишнего кода: var Symbols = [«й», «ц», «у», «к», «е», «н», «г», «ш», «щ», «з», «х», «ъ», «ф», «ы», «в», «а»];
//Теперь можно закодировать символ «f»: var bar = invisibleJS («f»); // bar = «ййгг»; Увеличение объема, занимаемого кодом в данном примере снизилось до 4х (4 символа, чтобы закодировать один), но по идее, если не нужен русский язык и/или какие-то другие не латинские символы, то можно добиться и 2х.Примеры в студию Пускай будет такой код: 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©{return String.fromCharCode (0+«x»+c.join (''))}).join ('')} Более чем уверен, что код мог быть лучше, меньше и элегантнее, но такой задачи не стоит в данном посте. Обращаю внимание, что в конце строки код опять идет справа-налево. В общем-то этот нюанс можно исключить, если подобрать немного другие невидимые символы.Теперь можно «проявлять» невидимый код:
var helloworld = »»;
revealJS (helloworld); // «alert («Hello world!»)»
eval (revealJS (helloworld)); // Оп! Можно прямо отсюда скопировать в консоль или посмотреть здесь.(Приведенный в качестве примера код «проявлятора» заточен конкретно под определенные символы, использующиеся в функции «исчезатора». Меняя эти символы местами и/или используя другие, кол-во вариантов «кодовой таблицы» взлетают далеко в бесконечность.)
У всего этого есть один большой минус: можно подглядеть выполненный с помощью eval () код. Более того, консоль даже укажет файл/строчку откуда этот код запущен:
Можно исправить это недоразумение. Если использовать все четыре символа для кодирования (о чем я говорил выше), то появляется возможность обфускации внутри обфускации. Xzibit в восторге:
// Брюки превращаются… alert («Hello world!»); // Превращаются… window[«alert»](«Hello world!»); // Брюки… window[revealJS (»»)](revealJS (»»)) // …превращаются в невидимый код: »» Смотреть можно здесь. Кстати, никто не мешает обфусцировать код хоть трижды, хоть четырежды.Теперь результирующий код выглядит так:
Уже лучше, но можно сделать еще кое-что. Вначале статьи я обозначил задачу скрыть код так, будто его нет. Сейчас же, можно просто открыть консоль и все сразу видно, что нехорошо. Устранить этот нюанс оказалось довольно просто: вместо eval () надо использовать :
var script = document.createElement («script»); script.innerHTML = revealJS (»»); document.getElementsByTagName ('body')[0].appendChild (script); // А потом сразу же удаляем тэг, чтобы не маячил перед глазами document.getElementsByTagName ('body')[0].removeChild (script); К сожалению, при выполнении этого кода на jsFiddle код все равно проявляется. Есть подозрение, что это каким-то образом связано с тем, что код в окне JavaScript так же оборачивается в eval () или тот же . При тестах на локальном проекте, где нет «чудес», все работает как надо, выполняемая функция себя не проявляет: Области применения 1. For fun.2. Средство обфускации кода.3. Использование совместно с другими способами минификации/обфускации кода. Например Google Closure Compiler или UglifyJS.4. Возможность скрытного общения на открытых площадках.5. «Спящие» скрипты, «закладки» в статьях, сообщениях на форумах, досках объявлений, в контекстной рекламе, да вообще где угодно, где дают что-нибудь написать и это потом попадает в браузеры пользователей.С последними двумя пунктами не все так однозначно, но не мог их не упомянуть. Немного раскрою мысль. При беглом просмотре, было обнаружено, что, например, Gmail и Яндекс.Почта не удаляют такие символы. Некоторые трансформируются в вид , но часть остается таки невидимой. Думаю, что в почтовых клиентах ситуация аналогичная (я проверил Thunderbird) — ничего не видно. Значит в письме можно послать скрытое сообщение, которое при «визуальном осмотре» не выдаст себя никак и при всем при этом даже в случае обнаружения непонятных скрытых символов расшифровать это сообщение сможет только тот, у кого есть алгоритм дешифрации (который, в общем-то, можно хранить в голове и написать код прямо в консоли браузера):
Привет, как дела? А на самом деле (надо применить ф-цию revealJS () к той части, что находится между буквой «а» и знаком вопроса): Привет как дела/*, сегодня в пять, приходи один*/? Таким образом есть возможность скрытного общения на открытых площадках. При этом никто ничего и не заподозрит. (Разве что целенаправлено будут искать, но это другой разговор). В общем, одно из главных преимуществ (а может и единственное) заключается в том, что содержание проходит «визуальный контроль» (но и тут не все так гладко: можно сменить кодировку и все обнажится). Это как человек-невидимка и система видеонаблюдения. Кто знает, может быть весь интернет уже давно напичкан такими сообщениями? :)
Что касается спрятанных скриптов, то тут все очевидно: если в ваш браузер каким-либо образом попадет вредоносный 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©{return String.fromCharCode (0+«x»+c.join (''))}).join ('')}