Оглавление


Недавно мы опубликовали детальный анализ тактик, техник и процедур 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

GET

/owa/auth/OutlookCN.aspx

200

POST

/owa/auth/OutlookCN.aspx

200

POST

/owa/auth/OutlookCN.aspx

200

N POST-запросов к /owa/auth/OutlookCN.aspx c кодом 200

Day 2

GET

/ecp/auth/TimeoutLogout.aspx

200

POST

/ecp/auth/TimeoutLogout.aspx

500

POST

/ecp/auth/TimeoutLogout.aspx

200

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. Его файловая структура выглядит так:

Файловая структура фреймворка BADSTATE
Файловая структура фреймворка BADSTATE

Опишем подробно каждый файл:

Файл / каталог

Описание

remotecmd.py

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

280 строк.

Каталог commanders

Содержит Python-классы, которые отвечают за сетевое взаимодействие с жертвой.

baseviewstate.py

Базовый класс ViewStateCommander:

шифрование / расшифровка данных, отправка полезных нагрузок.

139 строк.

sessionviewstate.py

Класс OldViewStateCommander

для работы с аутентифицированными сессиями

Не реализован до конца и не используется нигде в коде. Также в коде имеется класс NewViewStateCommander с заглушкой pass.

Оба класса являются дочерними ViewStateCommander.

33 строки.

Каталог downloads

Каталог для сохранения загружаемых данных жертвы.

Каталог targets

Каталог, в котором размещаются каталоги жертв.

params.json

Конфигурационный файл жертвы.

payload.b64

Зашифрованный VIEWSTATE, который при десериализации загружает в память .NET сборку основного веб-шелла фреймворка BADSTATE и запускает его. Мы назвали веб-шелл ViewStateExecutor. Он выполняет все команды оболочки фреймворка.

command.b64

Зашифрованный VIEWSTATE для активации основного веб-шелла ViewStateExecutor в памяти.

webshell.dll

Вредоносный .NET exe-файл, который создает виртуальный файл заданного содержимого по указанному веб-пути.

response.txt

Файл-шаблон для ответов основного веб-шелла.

Описание файлов фреймворка 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

Краткое описание параметров конфигурационного файла жертвы:

Параметр

Описание

valAlgo

Названия параметров говорят сами за себя. Эти данные получаются из конфигурационных файлов приложения на IIS-сервере жертвы.

valKey

decKey

legacyMode

Не используется в коде.

noerrorMode

Определяет, какая VIEWSTATE-нагрузка будет отправлена жертве первой. Подробнее далее.

vsg

Значение VIEWSTATEGENERATOR. Зависит от pagePath.

appPath

Параметры приложения, на которое будут отправляться вредоносные VIEWSTATE.

pagePath

После запуска remotecmd.py выполняется инициализация фреймворка и появляется оболочка BADSTATE:

Оболочка BADSTATE
Оболочка BADSTATE

На рисунке также виден список команд, доступный после ввода команды help.

Краткое описание команд:

Команда

Описание

EOF

Завершить работу с оболочкой BADSTATE с выводом Bye!

exit

Завершить работу с оболочкой BADSTATE.

reload

Обновить данные жертвы.

reload – повторно считать конфигурационный файл params.json для текущей цели.

reload <target_name> – считать файлы из <target_name>\params.json.

lcd

Перейти в каталог на C2 (выполняется локально в оболочке).

list_dir

Получить листинг указанного каталога в формате <dir1_name>, <file1_name> – <filesize>, …

remove_file

Удалить указанный файл / каталог.

get_free_space

Получить свободное место указанного логического диска в байтах

get_free_space C:\.

sysinfo

Получить информацию о системе (OS, User, Domain, Groups).

shell

Загрузить шелл-код с указанного URI и запустить его.

Запуск шелл-код не реализован (пустая функция).

run

Выполнить указанную команду путем создания дочернего процесса

run <process_to_run> <process_arguments>.

upload

Загрузить указанный файл по указанному пути на хост жертвы upload <attacker_file_path> <victim_file_path>.

В ответ возвращается размер скопированного файла.

short_download

Загрузить указанный файл с хоста жертвы.

В ответ приходит base64-кодированное содержимое файла, которое декодируется фреймворком и помещается в каталог downloads.

static_download

Параметры:

resource_url <filename>.<ext> [<local_file>].

Загрузить файл с URI:

resouce_url/<hex_ext>/<filename> в downloads\<local_file> или downloads\<filename>.<ext> Если <ext> не указано, то используется "00".

executeassembly

Создать и запустить задачу по загрузке в память и запуску указанного .NET exe-файла с параметрами.

checkassembly

Получить вывод сборки, запущенной командой executeassembly.

Если вывод >1000 байт, то записать его в txt-файл со случайным именем в текущем каталоге.

cancelassembly

Принудительно завершить поток выполнения сборки, запущенной командой executeassembly.

webshell

Загрузить в память .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> … – аргументы запуска сборки.

cmd_option

Описание

wait

Запустить exe-файл с параметрами в основном потоке веб-шелла. Ждать окончания его выполнения и отправить вывод оператору.

nowait

Запустить exe-файл в отдельном потоке. Через секунду после запуска оператору возвращается 1000 байт вывода (даже если сборка ещё не завершила свою работу).

