Недавно я видел видео, на котором Ахмед С. Касмани анализирует сценарий ComRAT PowerShell для получения основного вредоносного ПО, которое он сбрасывает на компьютер жертвы. Если вы еще не смотрели это видео, настоятельно рекомендую вам его посмотреть. В этом документе будут подробно рассмотрены аналогичные детали, а также мой собственный подход к деобфускации сценариев PowerShell, чтобы добраться до основной полезной нагрузки. Чтобы продолжить, вы можете использовать этот хеш для загрузки этого скрипта с VirusTotal:

134919151466c9292bdcb7c24c32c841a5183d880072b0ad5e8b3a3a830afef8

Так что же такое «ComRAT» помимо города и муниципалитета в Молдове и столицы автономной области Гагаузия? Он был начат хакерской группой Turla, одной из самых передовых хакерских групп в России, спонсируемых государством, которая начала свою деятельность в 2007 году. Хотя последняя версия ComRAT v4 была создана в 2017 году, она все еще используется немного сегодня.

Образ действий хакерской группы Turla заключался в том, чтобы атаковать правительственные и военные объекты. С тех пор Турлу называли другими именами, такими как Змея, Криптон и Ядовитый Медведь.

Цепочка атак

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

Погружение в PowerShell

В этом лабораторном упражнении мы собираемся использовать Visual Studio Code на виртуальной машине Windows, поскольку у них есть отличный линтер для сценариев PowerShell. Давайте откроем файл и погрузимся в него.

Сначала меня поразили три основные вещи: 1) это много base64, 2) PowerShell неправильно отформатирован и 3) имена переменных полностью рандомизированы. Сначала давайте позаботимся о том, сколько строк кода занимает base64. Мы можем легко исправить это, перейдя в View->Toggle Word Wrap и сняв флажок, просто щелкнув по нему. Теперь мы хотим, чтобы это было правильно отформатировано, это можно исправить, нажав SHIFT+ALT+F.

Это выглядит намного чище! Пора разбить две функции внутри этой оболочки PowerShell и начать переименовывать имена функций / переменных. Начнем с первого. Похоже на какой-то генератор строк.

Обфускация имен функций и переменных

function TVM730egf([string[]]$GP50afa) {
     $UC33gfa = ((1..(Get-Random -Min 2 -Max 4) | 
        % { [Char](Get-Random -Min 0x41 -Max 0x5B) }) -join '');
     $EQ33abh = ((1..(Get-Random -Min 2 -Max 4) | 
        % { [Char](Get-Random -Min 0x30 -Max 0x3A) }) -join '');
     $OFK689fa = ((1..(Get-Random -Min 2 -Max 4) | 
        % { [Char](Get-Random -Min 0x61 -Max 0x6B) }) -join '');
     $TTG32aa = $UC33gfa + $EQ33abh + $OFK689fa;
     if ($GP50afa -contains $TTG32aa) {
          $TTG32aa = Get-RandomVar $GP50afa;
     } 
     $GP50afa += $TTG32aa;
     return $TTG32aa, $GP50afa;
}

Первые три строки выглядят как генерирующие только заглавные буквы размером от 2 до 4 байтов. Вторая строка делает то же самое, что и строка 1, но генерирует только числа. Третий генератор генерирует строчную строку от 2 до 4 байтов. Давайте переименуем несколько переменных и посмотрим, как это будет выглядеть.

