Pull to refresh

Прячем, обфусцируем и криптуем клиентскую часть веб-приложений

Журнал Хакер corporate blog
Обфускация — это приведение исходного текста программы к виду, сохраняющему ее функциональность, но затрудняющему анализ, понимание алгоритмов работы и модификацию при декомпиляции. Применительно к JavaScript данная технология используется в таких видах теневого онлайн-бизнеса, как загрузки (iframe), спам и SEO. Наша задача на сегодня — изучить все методы скрытия JS-кода, которые, я надеюсь, ты будешь использовать только во благо.

Обфусцированный скрипт


Теория


Во-первых, ответим сами себе на несколько вопросов:
1. Что будем прятать?
Прятать будем только клиентскую часть web-приложений, то есть то, что в конце концов загрузит к себе на компьютер обычный пользователь.
К этому типу можно отнести следующие технологии:
  • HTML-код страницы;
  • JavaScript-код/JS-файлы страницы;
  • CSS-код/CSS-файлы страницы;
  • Изображения и другую информацию (только в браузерах, поддерживающих протокол «data»).
2. От кого прятать?Прятать всю эту информацию резонно от:
  • антивирусов (если речь идет об iframe или других вредоносных скриптах);
  • других людей (например, если ты написал чудесный скрипт на JavaScript и не хочешь, чтобы кто-то его «содрал» себе).
3. Насколько можно быть уверенным в безопасности скрытой информации?
Уверенным быть нельзя.
Почему? Потому что вся информация, которую загружает пользователь, все равно выполняется браузером. Это значит, что даже если закриптовать/скрыть информацию очень сложным способом, это не будет означать, что твой код нельзя будет прочитать. Прочитать будет можно в любом случае, ты сможешь лишь затруднить это чтение. Как раз об этих способах затруднения мы и поговорим.
4. Как происходит процесс криптовки/обфускации в принципе?
Тут есть два различных этапа: создание обфусцированного/криптованного кода и его расшифровка для нормального выполнения браузером. Генерировать обфусцированный код мы будем с помощью PHP-скриптов, хотя делать это можно с помощью чего угодно. А вот расшифровку выполнения кода нужно писать на JavaScript: в этом случае скрипт, по сути, расшифрует и выполнит сам себя.

Работа JJEncode


Базовое шифрование HTML/CSS


Что делать, если нам нужно зашифровать HTML или CSS код? Все просто: зашифровать на JavaScript, а после расшифровки вставить как HTML код.
Пример вставки (без шифровки/крипта/обфускации):

<html>
<script>
var html = '<center><h3>Пример хтмл-кода</h3></center>'; // кидаем в переменную "html" код, который хотим вставить
document.getElementsByTagName('html')[0].innerHTML = html; // вставляем между тегами <html> и </html>
</script>
</html>

Точно так же мы поступим и с CSS-стилями:

<html><script>
var css = 'body{margin:0px;}.subcl{padding:5px;}'; // кидаем в переменную "css" код, который хотим вставить
document.getElementsByTagName('html')[0].innerHTML = '<style>'+css+'</style>'; // вставляем между тегами <html> и </html>
</script>
</html>

Теперь постараемся абстрагироваться от HTML и CSS и поговорить только о самой сути — сначала скрытии, а затем и криптовке JavaScript.
Прежде, чем кто-либо захочет посмотреть и расшифровать наш код, он постарается его найти. Ниже ты найдешь несколько действенных методов сокрытия кода от глаз любопытных пользователей.