check

Получить вывод запущенной сборки.

Если сборка выполняется долго, то сначала ее можно запустить с параметром nowait, а потом через check проверять вывод и таким образом, получить результаты работы.

cancel

Завершить поток, в котором выполняется сборка. По умолчанию сборке дается 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:

MD5

b9f9d586d8a26b03c22773e10bc14d41

SHA1

762307a5d6abe0c00b7e759967b173723c141977

SHA256

58302c780f37c0ef449a45ba4e8607ac6d6036e8e2f2a54540ed345f324cec19

Module name

WebForm_7a310da2.exe

File name

webshell.dll

File size

7168 bytes (7 KB)

Comp. timestamp

2023-12-28 12:45:18

После запуска создает в памяти процесса виртуальный файл (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.

MD5

81093c3bf3ca623174866612c696f1b7

SHA1

5d3a44d18ca476b1ceb18ad20063bcfa1fc0c3bd

SHA256

3d18d9e9351a45376c3b0183299906464d07aaf2c86272b80608e66787a82779

Module name

WebForm_de37a582.dll

File name

payload.dll

File size

19456 bytes (19 KB)

Comp. timestamp

2023-12-26 15:37:39

Изначально этот веб-шелл в BADSTATE хранится в файле payload.b64, внутри которого — зашифрованная VIEWSTATE-нагрузка, представленная в виде base64-закодированнной строки. В аналогичном формате хранится и payload активации веб-шелла в файле command.b64. Скорее всего, такой вариант хранения сделан по следующим причинам:

  • Можно с легкостью заменять нагрузки для разных целей с разными ключами VIEWSTATE. Для этого необходимо сгенерировать зашифрованную VIEWSTATE-нагрузку и закодировать ее в base64.
  • Удобство применения в фреймворке. BADSTATE просто считывает содержимое данных файлов и подставляет их в параметр VIEWSTATE POST-запросов без какой-либо дополнительной обработки.

Параметр конфига атакуемой цели noerrorMode определяет, какой payload или какой b64-файл будет отправляться первым на хост жертвы:

noerrorMode

Зашифрованная VIEWSTATE-нагрузка, которая отправляется первой

true

веб-шелл ViewStateExecutor (payload.b64)

false

нагрузка для выполнения команды (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

В инциализированном состоянии 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.

Параметр

Описание

__VIEWSTATE

Одна из нагрузок: активация веб-шелла или сам веб-шелл ViewStateExecutor.

__EVENTARGUMENT

Номер команды и ее аргументы в формате json.

__EVENTTARGET

Формат ответа (файл response.txt).

__EVENTVALIDATION

Используется командами executeassembly, webshell и upload, то есть везде, где передается файл на хост жертвы.

В значении – передаваемый файл, который перед шифрованием сжимается с помощью gzip.compress и только потом шифруется.

__VIEWSTATEGENERATOR

Значение vsg из params.json

__VIEWSTATEENCRYPTED

Данный ключ добавляется в запрос, если 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 после расшифровки имеет следующий вид:

Декодированный VIEWSTATE с самописным гаджетом

Примечательно, что 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].

Callback десериализации в веб-шелле ViewStateExecutor
Callback десериализации в веб-шелле ViewStateExecutor

Это говорит о том, что метод с таким атрибутом будет вызван после десериализации соответствующего объекта класса. Обычно это используется для корректирования значений объекта после десериализации. Shedding Zmiy использовали данный callback для вызова основного метода веб-шелла, в котором выполняются команды.

Нагрузка command.b64 представляет собой сериализованный объект WebForm.A internal класса A. Она предназначена только для активации callback’а десериализации, который вызывается в веб-шелле, поэтому больше никаких полезных данных в ней нет. На картинке видно место срабатывания callback’а десериализации (RaiseDeserializationEvent) и сам объект WebForm.A.

Место срабатывания callbacka десериализации
Место срабатывания callbacka десериализации

С подобной техникой активации веб-шелла мы сталкиваемся впервые и не нашли похожей информации в публичных источниках, поэтому считаем ее уникальной. Она позволяет держать в памяти один небольшой экземпляр веб-шелла. Если фреймворк 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

Комментарий

payload.dll

81093c3bf3ca623174866612c696f1b7

веб-шелл ViewStateExecutor.

webshell.dll

b9f9d586d8a26b03c22773e10bc14d41

badstate\targets\<redacted>\webshell.dll
DLL, которая записывает заданное содержимое по указанному виртуальному пути (ghost file). Путь делается доступным извне через переопределение провайдера виртуального пути (Virtual Path Provider) в .NET.

remotecmd.py

7d626c1bb19def4369386ee06aa08f76

badstate\remotecmd.py

основной файл BADSTATE фреймворка. Запускает интерактивную оболочку фреймворка.

sessionviewstate.py

692b9d3376f735931214664afe4eb7bb

badstate\commanders\sessionviewstate.py

Класс OldViewStateCommander

для работы с аутентифицированными сессиями.

Не реализован до конца и не используется нигде в коде.

baseviewstate.py

fa31f5578b5d51c3354ee56d72d564b8

badstate\commanders\baseviewstate.py

Базовый класс 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>