function rand_string_generator([string[]]$param1_str) {
     $rand_upper_str = ((1..(Get-Random -Min 2 -Max 4) ...
     $rand_num_str = ((1..(Get-Random -Min 2 -Max 4) ...
     $rand_lower_str = ((1..(Get-Random -Min 2 -Max 4) ...
     $rand_str_gen = $rand_upper_str 
                     + $rand_num_str 
                     + $rand_lower_str;
     if ($param1_str -contains $rand_str_gen) {
          $rand_str_gen = Get-RandomVar $param1_str;
     } 
     $param1_str += $rand_str_gen;
     return $rand_str_gen, $param1_str;
}

Теперь мы можем скопировать эту функцию, вставить ее в командную строку PowerShell и посмотреть, как будет выглядеть результат.

PS C:\Users\ryancor> rand_string_generator("test")
FN36dd
test
FN36dd

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

$rand_string_array = @();
[string]$PS061hh, [string[]]$rand_string_array = 
                        rand_string_generator $rand_string_array;
[string]$RPW45dij, [string[]]$rand_string_array = 
                        rand_string_generator $rand_string_array;
[string]$RIZ505ia, [string[]]$rand_string_array = 
                        rand_string_generator $rand_string_array;
...
PS C:\Users\ryancor> $rand_string_array
XLA320efe
YUP59cg
CB456fgb
BW13chi
NQG095gg
NP120ceh
YG27gf
OXN26bd
VE440ihi
GH90ggd

Если мы посмотрим на массив и возвращенные одиночные случайные строки, на них больше не будет ссылок в программе. При этом, если мы обратим внимание на то, как конкретно помечены имена функций и переменных, мы обнаружим огромное сходство с выводом выше. Генератор строк принимает строку и объединяет массив случайных байтов, которые начинаются с двух-трех заглавных букв, за которыми следуют два-три целых числа, а затем, наконец, две-три строчные буквы. Весь этот сценарий следует этому XXX000xxx соглашению об именах. Таким образом, можно с уверенностью сказать, что именно так они запутали весь дроппер, поскольку я предполагаю, что авторская копия этого сценария PowerShell содержит символы отладки, которые помогли разработчикам вредоносных программ обеспечить контроль качества их работы, прежде чем отправлять ее своим целям / жертвам.

Выполнение встроенного кода C #

Пора перейти к function PAZ488af, который ссылался на генератор случайных строк, но мы собираемся начать сверху, поскольку он содержит важную информацию о том, что будет удалено, а также переименовывает некоторые переменные, чтобы лучше понять, что здесь происходит. Начиная с первых 10 строк, происходит уже столько всего:

$task_sched = New-Object -ComObject('Schedule.Service');
$task_sched.connect('localhost');
$objFoldr = $task_sched.GetFolder($param2);
$null_task = $task_sched.NewTask($null);
[string]$filename = [System.IO.Path]::GetTempFileName();
Remove-Item -Path $filename -Force;
[string]$ps1_name = [System.IO.Path]::GetFileName($filename);
$ascii = New-Object System.Text.ASCIIEncoding;
$base64_decoded_bytes = 
              [Convert]::FromBase64String("cHVibGljIHN0YXRpY....");
$ps_decoded_class = $ascii.GetString($base64_decoded_bytes, 0, 
                                     $base64_decoded_bytes.Length);
try { 
      Add-Type $ps_decoded_class -erroraction 'silentlycontinue' } 
 catch {
      return;
 }

Первые четыре строки посвящены проверке наличия папки и планированию задачи на Microsoft\Windows\Customer Experience Improvement Program, мы пока не знаем, какое значение это имеет, но, возможно, мы узнаем позже. Если вам интересно, как я узнал, что $param2 было в $task_sched.GetFolder($param2);, все, что мне нужно было сделать, это проследить, как вызывалась эта функция, а вторая и последняя строка этого дроппера PowerShell показывает использованные строковые аргументы.

Следующие 3 строки будут извлекать имя сценария PowerShell и удалять из него путь, пока он не станет просто строкой имени файла. Теперь последние несколько строк приведенного выше сценария декодируют большую строку base64, поэтому мы можем использовать cyberchef, чтобы увидеть, что это такое.

Похоже, какой-то интересный встроенный C #! Так что мне нравится делать, поскольку это имя класса, скорее всего, будет упоминаться в нашем скрипте, - это скопировать и вставить его в наш файл дроппера. Да, вы можете выполнять функции C # из PowerShell, и это то, что пытается сделать оператор try,except. Как показано в документации Microsoft, командлет Add-Type позволяет определить класс Microsoft .NET Core в сеансе PowerShell. Затем вы можете создавать экземпляры объектов с помощью командлета New-Object и использовать их так же, как вы бы использовали любой объект .NET Core.

Итак, давайте переименуем имя класса RZP645be в decryption_class, а функцию в XD014ic на decrypt, поскольку это выглядит как простое многоключевое байтовое дешифрование XOR. Вы заметите, как мы заменяем это в скрипте, мы видим, что он вызывается несколько раз в скрипте PowerShell.

$TEX262hh = 'H4sIAAAAAAAEAIy5xw7ETJIeeB9g3qEhCJAEzgy9KQ...'
$HT29hh = [Convert]::FromBase64String($TEX262hh);
$MO67cc = 'H4sIAAAAAAAEAIy5xw7ETJIeeB9g3qEhCJAEzgy9KQ...'
$PVU468aa = [Convert]::FromBase64String($MO67cc);
$GS459ea = "$((1..(Get-Random -Min 8 -Max 10) | % 
        {[Char](Get-Random -Min 0x3A -Max 0x5B)}) -join '')
        $((1..(Get-Random -Min 5 -Max 8) | % {[Char](Get-Random 
        -Min 0x30 -Max 0x3A)}) -join '')
        $((1..(Get-Random -Min 8 -Max 10) | %{[Char](Get-Random 
        -Min 0x61 -Max 0x7B)}) -join '')";
[byte[]]$JQ587aa = [decryption_class]::decrypt($HT29hh, 
                                        $ascii.GetBytes($GS459ea));
[byte[]]$QIG418ba = [decryption_class]::decrypt($PVU468aa, 
                                        $ascii.GetBytes($GS459ea));
$AT85ced = [Convert]::ToBase64String($JQ587aa);
$ARO88iab = [Convert]::ToBase64String($QIG418ba);

Давайте разберемся с этим, у нас есть две чрезвычайно большие строки base64, и поэтому мы начнем с тех, которые используют cyberchef. Как только вы воспользуетесь декодером base64, вы заметите, что обе эти закодированные строки имеют очень похожие заголовки, так что это должно что-то значить:

...........¹Ç.ÄL..x.`Þ¡!...Î.½)

Проблема в том, что мы понятия не имеем, что это за формат файла. Таким образом, мы можем использовать Detect File Type plugin cyberchef, чтобы помочь нам идентифицировать себя.

Похоже, у нас распаковывается еще больше кода PowerShell. Итак, мы можем начать переименовывать переменные, чтобы этот скрипт выглядел чище.

Мы не уверены, что он расшифровывает, учитывая тот факт, что это сжатые байты, а не зашифрованные байты из того, что мы смогли доказать с помощью cyberchef, но, возможно, это станет более ясным по мере нашего продвижения. На этом этапе я взял распакованный код, переместил его в отдельный файл, назвав его dropper_part_2.ps1, и переформатировал его.

Вернемся к нашему основному скрипту дроппера, потому что нам нужно взглянуть на эту функцию ([decryption_class]::decrypt) немного подробнее. После того, как сценарий расшифровывает декодированные байты, он присваивает определенные значения указателя.

[byte[]]$decrypted_ps_bytes_1 = 
                          [decryption_class]::decrypt($ps_decoded_1, 
                                        $ascii.GetBytes($rand_key));
[byte[]]$decrypted_ps_bytes_2 = 
                          [decryption_class]::decrypt($ps_decoded_2, 
                                        $ascii.GetBytes($rand_key));
$base64_encoded_decrypted_bytes_1 = 
                   [Convert]::ToBase64String($decrypted_ps_bytes_1);
$base64_encoded_decrypted_bytes_2 =         
                   [Convert]::ToBase64String($decrypted_ps_bytes_2);
...
$sqmclient_reg_path = "HKLM:\SOFTWARE\Microsoft\SQMClient\Windows";
if ([System.IntPtr]::Size -eq 4) {
    $HQO388ea = $base64_encoded_decrypted_bytes_1;
}
else {
    $HQO388ea = $base64_encoded_decrypted_bytes_2;
}

У нас есть два способа выяснить, какова цель дешифрования: мы можем просто выяснить, что [System.IntPtr]::Size делает, или мы можем отладить это. Ленивый способ - посмотреть документацию Microsoft. В нем указано, что размер указателя или дескриптора в этом процессе измеряется в байтах. Значение этого свойства - 4 в 32-битном процессе и 8 в 64-битном процессе. Вы можете определить тип процесса, установив переключатель /platform при компиляции кода с помощью компиляторов C # и Visual Basic. Теперь мы знаем, почему было декодировано в основном два идентичных сценария PowerShell: один, скорее всего, сбросит 64-битную DLL или EXE, а другой скрипт сбросит 32-битный.

Письменные и настойчивые механизмы

Как вы можете видеть ниже, после переименования некоторых переменных мы видим, что основная цель остальной части скрипта - создание планировщиков, триггеров и выполнения с помощью двоичного файла wsqmcons, который является программным компонентом Microsoft . Консолидатор Windows SQM отвечает за сбор и отправку данных об использовании в Microsoft. Wsqmcons - это файл, который запускает консолидатор Windows SQM, и обычно считается безопасным файлом для вашего ПК. В этом случае он используется в злонамеренных целях. Модификация запланированной задачи, показанная ниже, указывает на то, что основной целью этой модификации задачи является декодирование и выполнение сценария PowerShell, содержащегося в разделе реестра HKLM:\SOFTWARE\Microsoft\SQMClient\Windows = WSqmCons, и сценарий вставит полезную нагрузку в раздел реестра WsqmCons.

Теперь, зная это, я могу пропустить оставшуюся часть основного сценария, который мы рассматривали. Таким образом, мы можем снова сосредоточить наше внимание на скрипте, который мы только что декодировали (скрипт, который мы дублировали dropper_part_2.ps1).

PE капельница

Анализируя первые несколько строк, он выглядит довольно похоже на то, что мы видели раньше в основном скрипте. Я возьму эту строку base64 и расшифрую ее в cyberchef. Как только вы это сделаете, вы заметите еще один кусок кода C #.

Когда мы выделяем некоторые общедоступные классы и функции, мы видим, где они выделяются в сценарии PowerShell. VO01bag, который имеет функции XOP22aj & RJ85ige, выглядит как простое сжатие и распаковка с помощью Gunzip, поэтому мы можем переименовать их соответствующим образом. Класс WQS70fb и функция YQ498hff выглядят так, как будто они вводят байты и записывают их в файл. Я тоже переименовал их, так как мы видим, что они используются во всем файле. Теперь, если мы вернемся к функции декомпрессии из декодированного C # с нашими переименованными переменными, создается ощущение, что мы приближаемся к нашему PE-файлу.

public static byte[] decompress_array(byte[] arrayToDecompress)
{
  using (MemoryStream inStream = newMemoryStream(arrayToDecompress))
  using (GZipStream bigStream = new GZipStream(inStream,         
                                        CompressionMode.Decompress))
  using (MemoryStream bigStreamOut = new MemoryStream())
  {
     WriteClass.write_to_file(bigStream, bigStreamOut);
     return bigStreamOut.ToArray();
  }
}

Наш WriteClass не вызывается в сценарии PowerShell, но он вызывается в коде C # внутри DecompressionClass, который сообщает нам, что после распаковки определенных байтов он записывается в файл, потому что если мы обратимся к этой decompress_array функции, мы увидим он используется как таковой:

$FV18hi = [DecompressionClass]::decompress_array($TEM52cbe);
....
$PEBytes = [DecompressionClass]::decompress_array($PEbytes);

Похоже, мы выяснили, где наши байты PE распаковываются, записываются и удаляются.

Остальная часть сценария до того, как байты PE будут записаны в память, - это использование алгоритма дешифрования 3DES с вектором инициализации FVADRCORAOSKBHPX для шифрования / дешифрования содержимого другого сценария PowerShell с паролем и солью. Затем он будет сохранен в пути реестра Windows, как показано на скриншоте выше. В свою очередь, это сделает анализ скрипта невозможным без правильного сочетания пароля и соли. Эта команда (IEX) в последней строке выполнит сброшенный PE-файл на машину жертвы. Вы можете найти скрипт PowerSploit с открытым исходным кодом здесь.

А пока мы все так ждали, давайте возьмем строку base64, которую я помечен как $pe_encoded_bytes, и бросим ее в Cyber ​​Chef для декодирования и распаковки.

Если мы щелкнем значок сохранения файла, мы сможем загрузить этот двоичный файл. Теперь мы можем проверить IOC на нем и посмотреть, появится ли что-нибудь в VirusTotal.

➜ file payload.bin
payload.dll: PE32 executable (DLL) (GUI) Intel 80386, for MS Windows
➜ openssl sha1 payload.dll
SHA1(payload.dll)= d117643019d665a29ce8a7b812268fb8d3e5aadb

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

Похоже, мы сорвали джекпот, и я уверен, что DLL покажет всю внутреннюю работу ComRAT.

Взяв небольшой пик под капотом этой DLL, мы можем увидеть, что многие импортированные вызовы API связаны с криптографией и внедрением процессов, что может означать, что у этого вредоносного ПО есть другие этапы, но, как вы можете видеть справа от На картинке выше есть функция, которую я уже реконструировал, которая отвечает за расшифровку и разрешение сотен API-интерфейсов из Kernel32.

МОК

  • Основной скрипт PowerShell
134919151466c9292bdcb7c24c32c841a5183d880072b0ad5e8b3a3a830afef
  • Скрипт PowerShell для PE-капельницы
187bf95439da038c1bc291619507ff5e426d250709fa5e3eda7fda99e1c9854c
  • Отброшен бэкдор DLL
b93484683014aca8e909c9b5648d8f0ac21a45d0c193f6ca40f0b01d2464c1c4

Заключение

Этот сценарий PowerShell, который мы прошли, устанавливает вторичный сценарий PowerShell, который мы проанализировали и решили, что он декодирует и загружает либо 32-разрядную, либо 64-разрядную DLL, которая, скорее всего, будет использоваться в качестве коммуникационного модуля. CISA заявило, что ФБР было уверено, что это вредоносное ПО является спонсируемой российским государством группой APT (Advanced Persistent Threat), которая использует этот вредоносный вирус для эксплуатации сетей жертвы. С учетом сказанного, вот все сценарии PowerShell, которые я деобфусцировал для этой исследовательской статьи. Капельница Часть I и Капельница Часть II.

Спасибо, что подписались! Надеюсь, вам понравилось так же, как и мне. Если у вас есть какие-либо вопросы по этой статье или где найти задание, напишите мне в Instagram: @hackersclub или Twitter: @ringoware

Хорошей охоты :)

использованная литература