Прячем слово «][akep» в теле документа


Замена атрибутов тега <script>


Все очень просто, но это нельзя считать хорошей или даже средней защитой, так как она ориентирована на невнимательность пользователей.
Сам принцип донельзя прост. Мы имеем страничку («index.html») и JavaScript-файл, код которого мы хотим скрыть («script.js»). На странице «index.html» у нас указано:

<script type="text/javascript" src="./script.js"></script>

А теперь просто создадим папку «text», в которую положим наш скрипт («script.js») под именем «javascript» и поменяем атрибуты местами. Это будет выглядеть так:

<script src="text/javascript" type="./script.js"></script>

Для лучшего эффекта сразу отвлечем внимание пользователя на путь. Например, так:

<script src="text/javascript" type="http://host.com/очень/длинный/путь/который/сразу/бросится/в/глаза/script.js"></script>

Проверено на личном опыте: работает отлично! Таким способом я сам накручивал партнерку по кликам, так как пришлось использовать накрутчик на JavaScript. Администрация партнерки его так и не увидела :).

JavaScript-обработчики


Данный способ также не является универсальным средством для сокрытия JS-кода, но все же я расскажу о нем. Главная идея состоит в том, чтобы прятать код внутрь обработчиков событий onLoad, onClick и т.д. То есть примерно в следующие конструкции:

<body onLoad="alert(1);"></body>
<textarea onClick="alert(1);"></textarea>

Например, для тегов body и frameset есть обработчик onLoad, который запустит в нем прописанный код после загрузки страницы/фрейма.
Отмечу, что не для всех объектов обработчики одинаковы.

Cookie, Referrer и адрес


JavaScript можно также спрятать и в такие нестандартные места, как cookie (document.cookie), реферрер (document.referrer) и адрес страницы (location.href). В данном случае код будет храниться как обычный текст, а выполняться с помощью функции eval(), которая берет в качестве аргумента текст и выполняет его как JavaScript-код.
В качестве примера примем такое допущение, что у нас уже установлены кукисы следующего вида:

cookievalue=||alert(1);||

Теперь выполним этот алерт следующим образом:

<script>eval(unescape(document.cookie).split("||")[1]);</script>

Здесь мы берем текст всех cookie-записей для нашего хоста и делим его на части в местах, где стоит «||». Затем берем второй элемент ([1]) и запускаем его через eval().
Данный способ не так уж и плох, так как код, который мы хотим исполнить, не виден на самой странице, а также потому, что мы можем заставить код удалить самого себя! Пример реализации:

<?php
// ставим в куки JavaScript-код + код удаления (замены на 123)
setcookie('cook', '||alert(1);document.cookie="cook=123";||');
?>

<script>
// исполняем код и удаляемся.
eval(unescape(document.cookie).split('||')[1]);
 </script>

Аналогичным образом можно использовать и другие строки, доступные через JavaScript, например, location.href и document.referrer.

Сокрытие кода на Ajax


В данном случае код будет находиться в отдельном файле, а его запуск будет осуществляться с помощью чтения этого файла и выполнения его содержимого функцией eval().
Нам понадобится составить страницу со скрываемым кодом, а также страницу с функцией запуска этого кода:
  1. Страница с кодом, который мы хотим скрыть (с именем «l»):
    alert(1);
  2. Страница с вызовом кода:
    
    <script>function x(){try{return new XMLHttpRequest();}catch(e){try{return new ActiveXObject('Msxml2.XMLHTTP');}catch(e){try{return new ActiveXObject('Microsoft.XMLHTTP');}catch(e){return null;}}}};function y(){var z=x();if(z){z.open('get','./l');z.onreadystatechange=function(){if(z.readyState==4){
    eval(z.responseText);}};z.send(null);}};y();</script>
Данный метод скрывает код в отдельном файле (в нашем случае «l»). Конечно, можно найти путь файла и открыть его, но при обфускации подобного кода найти имя файла достаточно трудно.

Нуллбайт атакует Оперу


Этот метод прост и достаточно эффективен, но, к сожалению, он рассчитан только на браузер Opera. Суть метода в том, чтобы перед скрываемым кодом поставить так называемый нуллбайт (нуллбайт или nullbyte — это символ с ASCII кодом «0»). Зачем? Затем, что Opera просто-напросто не показывает код во встроенном просмотрщике после данного символа. Пример:

<html>тут какой-либо код</html>
<?php echo(chr(0)); ?>
<script>alert(1); /* этот код мы скрыли  */</script>

