Оглавление
- Атака через подрядчика
- Фреймворк BADSTATE
- Особенности VIEWSTATE-нагрузок
- Shedding Zmiy vs Obstinate Mogwai. Сравнение техник десериализации
- Заключение
- IOCs
- Приложение I - Расширенная информация по файловым IOCs
- Приложение II — Примеры запроса фреймворка BADSTATE
- Приложение III — Распакованный код гаджета XamlAssemblyLoadFromFile
Недавно мы опубликовали детальный анализ тактик, техник и процедур Shedding Zmiy – активной восточно-европейской прогосударственной группировки, атакующей российские организации минимум с 2022 года. Инструментарий злоумышленников оказался слишком обширен, поэтому мы решили разделить на несколько публикаций описание всего, с чем мы столкнулись, распутывая этот змеиный клубок. В этом посте расскажем про кейс, в котором группировка Shedding Zmiy использовала уязвимость десериализации ненадёжных данных в параметре VIEWSTATE ASP.NET.
Мы уже подробно писали об истории этой уязвимости и практике её эксплуатации в целевых атаках азиатской группировки Obstinate Mogwai. Но Shedding Zmiy использует уязвимость десериализации в собственном стиле.
Атака через подрядчика
Как мы писали ранее, начальным вектором атаки Shedding Zmiy была эксплуатация уязвимости Log4j (CVE-2021-44228, CVE-2021-45046 и CVE-2021-45105) в системе управления проектами YouTrack. После чего был атакован Exchange сервер и украдены ключи валидации и расшифровки VIEWSTATE.
В ходе расследования в IIS-логах мы наблюдали следующие запросы с IP-адреса 91.142.73[.]205 (хостинг провайдер VDSina) с User-Agent Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64;+rv:103.0)+Gecko/20100101+Firefox/103.0:
method |
uri-stem |
status |
---|---|---|
Day 1 |
||
|
|
|
|
|
|
|
|
|
N POST-запросов к /owa/auth/OutlookCN.aspx c кодом 200 |
||
Day 2 |
||
|
|
|
|
|
|
|
|
|
K POST-запросов к /ecp/auth/TimeoutLogout.aspx c кодом 200 |
Эти запросы не выглядят вредоносными и действительно выполнены к легитимным ресурсам. Однако нас смутил как IP-адрес источника, так и тот факт, что мы обнаружили на системе загрузчик XDHijack, который был создан в окрестности времени выполнения вышеупомянутых запросов в директории C:\Program Files\Microsoft\Exchange Server\V15\Bin\dismperf.dll.
В Application логах для события с кодом ответа 500 мы нашли следующее событие:
О том, как правильно анализировать подобные события, читайте в нашей публикаций об Obstinate Mogwai.
Событие Viewstate verification failed. Reason: Viewstate was Invalid (Event detail code: 50204) является довольно опасным, так как может сигнализировать о возможности RCE через десериализацию вредоносного VIEWSTATE, который указывается в поле PersistedState.
В данном случае VIEWSTATE зашифрован, так как стандартный VIEWSTATE начинается с символов /wE
. Для его расшифровки необходим decryption key из web.config-файла для приложения ECP. В случае использования auto generated ключей (по умолчанию используются сразу после установки Exchange) алгоритм получения ключа усложняется, так как decryption key получается из мастер-ключа, который хранится в реестре.
Подобные VIEWSTATE можно расшифровывать, например, opensource-утилитой viewgen с помощью команды:
viewgen -e --decode --valg "SHA256" --dalg "AES" --dkey "<decryption_key>" -f "path-to-file-with-b64-viewstate"
Однако для успешного декодирования нам пришлось пропатчить код метода decode, так как при работе утилиты “из коробки” отбрасывалось больше байт, чем требовалось.
Изменения внесли в данную строку:
Было: random_bytes_size = block_size
Стало: random_bytes_size = 24-block_size
Кроме того, мы добавили код для записи расшифрованного VIEWSTATE в файл, так как без этого viewgen пытается декодировать расшифрованный VIEWSTATE :
with open(os.path.join(os.getcwd(), 'decrypted_viewstate.bin'), 'wb') as f:
f.write(unpad_dec[random_bytes_size:-idx])
Код viewgen полезен для понимания формата зашифрованного VIEWSTATE, хотя он в некоторых моментах отличается от исходного кода. Подробности расшифровки VIEWSTATE доступны в исходниках .NET в методе EncryptOrDecryptData.
После расшифровки был получен следующий VIEWSTATE:
Чтобы лучше понять расшифрованные данные VIEWSTATE, приведем их структуру, полученную из анализа документации:
struct SERIALIZED_VIEWSTATE_without_MAC {
byte Marker_Format = 0xFF; // https://referencesource.microsoft.com/#System.Web/UI/ObjectStateFormatter.cs,116
byte Marker_Version_1 = 0x01; // https://referencesource.microsoft.com/#System.Web/UI/ObjectStateFormatter.cs,117
byte token = 0x32; // Token_BinarySerialized https://referencesource.microsoft.com/#System.Web/UI/ObjectStateFormatter.cs,90
byte length_7BitEncodedInt = 0x76; // https://referencesource.microsoft.com/#mscorlib/system/io/binaryreader.cs,582
SERIALIZED_DATA d; // has size of decoded length_7BitEncodedInt (0x76)
}
struct SERIALIZED_DATA {
struct SerializationHeaderRecord {
byte binaryHeaderEnum = 0x0 (SerializedStreamHeader);
dword topId = 0x1;
dword headerId = 0xFFFFFFFF;
dword majorVersion = 0x1;
dword minorVersion = 0x0;
}
data; // serialized data itself
}
Как видно из структуры, полученные данные VIEWSTATE, сериализованы провайдером BinaryFormatter, как и большинство гаджетов утилиты ysoserial.net, которую злоумышленники обычно используют для генерации подобных нагрузок. Однако код в сериализованных данных не похож ни на один гаджет из этого набора.
На тот момент не было понимания, какие функции выполняют данные во VIEWSTATE, но мы точно знали, что IP-адрес, с которого шли эти запросы, был ранее замечен в других атаках группировки. Позднее мы получили на анализ дополнительные файлы злоумышленников, исследование которых помогло воссоздать всю картину происходящего.
Фреймворк BADSTATE
Среди дополнительных файлов мы обнаружили уникальный самописный фреймворк для эксплуатации десериализации VIEWSTATE, который назывался BADSTATE и написан на Python. Его файловая структура выглядит так:
Опишем подробно каждый файл:
Файл / каталог |
Описание |
---|---|
|
Основой файл фреймворка. Содержит реализацию всех доступных команд и предоставляет интерактивную оболочку оператору. 280 строк. |
|
Содержит Python-классы, которые отвечают за сетевое взаимодействие с жертвой. |
|
Базовый класс ViewStateCommander: шифрование / расшифровка данных, отправка полезных нагрузок. 139 строк. |
|
Класс OldViewStateCommander для работы с аутентифицированными сессиями Не реализован до конца и не используется нигде в коде. Также в коде имеется класс NewViewStateCommander с заглушкой pass. Оба класса являются дочерними ViewStateCommander. 33 строки. |
|
Каталог для сохранения загружаемых данных жертвы. |
|
Каталог, в котором размещаются каталоги жертв. |
|
Конфигурационный файл жертвы. |
|
Зашифрованный VIEWSTATE, который при десериализации загружает в память .NET сборку основного веб-шелла фреймворка BADSTATE и запускает его. Мы назвали веб-шелл ViewStateExecutor. Он выполняет все команды оболочки фреймворка. |
|
Зашифрованный VIEWSTATE для активации основного веб-шелла ViewStateExecutor в памяти. |
|
Вредоносный .NET exe-файл, который создает виртуальный файл заданного содержимого по указанному веб-пути. |
|
Файл-шаблон для ответов основного веб-шелла. |
Описание файлов фреймворка BADSTATE
Основной файл имеет следующие параметры запуска:
remotecmd.py <target_name> <target_host>
-
<target_name>
– название подкаталога жертвы в каталоге targets -
<target_host>
– базовый URL IIS веб-сервера жертвы.
Пример запуска:
remotecmd.py target1 https://<IP_IIS_server>/
В процессе запуска обрабатывается конфигурационный файл <target_name>/params.json, который содержит данные об атакуемой жертве. Файл имеет следующий формат:
{
"valAlgo": "HMACSHA256",
"legacyMode": true,
"noerrorMode": true,
"valKey": "<validation_key>",
"decAlgo": "AES",
"vsg": "<VIEWSTATEGENERATOR_value>",
"decKey": "<decryption_key>",
"appPath": "/ecp",
"pagePath": "/ecp/auth/TimeoutLogout.aspx"
}
Формат конфигурационного файла жертвы params.json
Краткое описание параметров конфигурационного файла жертвы:
Параметр |
Описание |
---|---|
|
Названия параметров говорят сами за себя. Эти данные получаются из конфигурационных файлов приложения на IIS-сервере жертвы. |
|
|
|
|
|
Не используется в коде. |
|
Определяет, какая VIEWSTATE-нагрузка будет отправлена жертве первой. Подробнее далее. |
|
Значение VIEWSTATEGENERATOR. Зависит от pagePath. |
|
Параметры приложения, на которое будут отправляться вредоносные VIEWSTATE. |
|
После запуска remotecmd.py выполняется инициализация фреймворка и появляется оболочка BADSTATE:
На рисунке также виден список команд, доступный после ввода команды help.
Краткое описание команд:
Команда |
Описание |
---|---|
|
Завершить работу с оболочкой BADSTATE с выводом Bye! |
|
Завершить работу с оболочкой BADSTATE. |
|
Обновить данные жертвы. reload – повторно считать конфигурационный файл params.json для текущей цели. reload <target_name> – считать файлы из <target_name>\params.json. |
|
Перейти в каталог на C2 (выполняется локально в оболочке). |
|
Получить листинг указанного каталога в формате <dir1_name>, <file1_name> – <filesize>, … |
|
Удалить указанный файл / каталог. |
|
Получить свободное место указанного логического диска в байтах get_free_space C:\. |
|
Получить информацию о системе (OS, User, Domain, Groups). |
|
Загрузить шелл-код с указанного URI и запустить его. Запуск шелл-код не реализован (пустая функция). |
|
Выполнить указанную команду путем создания дочернего процесса run <process_to_run> <process_arguments>. |
|
Загрузить указанный файл по указанному пути на хост жертвы upload <attacker_file_path> <victim_file_path>. В ответ возвращается размер скопированного файла. |
|
Загрузить указанный файл с хоста жертвы. В ответ приходит base64-кодированное содержимое файла, которое декодируется фреймворком и помещается в каталог downloads. |
|
Параметры: resource_url <filename>.<ext> [<local_file>]. Загрузить файл с URI: resouce_url/<hex_ext>/<filename> в downloads\<local_file> или downloads\<filename>.<ext> Если <ext> не указано, то используется "00". |
|
Создать и запустить задачу по загрузке в память и запуску указанного .NET exe-файла с параметрами. |
|
Получить вывод сборки, запущенной командой executeassembly. Если вывод >1000 байт, то записать его в txt-файл со случайным именем в текущем каталоге. |
|
Принудительно завершить поток выполнения сборки, запущенной командой executeassembly. |
|
Загрузить в память .NET сборку webshell.dll из каталога жертвы и выполнить её с заданными параметрами. |
Таблица команд оболочки BADSTATE
Подробнее опишем особенности некоторых команд:
1) executeassembly
Загружает в память процесса сборку и запускает ее EntryPoint.
Формат запуска команды:
executeassembly <path-to-exe> <cmd_option> <arg1> <arg2> …
-
path-to-exe
– путь до .NET-сборки в формате exe; -
cmd_option
– строка, от которой зависит тип запуска exe-файла. -
<arg1> <arg2> …
– аргументы запуска сборки.
|
Описание |
---|---|
|
Запустить exe-файл с параметрами в основном потоке веб-шелла. Ждать окончания его выполнения и отправить вывод оператору. |
|
Запустить exe-файл в отдельном потоке. Через секунду после запуска оператору возвращается 1000 байт вывода (даже если сборка ещё не завершила свою работу). |
|
Получить вывод запущенной сборки. Если сборка выполняется долго, то сначала ее можно запустить с параметром nowait, а потом через check проверять вывод и таким образом, получить результаты работы. |
|
Завершить поток, в котором выполняется сборка. По умолчанию сборке дается 24 часа на выполнение, после чего поток принудительно завершается. |
Таблица аргументов запуска сборки
Команда executeassembly в коде помещается в словарь tasks, в котором нет других элементов. Это указывает на то, что в будущем у веб-шелла могут появиться другие задачи с новым функционалом.
2) webshell
Загружает в память .NET сборку webshell.dll из каталога жертвы и запускает её с заданными параметрами.
Формат запуска команды:
webshell <path_to_webshell_file> <webshell_virtual_filename>
-
<path_to_webshell_file>
– путь до файла веб-шелла (в качестве веб-шелла мы наблюдали файл webshells\tunnel.aspx – файл веб-шелла Neo-reGeorg). -
<webshell_virtual_filename>
– имя виртуального файла webshell, по которому можно будет к нему обращаться. Далее мы поясним, что это значит.
На сервере злоумышленников webshell.dll – это .NET exe-файл, накрытый open source обфускатором Obfuscar:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
После запуска создает в памяти процесса виртуальный файл (ghost file) с указанным в параметрах содержимым, переопределяя ASP.NET класс VirtualPathProvider. Более подробно об этой технике описано в статье компании MDSec и блоге 3gstudent.
Параметр <webshell_virtual_filename>
задает путь, по которому будет доступен виртуальный веб-шелл. Например, если значение pagePath в конфиге цели задано /ecp/auth/Timeoutlogout.aspx, а в данном параметре указать /test/webshell.aspx, то можно будет обращаться к виртуальному веб-шеллу по пути /ecp/auth/test/webshell.aspx. При этом в файловой системе такого aspx-файла не будет.
Данный прием можно рассматривать как evasion-технику, так как обычно при обнаружении запросов к подобным aspx-файлам последние рекомендуют удалить. Но в данном случае удалять нечего, веб-шелл будет находиться в памяти worker-процесса до его перезапуска. Подобные виртуальные файлы можно детектировать как по ETW-событиям загрузки в память неподписанных сборок, так и по появлению скомпилированных файлов в следующих каталогах в зависимости от атакуемого приложения:
C:\Windows\Microsoft.NET\Framework | Framework64\<version>\Temporary ASP.NET Files\ecp\hash1\hash2
C:\Windows\Microsoft.NET\Framework | Framework64\<version>\Temporary ASP.NET Files\owa\hash1\hash2
3) static_download
Работает на основе вредоносных маршрутов, которые создаются через команду statichandler другого веб-шелла Shedding Zmiy, который мы назвали ExchangeExporter. Его анализ мы представим в другом посте.
Веб-шелл ViewStateExecutor
Основной файл, который выполняет все команды фреймворка BADSTATE, представляет собой веб-шелл. Мы назвали его ViewStateExecutor, так как большинство его команд направлены на выполнение произвольного кода на стороне жертвы, а метод выполнения – десериализация VIEWSTATE.
ViewStateExecutor представляет собой x64 .NET DLL-файл, который также накрыт протектором Obfuscar, как и webshell.dll.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Изначально этот веб-шелл в BADSTATE хранится в файле payload.b64, внутри которого — зашифрованная VIEWSTATE-нагрузка, представленная в виде base64-закодированнной строки. В аналогичном формате хранится и payload активации веб-шелла в файле command.b64. Скорее всего, такой вариант хранения сделан по следующим причинам:
- Можно с легкостью заменять нагрузки для разных целей с разными ключами VIEWSTATE. Для этого необходимо сгенерировать зашифрованную VIEWSTATE-нагрузку и закодировать ее в base64.
- Удобство применения в фреймворке. BADSTATE просто считывает содержимое данных файлов и подставляет их в параметр VIEWSTATE POST-запросов без какой-либо дополнительной обработки.
Параметр конфига атакуемой цели noerrorMode определяет, какой payload или какой b64-файл будет отправляться первым на хост жертвы:
|
Зашифрованная VIEWSTATE-нагрузка, которая отправляется первой |
---|---|
|
веб-шелл ViewStateExecutor (payload.b64) |
|
нагрузка для выполнения команды (command.b64) |
Как мы уже описывали выше, в любом запросе фреймворка BADSTATE всегда отправляются:
- VIEWSTATE-нагрузка;
- команда для выполнения;
- параметры команды.
В качестве нагрузки может использоваться как command.b64 (активация веб-шелла), так и payload.b64 (сам веб-шелл).
В режиме noerrorMode = False
жертве сначала отправляется мелкая по размеру нагрузка активации веб-шелла (command.b64). Если в памяти worker-процесса ещё нет веб-шелла, то возникнет ошибка выполнения команды. Следующим запросом будет отправлен сам веб-шелл ViewStateExecutor. В случае успешной десериализации он будет загружен в память worker-процесса, а BADSTATE перейдет в инициализированное состояние (initialised = True
).
В режиме noerrorMode = True
жертве сразу отправляется веб-шелл ViewStateExecutor, и в случае успешной десериализации BADSTATE также переходит в инициализированное состояние (initialised = True
).
В инциализированном состоянии BADSTATE всегда отправляет в качестве нагрузки command.b64, активируя веб-шелл в памяти.
Сетевое взаимодействие
Взаимодействие с веб-шеллом происходит через POST-запросы следующего формата:
post_data = {
'__VIEWSTATE': (None, payload),
'__EVENTARGUMENT': (None, encCommand),
'__EVENTTARGET': (None, response_format)
'__EVENTVALIDATION': (None, byte_arg)
'__VIEWSTATEGENERATOR': (None, vsg)
'__VIEWSTATEENCRYPTED': (None, '')
}
Значения каждого из ключей зашифрованы AES-128 CBC и закодированы в base64.
Параметр |
Описание |
---|---|
|
Одна из нагрузок: активация веб-шелла или сам веб-шелл ViewStateExecutor. |
|
Номер команды и ее аргументы в формате json. |
|
Формат ответа (файл response.txt). |
|
Используется командами executeassembly, webshell и upload, то есть везде, где передается файл на хост жертвы. В значении – передаваемый файл, который перед шифрованием сжимается с помощью gzip.compress и только потом шифруется. |
|
Значение vsg из params.json |
|
Данный ключ добавляется в запрос, если VIEWSTATE зашифрован. В BADSTATE по умолчанию параметры VIEWSTATE всегда шифруются. |
Формат команды в ключе __EVENTARGUMENT:
{"CommandType": <command_type>, "StringArgument": [<arg0>, <arg2>, <arg3>, …]}
Типы команд:
commandTypes = {
"sysinfo":0,
"shellcode":1,
"run": 2,
"download":3,
"ls": 4,
'rm': 5,
'free': 6,
'assembly': 7,
'upload': 8
}
Примечательно, что имена параметров, в которых отправляются значения, выбраны не случайно. Они мимикрируют под легитимные параметры ASP.NET (__EVENTARGUMENT, __EVENTTARGET, __EVENTVALIDATION). Пример такого POST-запроса с ответом приведен в Приложении II.
Особенности VIEWSTATE-нагрузок
Как упоминалось выше, в BADSTATE-фреймворке имеется две основные зашифрованные VIEWSTATE-нагрузки:
- активация веб-шелла – файл command.b64;
- веб-шелл ViewStateExecutor – файл payload.b64.
Каждая из нагрузок собирается под конкретную жертву и зашифрована, поэтому для их создания необходимы оба ключа (validation и decryption) какого-либо доступного извне приложения жертвы (например, owa или ecp). То есть перед использованием фреймворка BADSTATE необходимо сначала скомпрометировать оба ключа жертвы.
Нагрузка payload.b64 – веб-шелл ViewStateExecutor
Нагрузка payload.b64 после расшифровки имеет следующий вид:
Примечательно, что Shedding Zmiy используют самописный гаджет, который распаковывает и вызывает гаджет XamlAssemblyLoadFromFile из набора ysoserial. Он сжат с помощью gzip compress, закодирован в base64 и помещен в их собственный гаджет, откуда вызывается методом XamlReader.Load(Stream stream).
Полученный из xaml C# код кастомного гаджета Shedding Zmiy:
var data = Convert.FromBase64String(b64_gzip_XamlAssemblyLoadFromFile_gadget);
var inputStream = new MemoryStream(data);
var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress);
var parser = XamlReader.Load(gzipStream);
А так в распакованном гаджете XamlAssemblyLoadFromFile (код гаджета целиком можно найти в Приложении III) выглядит запакованная DLL веб-шелла ViewStateExecutor, про которую мы рассказывали выше:
<s:Array x:Key="data" x:FactoryMethod="s:Convert.FromBase64String">
<x:Arguments>
<s:String>H4sIAAAAAAAEAO18a2BU1bXwOufMnDNzZjLJ...<REDACTED></s:String>
</x:Arguments>
</s:Array>
Возможно, кастомный гаджет был применен в качестве Defense Evasion техники.
Нагрузка command.b64 – активация веб-шелла
Напомним, что нагрузка command.b64 после расшифровки имеет следующий вид:
Именно эту нагрузку в зашифрованном виде мы наблюдали в событии 1316 журнала Application. Она представляет собой VIEWSTATE с binary serialized данными, в которых видно упоминание сборки WebFrom_de37a582 – веб-шелла ViewStateExecutor. В ходе глубокого анализа мы выяснили, что эта нагрузка после десериализации активирует веб-шелл ViewStateExecutor в памяти через callback.
В декомпилированном коде веб-шелла видно, что имеется internal class A, у которого есть атрибут [OnDeserialized].
Это говорит о том, что метод с таким атрибутом будет вызван после десериализации соответствующего объекта класса. Обычно это используется для корректирования значений объекта после десериализации. Shedding Zmiy использовали данный callback для вызова основного метода веб-шелла, в котором выполняются команды.
Нагрузка command.b64 представляет собой сериализованный объект WebForm.A internal класса A. Она предназначена только для активации callback’а десериализации, который вызывается в веб-шелле, поэтому больше никаких полезных данных в ней нет. На картинке видно место срабатывания callback’а десериализации (RaiseDeserializationEvent) и сам объект WebForm.A.
С подобной техникой активации веб-шелла мы сталкиваемся впервые и не нашли похожей информации в публичных источниках, поэтому считаем ее уникальной. Она позволяет держать в памяти один небольшой экземпляр веб-шелла. Если фреймворк BADSTATE работает в режиме noerrorMode = false и не инициализирован, то после десериализации нагрузки активации возникнет событие 1316 журнала Application, которое содержит минимум информации и может ввести в заблуждение исследователей, не встречавшихся с таким способом активации.
Shedding Zmiy vs Obstinate Mogwai. Сравнение техник десериализации
В статье про десериализацию VIEWSTATE мы описывали, как группировка Obstinate Mogwai использовала традиционные техники внедрения и активации веб-шелла, а также другие стандартные гаджеты. Как мы видим, Shedding Zmiy действует иначе. Сравним параметры, связанные с десериализацией VIEWSTATE и фреймворками двух группировок.
Shedding Zmiy BADSTATE framework ViewStateExecutor webshell |
Obstinate Mogwai ViewState exploitation framework |
---|---|
Payload activation |
|
Одна сборка ViewStateExecutor webshell в памяти для выполнения всех команд фреймворка BADSTATE |
Сборка в памяти на выполнение каждой команды фреймворка |
События 1316 журнала Application |
|
Событие в начале работы с веб-шеллом в режиме errorMode = false |
Событие на использование гаджета ActivitySurrogateDisableTypeCheck |
Размеры payload |
|
ViewState webshell activation payload (command.b64) – 256 bytes ViewState ViewStateExecutor webshell – 23 790 bytes ViewStateExecutor webshell assembly – 19 456 bytes |
ViewState ActivitySurrogateDisableTypeCheck payload – 5 164 bytes ViewState Ps assembly payload – 17 300 bytes Assembly payloads – 5 120 or 5 632 bytes |
Используемые гаджеты |
|
Customized XamlAssemblyLoadFromFile |
ActivitySurrogateDisableTypeCheck ActivitySurrogateSelectorFromFile |
Группировка Shedding Zmiy использует более продвинутую технику десериализации, разрабатывая собственные сложные гаджеты-матрешки, в то время как Obstinate Mogwai полагается на стоковые ysoserial. Это особенно примечательно в контексте того, что исторически десериализацию VIEWSTATE использовали в основном азиатские APT-группировки.
Заключение
Shedding Zmiy имеет в своем арсенале фреймворк BADSTATE, основанный на эксплуатации уязвимости десериализации VIEWSTATE. В нем содержатся как минимум два уникальных веб-шелла для проведения атак на IIS-серверы (в приоритете Exchange-серверы).
Использование callback-а десериализации для активации веб-шеллов, кастомного гаджета для их загрузки, а также команд по добавлению вредоносных маршрутов – все это демонстрирует высокий уровень знаний в области десериализации и среды ASP.NET.
Видно, что обнаруженные фреймворк и веб-шеллы находятся в активной разработке. Например, в веб-шелле ViewStateExecutor имеются нереализованные команды (запуск шелл-код в команде shell) и много функций, которые не используются и, скорее всего, были просто перенесены из веб-шелла ExchangeExporter.
Все вышеперечисленное может означать, что в ближайшем будущем Shedding Zmiy будет атаковать более продвинутым инструментарием.
IOCs
File hashes
md5
81093c3bf3ca623174866612c696f1b7
b9f9d586d8a26b03c22773e10bc14d41
7d626c1bb19def4369386ee06aa08f76
692b9d3376f735931214664afe4eb7bb
fa31f5578b5d51c3354ee56d72d564b8
sha1
5d3a44d18ca476b1ceb18ad20063bcfa1fc0c3bd
762307a5d6abe0c00b7e759967b173723c141977
e29e0ea89126db232db75890381e286e375547d9
852e6dbdc86c360b1024f59a7f3d5100c5e88fcd
e3cd102fe0666abbfbc134a494d70c9a803d09c2
sha256
3d18d9e9351a45376c3b0183299906464d07aaf2c86272b80608e66787a82779
58302c780f37c0ef449a45ba4e8607ac6d6036e8e2f2a54540ed345f324cec19
c7e18ea14704d88398cf31f834e89ef59743f04b92ad74a7b882ca11db228899
e7f80084419ceac2d4015436efb652de72f0f459a4e2df12751f6d49ef6699ad
35545711eaffcc5ec42c94d85d4cb2fe4b8b856d9d386e11ba0f9d79df93f5b0
Deserialization source IP
91.142.73[.]205
Приложение I - Расширенная информация по файловым IOCs
Имя файла |
MD5 |
Комментарий |
---|---|---|
|
|
веб-шелл ViewStateExecutor. |
|
|
|
|
|
основной файл BADSTATE фреймворка. Запускает интерактивную оболочку фреймворка. |
|
|
Класс OldViewStateCommander для работы с аутентифицированными сессиями. Не реализован до конца и не используется нигде в коде. |
|
|
Базовый класс ViewStateCommander: шифрование / расшифровка данных, отправка полезных нагрузок. |
Приложение II — Примеры запроса фреймворка BADSTATE
Пример запроса
POST /ecp/auth/TimeoutLogout.aspx HTTP/1.1
Host: <redacted>
Accept-Encoding: gzip, deflate, br
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
Content-Length: 1599
Content-Type: multipart/form-data; boundary=73a28bcc07c328777d929d204e3b6197
Connection: close
--73a28bcc07c328777d929d204e3b6197
Content-Disposition: form-data; name="__VIEWSTATE"
GHK0h1moSz3zS9ibIvDn5stUYrjHc/n8vh/mCTH+F6L2UP6CQ+Q2z7gwMPmB4RDbqmW3Ou5MDV6GTROPPK5TmuoD1dGoi6Xb1PRQJFf4kPdTdW3GHK1+ASaR0wK0ESU2LUEygso+K359GVBUY31UtKC8u21eBHA/dfgTGSU12r3m7TvNbDahApf50IeMtZhuYxlq8BcWxdq9hmrolEBJJuHwWfUvhmRxd/eLg+QZjACn3b0+f9L098c1afb1y/Ie
--73a28bcc07c328777d929d204e3b6197
Content-Disposition: form-data; name="__EVENTARGUMENT"
<encrypted__EVENTARGUMENT>
--73a28bcc07c328777d929d204e3b6197
Content-Disposition: form-data; name="__EVENTTARGET"
<encrypted__EVENTTARGET>
--73a28bcc07c328777d929d204e3b6197
Content-Disposition: form-data; name="__VIEWSTATEGENERATOR"
277B1C2A
--73a28bcc07c328777d929d204e3b6197
Content-Disposition: form-data; name="__VIEWSTATEENCRYPTED"
--73a28bcc07c328777d929d204e3b6197--
Пример ответа:
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/10.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: <redacted>
Connection: close
Content-Length: 638
<!DOCTYPE HTML>
<html>
<head>
<title>
</title>
</head>
<body>
<form method="post" action="?" id="form1">
<div class="aspNetHidden">
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="<encrypted_response>" />
</div>
<div class="aspNetHidden">
<input type="hidden" name="__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="277B1C2A" />
<input type="hidden" name="__VIEWSTATEENCRYPTED" id="__VIEWSTATEENCRYPTED" value="" />
</div>
</form>
</body>
</html>
Приложение III — Распакованный код гаджета XamlAssemblyLoadFromFile
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:r="clr-namespace:System.Reflection;assembly=mscorlib"
xmlns:i="clr-namespace:System.IO;assembly=mscorlib"
xmlns:c="clr-namespace:System.IO.Compression;assembly=System"
>
<s:Array x:Key="data" x:FactoryMethod="s:Convert.FromBase64String">
<x:Arguments>
<s:String>H4sIAAAAAAAEAO18a2BU1bXwOufMnDNzZjLJ...<REDACTED></s:String>
</x:Arguments>
</s:Array>
<i:MemoryStream x:Key="inputStream">
<x:Arguments>
<StaticResource ResourceKey="data"></StaticResource>
</x:Arguments>
</i:MemoryStream>
<c:GZipStream x:Key="gzipStream">
<x:Arguments>
<StaticResource ResourceKey="inputStream"></StaticResource>
<c:CompressionMode>0</c:CompressionMode>
</x:Arguments>
</c:GZipStream>
<s:Array x:Key="buf" x:FactoryMethod="s:Array.CreateInstance">
<x:Arguments>
<x:Type TypeName="s:Byte"/>
<x:Int32>19456</x:Int32>
</x:Arguments>
</s:Array>
<ObjectDataProvider x:Key="tmp" ObjectInstance="{StaticResource gzipStream}" MethodName="Read">
<ObjectDataProvider.MethodParameters>
<StaticResource ResourceKey="buf"></StaticResource>
<x:Int32>0</x:Int32>
<x:Int32>19456</x:Int32>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<ObjectDataProvider x:Key="asmLoad" ObjectType="{x:Type r:Assembly}" MethodName="Load">
<ObjectDataProvider.MethodParameters>
<StaticResource ResourceKey="buf"></StaticResource>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<ObjectDataProvider x:Key="types" ObjectInstance="{StaticResource asmLoad}" MethodName="GetTypes">
<ObjectDataProvider.MethodParameters/>
</ObjectDataProvider>
<ObjectDataProvider x:Key="firstType" ObjectInstance="{StaticResource types}" MethodName="GetValue">
<ObjectDataProvider.MethodParameters>
<s:Int32>0</s:Int32>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<ObjectDataProvider x:Key="createInstance" ObjectInstance="{StaticResource firstType}" MethodName="InvokeMember">
<ObjectDataProvider.MethodParameters>
<x:Null/>
<r:BindingFlags>512</r:BindingFlags>
<x:Null/>
<x:Null/>
<x:Null/>
<x:Null/>
<x:Null/>
<x:Null/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</ResourceDictionary>