В данном примере сначала идет нормальный код, который нам скрывать не требуется. Потом с помощью PHP мы вставляем нуллбайт, а после него идет скрываемый код.

Прячемся в HTML-коде и комментариях


Код можно легко спрятать в HTML, затем обработать его и выполнить. Например, вот так:

<body><img src="./pict.jpg" a="al" b="er" c="t(1);"></body>
<script>a = document.body.innerHTML; eval(a.split('a="')[1].split('"')[0]+a.split('b="')[1].split('"')[0]+a.split(' c="')[1].split('"')[0]);</script> 

В данном случае мы спрятали код в атрибутах тега img, после чего обработали код всей страницы, собирая разбросанные кусочки. Таким же способом можно скрывать текст в HTML/JavaScript комментариях:

Комментарий на HTML:
<!-- alert(1); -->
Комментарий на JavaScript:
// alert(1);
/* alert(1); */

Отдельно стоит отметить, что очень эффективно можно прятать код внутри популярных фреймворков — например, jQuery, mooTools и подобных. Эти файлы не являются подозрительными, а исследование их займет много времени (хотя всегда существует возможность автоматического сравнения оригинала и измененного файла).
Теперь же, думаю, можно поговорить о том, что, в конце концов, видит эксперт безопасности, и о том, что исследуют антивирусы. Ниже читай о наиболее популярных методах криптовки и обфускации JS-кода.

Субституция стандартных функций/методов JavaScript


Данный метод ориентирован на то, чтобы вместо стандартных функций или методов JavaScript подставить свои переменные:

До субституции:
<script>document.getElementsByTagName("html")[0].innerHTML = document.getElementsByTagName("body")[0].length;</script>
После субституции:
<script>a=document;c='getElementsByTagName';a[c]("html").innerHTML = a[c]("body")[0].innerHTML.length;</script>

В данном случае мы заменили объект «document» переменной «a», а метод getElementsByTagName переменной «c». Следует заметить, что методы (то, что начинается точкой, например, .length или .getElementsByTagName) можно также заменять определением ключа в массиве (если рассматривать объект как массив). Если у нас есть объект «document», а в нем есть элемент getElementsByTagName, это означает, что мы может вызвать его двумя способами:
  1. document.getElementsByTagName
  2. document['getElementsByTagName']
Из этого следует, что во втором способе мы используем строковые данные («getElementsByTagName»), следовательно, их можно заменить на переменную, содержащую в себе строку — название метода.
Субституция полезна в случае частого использования одного и того же стандартного объекта/функции/переменной. Это сильно меняет код, а также сжимает его.

Флуд комментариями и кодом


Данный способ рассчитан на то, чтобы вставить в обфусцируемый код флуд, то есть что-то, что не несет смысловой нагрузки для кода скрипта. Флудить можно как код, так и комментарии:

Флуд комментариями:
<script>/* pOIEPGpmkG13Pg */ a /* PGpmkG13Pggweg */ = /* mkG13Pg */ 'hahaha' /* pOIE13Pg */ ; /* wegEGoh */ alert /* oiwboierhper */ ( /* igwepreorh */ a /* wbnponrhR */ ) /* inboierh */ ; /* roinero */</script>
Флуд кодом:
<script> weoibog = 'gwrobgoerh'; a = 'hahaha'; bfionb = 'wgeogioweg'; alert(a);

В данном случае флуд комментариями слишком плотный, хотя на самом деле код был очень простым: «a = "hahaha"; alert(a);».
Флуд кодом можно также мешать с флудом комментариями. При желании можно написать PHP-функцию для добавления флуда в код JavaScript. Лично я брал какую-то статью с английского блога, парсил на слова, и функция добавляла случайное количество этих слов в комментарии.
Кстати, желательно также использовать многострочные комментарии в форме «фальшивого закрытия»:

/*/ alert(1); /*/ alert(2); /*/ alert(3); /*/

Какое из чисел покажет алерт? :)

Работа обфусцированного алерта


Замена текста шестнадцатеричными кодами


Я хотел отнести к этому пункту и кое-какие другие способы преобразования текста, но оставил только этот, так как это единственный способ шифровки, не требующий функций-помощников для расшифровки:

<script> alert(document["\x63\x6F\x6F\x6B\x69\x65"]); </script>

В данном случае мы — во-первых, использовали внутреннюю переменную «cookie» объекта «document», как элемент массива. Во-вторых, мы перевели ее имя в шестнадцатеричный формат. Если бы мы использовали переменную «cookie» через точку, то есть как document.cookie, то мы бы не смогли перевести обращение к ней в шестнадцатеричный формат, так как это относится только к строкам (в массиве ключ является строкой), а в document.cookie строк нет.
PHP-функция перевода в шестнадцатеричный формат:

<?php
function cescape($s)
{
	foreach (str_split($s,1) as $sym)
	{
		$d = dechex(ord($sym));
		$c[] = (strlen($d) == 1) ? '0'.$d : $d;
	}
	return ('х'.'\\'.implode('х'.'\\',$c));
}
?>

Трюк с несуществующими функциями


Как мы уже знаем из прочитанного выше, в JavaScript можно вызывать методы, как элементы обьекта: document.getElementById и document['getElementById']. Оба варианта фактически одинаковы, различие есть только в записи — во втором варианте мы используем строку.
Как-то вечером я придумал очень интересный способ получения подобных строк. Например, нам нужно зашифровать вышеупомянутый «getElementById». Отвлечемся на короткое объяснение данного способа с помощью такого примера:

<script> a = b(c(d())); </script>

Этот скрипт не будет работать, так как функции b, c и d не были ранее объявлены. Теперь попробуем сделать так, чтобы этот код заработал, для этого будем использовать «песочницу» конструкции try{}catch(){}:

<script> try{a = b(c(d()))}catch(e){alert(e);} </script>

После запуска мы увидим ошибку, а это значит, что, хоть код и не является рабочим, он не остановил выполнение оставшейся корректной части.
А вот теперь мы зададимся вопросом, как такая схема может быть связана с шифрованием строки «getElementById»? А вот так:

<script>try{(getE(leme(ntB(yId()))))}catch(e){x = (e+'').split('(').slice(1,5).join('');}</script>

После выполнения этого кода у нас получится строка «getElementById», содержащаяся в переменной «x».
В чем соль этого метода? В том, что эвристический анализ антивирусов при нахождении функций будет ругаться на то, что они не существуют. Тем самым мы обфусцируем код не на уровне шифровки строк разными способами, а на уровне получения данных строк от самого JavaScript.

Числа с помощью оператора «~»


Оператор «~» (тильда) является битовым отрицанием и используется вот так: «alert(~13);». Этот код выведет нам «-14». Работает данный оператор по принципу «-(число+1)».
Представим, что мы хотим присвоить переменной «a» какое-нибудь число, причем нигде это число не писать: «a = ~[]»;
Данный код присвоит переменной «a» число «-1». Почему? Потому что массив представляет собой нейтральный элемент с числовым значением «0», следовательно, ~0 равносильно «-(0+1)», то бишь -1.
Примеры других преобразований:

a = ~[]; // -1
a = -~[]; // 1
a = []^[]; // 0
a = ~~[]; // 0
a = ~true; // -2
a = ~false; // -1
a = -~[]*(""+-~[]+-~-~-~-~-~[]+-~-~true); // 153

Буквы и строки без строковых данных


Иногда требуется получить букву/символ или какой-то текст без его явного написания. Сделать это позволяет одна особенность JavaScript. В этом языке существуют различные внутрисистемные сообщения, которые можно преобразовать в текст, а затем этот текст обработать.
Например, представим, что нам нужно получить текст «code». Эта строка содержится в именах таких методов, как charCodeAt(), fromCharCode() и других. Получить текст можно следующим образом:

a = (alert+'').split("ive ")[1].substr(0,4);

В данном примере переменная «a» будет содержать текст «code». Разберем подробнее. Попробуй исполнить вот такой код: «alert(alert+'');». Ты увидишь что-то вроде «function alert() { [native code] }». Тем самым, использовав всего-навсего два раза функцию alert(), мы получили совершенно другие символы.
Теперь постараемся понять, как это все работает. Представим, что у каждого объекта, функции и всего остального в JavaScript есть некое «описание». Чтобы получить к нему доступ, нужно явно изменить тип данного объекта или функции на строковой, присоединив, например, пустую строку (+"").

Шифровка строк


Для шифровки/расшифровки строк в JavaScript существуют несколько полезных функций. Разберем некоторые из них:

escape(); // шифрует строку как URL
unescape(); // дешифрует URL-строку
encodeURI(); // шифрует строку как URI
decodeURI(); // дешифрует URI-строку

Также есть два метода объекта String, которые работают с преобразованием символа в ASCII-код и наоборот:

a = String.fromCharCode(97);
b = "b".charCodeAt();

Стоит учесть, что строки можно преобразовывать еще и регулярными выражениями в сочетании с методами .match и .replace. Другие методы можно отнести, скорее, к поиску по строке.

Преобразование объектов/переменных


Имена объектов и переменных можно также преобразовать в строку (например, чтобы потом эту строку зашифровать). Преобразование происходит по тому же принципу, что и преобразование имен методов, то есть с помощью перехода из формы «.метод» в форму «[метод]». Для корректного преобразования нужно найти еще более высокий в иерархии объектов элемент, который бы имел внутри себя слово «document». Имя ему this. Согласно стандартам JavaScript, this не является объектом, а является оператором, возвращающим ссылку на объект. В результате теперь мы можем безболезненно использовать getElementById таким образом: «this[«document»][«getElementById»]».

Привязка кода


Иногда возникает необходимость написать код так, чтобы он выполнялся только после соблюдения некоторых условий. Например, мы создали JavaScript-код и хотим его продавать, но продавать мы хотим с привязкой к домену, чтобы его нельзя было запустить на других сайтах.
Еще раз повторюсь: никакой абсолютной защиты не придумать в принципе, есть лишь некоторые способы, затрудняющие процесс копирования/отвязки.
Вот несколько типов таких привязок + данные, от которых они зависят:
  • привязка к домену // location.href.split('/')[2];
  • привязка к параметрам (передаются странице после #?) // location.href.split('#')[1] или location.href.split('?').slice(1);
  • привязка к дате // a = new Date();
  • привязка к коду JavaScript // ;
  • привязка к коду всей страницы // a = document.getElementsByTagName('html')[0].innerHTML;
  • привязка к браузеру // a = navigator.userAgent;
  • привязка к куки-записям // document.cookie;
  • любые другие привязки, которые можно придумать.

Избегание подозрительных функций


Советую также избегать явное использование функций eval(), document.write() и других. При поиске настоящего кода люди часто используют метод подстановки alert() вместо данных функций, так как после этого код можно сразу прочитать таким, каким мы его начинали шифровать, следовательно, весь смысл обфускации пропадает. Как же выполнить код, не используя фунцкию eval()?
Вспомним про то, что во главе всего стоит оператор this. С помощью него функцию eval() можно превратить вот в такой код:

a = this["\x65\x76\x61\x6C"];

После такого преобразования мы спокойно сможем использовать «a()» вместо «eval()».

Изменение на нечитаемые строки


В обфусцированном коде следует использовать следующие символы и их комбинации для обозначения идентификаторов:
  1. "o", "O", "0"
  2. "i", "I", "l", "1"
  3. "_" (и варианты "__", "___" ...)
  4. "$" (и варианты "$$", "$$$" ...)
После использования подобных символов код становится крайне трудночитаемым, особенно если его сжать, убрав лишние пробелы и переносы строк.

Шифрование кода


Способов шифровки текста существует неограниченное количество, хотя все они основаны на использовании каких-либо текстовых/числовых функций. Часто работает конструкция: eval() + функция_расшифровки() + шифрованная_строка. Попробую без лишней воды показать один из таких способов.
Допустим, нам нужно зашифровать строку «alert(1);». Мне пришло в голову брать по два символа из нее, переводить их в числа (ASCII код), считывать их и рядом ставить первый символ в чистой (без перевода) форме. Только стоит учесть, что, разделяя код на такие двухбуквенные части, мы получим код примерно в 2-2,5 раза больше оригинала, а также нельзя забывать, что такие блоки лучше как-то разделять (как элемент массива или через разделитель). За разделитель возьмем знак «%», так как он делает шифрованную строку похожей на URL-строку. Напишем простой PHP-скрипт:

<?php
	$a = "alert(1);";
	$a = str_split($a, 2);
	$e = '';
	foreach ($a as $v)
	{
		$e .= '%' . $v[0] . (ord($v[0])+ord($v[1]));
	}
	echo($e);
?>

Вот что у нас получилось: «%a205%e215%t156%190%;59».
А теперь напишем дешифровщик этого кода на JavaScript:

function d(s)
{
	s = s.split('%').slice(1);
	c = '';
	for (i = 0; i < s.length; i++)
	{
		c += s[0] + String.fromCharCode(s.substr(1)-s[0].charCodeAt());
	}
	return c;
}

Вызов кода в таком случае будет выглядеть так: «eval(d('%a205%e215%t156%190%;59'));».

Теперь остается только немного обфусцировать весь этот скрипт. Мы не будем использовать все описанные методы, а затронем лишь некоторые из них:

z = '73706C697421736C696365216C656E6774682166726F6D43686172436F6465217375627374722163686172436F64654174';
_='';
for(__=0;__<z.length/2;__++){_+=unescape('%'+z[__*2]+z[__*2+1]);}
_=_[_[0]+_[1]+_[2]+_[3]+_[4]]('!');function ___(__){__ = __[_[0]]('\x25')[_[1]](-~[]); _I='';for (_l=0;_l<__[_[2]];_l++){_I+=__[_l][0]+String[_[3]](__[_l][_[4]](1)-__[_l][0][_[5]]());}return _I;}
this['\x65\x76\x61\x6C'](___('%a205%e215%t156%190%;59'));

Рассмотрим подробнее процесс работы этого на первый взгляд нечитабельного кода:
  1. z = '....' Здесь переменной присваевается текст, который был получен переводом строки split!slice!length!fromCharCode!substr!charCodeAt в шестнадцатеричны вид (\x73\x70\x6C\x69\x74...) без "\х";
  2. _='';for(...} Тут мы переводим обратно split!slice!length!fromCharCode!substr!charCodeAt в переменную "_";
  3. _=_...('!'); Разделяем строку в тех местах, где есть символ "!";
  4. function ___(__){...} Описанная выше функция d() в обфусцированном виде;
  5. this['\x65\x76\x61\x6C'](....); Декодирование строки и запуск кода.

Напоследок


Ну что ж, подведем итоги. При комбинации всех этих способов можно быть на 100% уверенным, что простой или даже средний пользователь не сможет прочитать или скопировать к себе твой код. Но так как специалисты по компьютерной безопасности прекрасно знают о большинстве данных трюков, а также потому что я выкладываю эту информацию на всеобщее обозрение, могу предположить, что данные методы станут более популярными и известными. Я надеюсь, что ты сможешь использовать представленную информацию в благих целях.

Делаем скрипт читаемым



image
Журнал Хакер, Сентябрь (09) 152
|qbz| (lopuxin.iv@yandex.ru, http://essenzo.net)
.

Подпишись на «Хакер»
Tags:
Hubs:
Total votes 181: ↑153 and ↓28 +125
Views 103K
Comments Comments 62

Information

Founded
Location
Россия
Website
xakep.ru
Employees
51–100 employees
Registered