Как стать автором
Обновить

Расшифровка зашифрованных файлов программы-вымогателя Akira (Linux/ESXi 2024) с использованием набора GPU

Уровень сложностиСредний
Время на прочтение23 мин
Количество просмотров2.8K
Автор оригинала: tinyhack.com

Хакер делает из любви то, что другие не стали бы делать и за деньги.

Недавно я помог компании восстановить их данные после атаки программы-вымогателя Akira без выплаты выкупа. Я делюсь тем, как я это сделал, вместе с полным исходным кодом.

Код доступен здесь: https://github.com/yohanes/akira-bruteforce

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

Была более ранняя версия (до середины 2023 года), которая содержала ошибку, позволившую Avast создать дешифратор. Однако, как только это было опубликовано, злоумышленники обновили свое шифрование. Я ожидаю, что они снова изменят своё шифрование после того, как я опубликую эту информацию.

Расшифровано: Программа-вымогатель Akira

Различные хеши образцов вредоносного ПО Akira можно найти по тут.

Образец, соответствующий случаю моего клиента: bcae978c17bcddc0bf6419ae978e3471197801c36f73cff2fc88cecbe3d88d1a

Он указан в версии: Linux V3. Образец можно найти на virus.exchange (просто вставьте хеш для поиска).

Обратите внимание, что сообщение о выкупе и закрытые/открытые ключи будут отличаться.

Мы делаем не потому, что это легко, а потому, что думали - это будет легко

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

По результатам первоначального анализа я заметил следующее:

  • Программа-вымогатель использует текущее время (в наносекундах) в качестве начального значения.

  • На моей машине с Linux время изменения файла имеет наносекундное разрешение.

  • Они предоставили снимок экрана с частичным журналом (shell.log), показывающим, когда была запущена программа-вымогатель, с миллисекундным разрешением.

Исходя из этого, моя первоначальная мысль была: "Это должно быть просто — используй перебор, посмотрев на временные метки файлов. Насколько это может быть сложно?"

Я объясню более подробно, но выяснилось, что это сложнее, чем ожидалось:

  • Вредоносное ПО полагается не на один момент времени, а использует четыре момента, каждый с наносекундным разрешением. Первые два и последние два связаны между собой, поэтому мы не можем просто перебирать время по одному. Генерация ключа сложна, включает 1500 раундов SHA-256 для каждой временной метки. Каждый файл в итоге получает уникальный ключ.

  • Файловая система VMware VMFS записывает время изменения файлов только с секундной точностью.

  • Не все хосты ESXi имеют миллисекундное разрешение в своих журналах, некоторые регистрируют только с секундной точностью. Я до сих пор не уверен, какой конфигурационный файл вызывает это разное поведение.

  • Вредоносное ПО использует несколько потоков во время выполнения.

  • Время изменения файла отражает когда файл закрыт, а не когда он открыт для записи.

Reverse Engineering

Код написан на C++, который печально известен своей сложностью для чтения, но, к счастью, он не был запутан. Бинарный файл статически связан (немного сложнее для анализа), но все строки находятся в открытом виде. Сообщения об ошибках указывают на использование библиотеки Nettle, что сделало понимание кода намного проще.

Наличие строк ошибок действительно помогает

Код для генерации случайных чисел выглядит следующим образом (реальный код находится в 0x455f40 в бинарном файле)

void generate_random(char *buffer, int size)
{
    uint64_t t = get_current_time_nanosecond();
    char seed[32];
    snprintf(seed, sizeof(seed), "%lld", t);
    struct yarrow256_ctx ctx;
    yarrow256_init(&ctx, 0, NULL);
    yarrow256_seed(&ctx, strlen(seed), seed);
    yarrow256_random(&ctx, size, buffer);
}

Генератор случайных чисел реализован в yarrow256.c. Вот соответствующий код, с удаленными ненужными частями. Как отмечено в комментариях:

Количество итераций при повторном заполнении, P_t в статье о yarrow. Должно быть выбрано так, чтобы повторное заполнение занимало порядка 0,1-1 секунды.

void yarrow256_seed(struct yarrow256_ctx *ctx, size_t length, const uint8_t *seed_file) {
    sha256_update(&ctx->pools[YARROW_FAST], length, seed_file);
    yarrow256_fast_reseed(ctx);
}

void yarrow256_fast_reseed(struct yarrow256_ctx *ctx) {
    uint8_t digest[SHA256_DIGEST_SIZE];

    sha256_digest(&ctx->pools[YARROW_FAST], sizeof(digest), digest);
    yarrow_iterate(digest);
    aes256_set_encrypt_key(&ctx->key, digest);

    memset(ctx->counter, 0, sizeof(ctx->counter));
    aes256_encrypt(&ctx->key, sizeof(ctx->counter), ctx->counter, ctx->counter);
}

/* The number of iterations when reseeding, P_t in the yarrow paper.
 * Should be chosen so that reseeding takes on the order of 0.1-1 seconds. */
#define YARROW_RESEED_ITERATIONS 1500

static void yarrow_iterate(uint8_t *digest) {
    uint8_t v0[SHA256_DIGEST_SIZE];

    memcpy(v0, digest, SHA256_DIGEST_SIZE);

    for (unsigned i = 0; ++i < YARROW_RESEED_ITERATIONS; ) {
        uint8_t count[4];
        struct sha256_ctx hash;

        sha256_init(&hash);

        WRITE_UINT32(count, i);
        sha256_update(&hash, SHA256_DIGEST_SIZE, digest);
        sha256_update(&hash, sizeof(v0), v0);
        sha256_update(&hash, sizeof(count), count);

        sha256_digest(&hash, SHA256_DIGEST_SIZE, digest);
    }
}

Начальное значение и шифрование

Программа-вымогатель вызывает генератор случайных чисел четыре раза:

generate_random(chacha8_key 32);
generate_random(chacha8_nonce, 16);
generate_random(kcipher2_key, 16);
generate_random(kcipher2_key, 16);

Каждый вызов generate_random использует текущую наносекундную временную метку в качестве начального значения. Следовательно, есть четыре уникальные временные метки, которые необходимо идентифицировать. Программа-вымогатель генерирует разные ключи для каждого файла.

Эти ключи затем сохраняются в конце файла как трейлер, зашифрованные с помощью RSA-4096 и дополненные с использованием заполнения PKCS#11.

Файлы разделены на N блоков, и процент каждого блока зашифрован. Этот процент определяется параметром -n программы-вымогателя. Для каждого блока:

  • Первые 0xFFFF байтов зашифрованы с использованием KCipher2.

  • Оставшиеся байты зашифрованы с использованием ChaCha8.

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

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

Другие подробности

На данный момент я не анализировал глубже. Но я уверен, что смогу позже восстановить алгоритмы, в частности:

  • Как разделить файл на блоки

  • Как выполняется шифрование между блоками, продолжается ли поток

Эти детали будут важны позже. Однако сейчас, если мы не сможем успешно перебрать временные метки, остальные шаги не будут иметь значения.

Возможность перебора

Подход следующий:

  1. Генерируем две временные метки (t3 и t4).

  2. Преобразуем эти метки в начальные значения и генерируем случайные байты.

  3. Используем эти байты как ключ и вектор инициализации KCipher2.

  4. Шифруем известный открытый текст и сравниваем результат с известным шифротекстом из зашифрованного файла.

Составим план:

  • Проверка осуществимости: Определить, достаточно ли быстр перебор для практического применения.

  • Идентификация открытого текста: Для перебора требуется известный открытый текст.

  • Оценка времени инициализации начального значения: Нам нужно знать, когда было инициализировано начальное значение шифрования, хотя бы с точностью до секунды. Эти знания могут сократить объем перебора примерно до 1 миллиарда значений.

Самый простой (но неэффективный) способ — перебрать все возможные пары временных меток, где T4 > T3. Количество возможных пар рассчитывается как: N×(N−1)/2

При N = 1 миллиард это дает 500 квадриллионов возможных пар.

Нам нужно оптимизировать это. Сначала необходимо преобразовать все наносекунды в случайные значения:

  • На моем мини-ПК я оценил скорость обработки в 100 000 преобразований временных меток в случайные байты в секунду (используя все ядра).

  • Это означает, что потребуется около 10 000 секунд (менее 3 часов) для преобразования всех временных меток в начальные значения.

  • После преобразования эти значения можно сохранить для повторного использования.

  • Позже я оптимизировал процесс с использованием GPU, сократив время преобразования с 3 часов до менее чем 6 минут.

Если бы у нас была полностью детерминированная машина без прерываний, мы могли бы запустить вредоносное ПО, измерить его и точно знать время между T3 и T4. Но, к сожалению, у нас этого нет:

  • Вредоносное ПО использует несколько потоков,

  • Оно работает на машине, которая не простаивает, расстояние между T3 и T4 варьируется в зависимости от планировщика и загруженности системы в это время.

  • Код также вызывает множество библиотек C++, которые выделяют и освобождают объекты, делая время выполнения более непредсказуемым.

Для ясности:

  • нам нужно перечислить t3 (1 миллиард значений для каждой секунды)

  • мы начинаем не с t3 + 1, а с t3 + начальное смещение, поскольку знаем, что инициализация значения требует времени (как минимум миллиона наносекунд на моей машине), это "начальное смещение"

  • мы предполагаем, что до выполнения следующего кода пройдет всего несколько миллионов наносекунд (помните: могут быть прерывания из-за планировщика ЦП, и выполняется несколько миллионов инструкций). Это значение "диапазона смещения"

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

Мой друг Deny отправился в центр обработки данных и провел тестирование на реальном оборудовании, которое было заражено. Результат: временной диапазон варьируется, иногда довольно сильно. Нормальный диапазон смещения составляет около 2-4 миллионов наносекунд (так что диапазон смещения составляет 2 миллиона), но значение варьируется от 1,5 до 5 миллионов (общий диапазон смещения составляет 4,5 миллиона).

Нам все еще нужно перебрать 4,5 квадриллиона пар, но это представляется выполнимым. Если у нас есть система, способная выполнять 50 миллионов шифрований в секунду, процесс займет несколько сотен дней. Однако с 16 такими системами мы могли бы завершить это за несколько месяцев на процессоре. Арендуя дополнительные машины, мы могли бы еще больше ускорить процесс. Позже я оптимизировал это с использованием GPU, достигнув значительного ускорения.

Я не был уверен, насколько быстро мы можем выполнять Kcipher2, но быстрое сравнение с chacha и некоторые быстрые тесты показывают, что используя только CPU, я должен быть в состоянии выполнять по крайней мере миллионы операций Kcipher в секунду на моей машине.

Как объяснялось ранее, если t3 и t4 верны, мы сможем расшифровать первые 8 байт файла, и они расшифруются в известный открытый текст.

Далее давайте проверим возможность получения открытого текста из различных файлов VMware.

Типы файлов VMware

Для каждого файла нам нужен образец открытого текста: первые 8 байт файла для KCipher2 (смещение 0) и еще 8 байт, начиная со смещения 65 535 (только для больших файлов). Поскольку каждый блок KCipher2 составляет 8 байт, мы должны использовать 8-байтовый открытый текст. Можно использовать меньше байт (с помощью битовой маски), но это может увеличить риск ложных срабатываний.

Flat-VMDK

Это файл raw-диска. Если повезет, это может быть единственный файл, который вам нужно восстановить. Однако, если были сделаны снимки (как в случае с этим клиентом), новые данные будут записаны в файлы sesparse.

Чтобы получить первые 8 байт flat VMDK, вам нужно установить ту же ОС, которая использовалась на исходной ВМ. Существует несколько вариаций загрузчиков, используемых разными версиями ОС.

Чтобы определить, какая ОС использовалась, проверьте соответствующий файл VMX. Он должен содержать частично читаемый открытый текст, позволяющий проверить конфигурацию на наличие "guestOS". Вы можете найти что-то вроде: guestOS="ubuntu". Однако, в идеале, у вас уже есть документация о том, какая ОС использовалась для каждой ВМ, так что вам не придется полагаться на этот метод.

Для байтов в позиции 65 535 (открытый текст для ChaCha8) почти всегда гарантированно значение ноль, поскольку раздел обычно начинается с более позднего сектора.

Sesparse

Если вы создаете снимки для вашей ВМ, для каждого снимка будет файл SESPARSE. Мы можем увидеть формат файла из исходного кода QEMU.

https://github.com/qemu/qemu/blob/master/block/vmdk.c

Заголовок файла — 0x00000000cafebabe, а в позиции 65 535 должно быть 0x0 (по крайней мере, это то, что я наблюдал в своем анализе).

Другие файлы

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

Вот некоторые общие сигнатуры файлов для идентификации открытых текстов:

  • Файлы NVRAM начинаются с: 4d 52 56 4e 01 00 00 00

  • Файлы VMDK (дескриптор диска) начинаются со строки: # Disk Descriptor

  • Файлы .VMX начинаются с: .encoding

  • Файлы журнала VMware имеют строки, начинающиеся с формата: YYYY-MM-DD Поскольку эти файлы частично читаемы, мы часто можем догадаться о начальной временной метке по началу файла (например, части YYYY-MM- в журнале).

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

Временная метка шифрования

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

Журнал ESXi

Команда, использованная для запуска вредоносного ПО, записана в файле shell.log (включая настройку для n, которая определяет, какая часть файла должна быть зашифрована).

Некоторые хосты ESXi обеспечивают разрешение в миллисекундах в своих журналах, в то время как другие предлагают только точность на уровне секунд. Этот журнал дает нам начальную временную метку для момента запуска вредоносного ПО.

Например, если журнал показывает, что вредоносное ПО запустилось в 10:00:01.500, мы можем безопасно игнорировать первые 500 миллионов наносекунд при переборе, что помогает сузить диапазон поиска.

Временная метка файловой системы и время модификации

К сожалению, файловые системы ESXi не поддерживают наносекундную точность.

Еще одна проблема заключается в том, что время модификации файла записывается только при закрытии файла. Это означает, что записанная временная метка может не точно отражать момент начала процесса шифрования, а скорее момент его завершения.

В Linux (используя большинство файловых систем), точность временной метки составляет наносекунды

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

в VMFS точность составляет секунды

Многопоточное шифрование

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

Если вредоносное ПО нацелено на один каталог, и количество файлов меньше количества ядер процессора, процесс прост — каждый файл будет иметь временную метку, очень близкую к другим. На машине ESXi обычно используются процессоры с большим количеством ядер (в данном случае сервер имеет 64 ядра).

При проверке временных меток с помощью:

find /vmfs/volumes -exec stat {} \;

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

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

Вредоносное ПО использует boost::filesystem для обхода каталогов и файлов. Итератор в boost::filesystem следует порядку, возвращаемому readdir, который совпадает с порядком, наблюдаемым при использовании команд типа ls -f или find ..

Рассмотрим пример, когда у нас есть 4 ядра процессора и 8 файлов. Если файлы крошечные (менее 1 КБ, например, дескрипторные файлы VMDK), их обработка происходит практически мгновенно (в течение миллисекунд). Вот как может выглядеть обработка:

  • Потоки A, B и C каждый находит и обрабатывает маленькие файлы (file_a, file_b, file_c), в то время как Поток D находит большой файл (file_d). Все четыре файла обрабатываются немедленно.

  • Как только Потоки A, B и C завершают работу, они начинают обрабатывать следующий набор файлов (file_e, file_f, file_g). Однако эти файлы больше и требуют больше времени для обработки.

  • Пока остальные три потока все еще работают, Поток D заканчивает обработку большого file_d и начинает работать над последним файлом (file_h). В результате, начальная временная метка file_h будет совпадать со временем завершения file_d.

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

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

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

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

Сетевая файловая система

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

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

Создание брутфорсера

План казался надежным, поэтому следующим шагом было реализовать код. Мне нужно было подтвердить, работает ли процесс шифрования точно так же, как вредоносное ПО.

Для проверки я модифицировал код вредоносного ПО, чтобы функция gettime возвращала постоянное значение 0, обеспечивая предсказуемые и согласованные результаты во время тестирования.

KCipher2

Я сосредоточился на KCipher2, поскольку не все файлы используют ключ ChaCha8, особенно маленькие файлы. Хотя KCipher2 является стандартным алгоритмом шифрования, он не широко известен, и я не смог найти оптимизированную реализацию для него.

В ходе экспериментов я заметил, что мои результаты не соответствовали стандартным реализациям KCipher2, доступным в интернете. Оказалось, что вредоносное ПО включало небольшую модификацию в инициализирующем векторе и процессе шифрования, особенно связанную с обменом эндианности.

CUDA

Я не эксперт в программировании CUDA. Около 10 лет назад я кратко экспериментировал с ним, но не смог найти практический случай использования для компании, в которой я работал в то время.

Чтобы ускорить разработку, я попросил ChatGPT (o1) портировать код на CUDA. Код успешно скомпилировался, но давал неправильные результаты. Оказалось, что ChatGPT немного изменил числа в таблицах констант. После ручной коррекции этих значений код начал работать.

Хотя реализация запускалась, я подозревал, что она не оптимальна, но не смог получить дальнейшие предложения по оптимизации от ChatGPT (o1). На этом этапе у меня было два варианта: потратить больше времени на оптимизацию кода или продолжить с предсказанным диапазоном смещений и уточнить код по ходу дела. Я выбрал начать тестирование немедленно и оптимизировать по мере необходимости. К сожалению, этот подход оказался пустой тратой денег, так как он не дал никаких успешных результатов.

В начале проекта у меня было только две видеокарты RTX 3060. Одна была выделена для моего компьютера на Windows, поэтому я мог использовать только одну видеокарту на моем Mini PC (подключенном внешне через Oculink). Чтобы повысить производительность, я решил приобрести RTX 3090. Цена в Таиланде все еще была разумной по сравнению с 4090 или более высокими моделями.

Я проверил реализацию, считывая ключ и IV из памяти, шифруя нулевые блоки и записывая результаты обратно в память. Производительность была разочаровывающей, достигая только около 60 миллионов шифрований в секунду. При такой скорости весь процесс занял бы около 10 лет, что явно слишком медленно для практического восстановления.

Ручная оптимизация

Я выполнил некоторые ручные оптимизации, удалив ненужный код для повышения производительности:

  • Для перебора нужен только первый блок, поэтому не было необходимости обрабатывать дополнительные блоки.

  • Код был упрощен для шифрования только блоков нулей, что уменьшило ненужную обработку.

  • Поскольку требовались только первые 8 байтов результата, остальная часть вывода игнорировалась для минимизации вычислений.

Разделяемая память

После исследования оптимизаций CUDA для AES, я обнаружил, что использование разделяемой памяти значительно улучшает производительность, вопреки тому, что предлагал ChatGPT. Удивительно, но дополнительные шаги, связанные с копированием данных из константной памяти в разделяемую, оказались незначительными с точки зрения накладных расходов, но привели к тому, что код работал в несколько раз быстрее.

Избегание записи в память

Изначально я выполнял шифрование на GPU, а сопоставление на хосте (CPU). Однако этот подход был медленным, даже при параллельном выполнении:

  • генерация шифрования на GPU

  • копирование результата на CPU

  • Выполнение сопоставления в новом потоке и отправка следующей партии работы на GPU.

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

Сопоставление нескольких файлов

Для каждой комбинации t3 и t4, совпадение может произойти для любого файла, который имеет одинаковую метку времени второго уровня (но с различными наносекундами).

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

Цикл

Я рассмотрел и реализовал два способа выполнения цикла. Для каждого значения t3 мы могли бы запустить ядро GPU для проверки всех диапазонов смещений. Однако этот метод неэффективен, поскольку потребовал бы запуска ядра миллиард раз, что приводит к значительным накладным расходам.

В качестве альтернативы, мы можем запустить ядро GPU для каждого смещения. Затем каждое ядро будет выполнять необходимые проверки. Этот подход намного быстрее, поскольку сокращает количество отправок до "диапазона смещений", который составляет примерно от 2 до 4,5 миллионов заданий.

Пакетная проверка

Изначально мой подход заключался в отправке задачи на GPU, ожидании результата с использованием cudaDeviceSynchronize() и затем отправке следующей партии работы. Однако этот метод оказался медленным.

  • Отправляем работу на GPU, и если найдено совпадение, просто отмечаем его с помощью флага.

  • Вызываем cudaDeviceSynchronize() для проверки результатов только каждые 100 шагов. Если найдено совпадение, флаг сбрасывается в ноль перед продолжением.

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

Итоговая скорость

Я полагаю, что эксперты по GPU всё еще могут найти способы дальнейшей оптимизации моего кода. В настоящее время я достигаю около 1,5 миллиардов шифрований в секунду для KCipher2 на моем RTX 3090.

  • Для тестирования 1 миллиарда значений с одним смещением требуется около 0,7 секунды, включая время на проверку совпадений (с максимумом 32 совпадения на пакет).

  • Тестирование 2 миллионов смещений потребует примерно 16 дней на одном GPU, или всего 1 день при использовании 16 GPU.

Я также провел тесты с использованием Runpod, и RTX 4090 оказался идеальным вариантом. Хотя он примерно на 60% дороже, чем 3090, он также в 2,3 раза быстрее.

  • С 4090 тот же процесс займет около 7 дней на одном GPU.

  • При использовании 16 GPU процесс может быть завершен всего за 10 часов.

Выполнение полного перебора

С точки зрения стоимости, RTX 4090 является отличным выбором для этой задачи по нескольким причинам:

  • Большой объем памяти не требуется.

  • Операции с плавающей запятой не нужны.

  • RTX 4090 предлагает большое количество ядер CUDA, повышая скорость обработки.

  • Цена аренды RTX 4090 относительно низкая по сравнению с другими высокопроизводительными GPU.

Если 4090 недоступен, 3090 также является хорошей альтернативой, учитывая его соотношение цены и производительности.

Изначально мой клиент рассматривал использование машин Google Cloud Platform (GCP) и поиск скидки на аренду на месяц. Однако этот вариант оказался чрезвычайно дорогим (стоимость десятки тысяч долларов США).

После некоторого исследования я нашел более экономичные альтернативы: Runpod и Vast.ai.

Runpod

Для полного перебора 1 секунды (1 миллиард наносекунд), с диапазоном смещений 2 миллиона, потребуется 7 дней. Стоимость RTX 4090 (на момент написания) составляет 0,69 USD/час. Это будет стоить около 116 USD для полного перебора одной секунды. Аренда 16 GPU позволит завершить работу примерно за 10 часов, с той же стоимостью, но быстрее.

Полный перебор с диапазоном 4,5 миллиона (который нам нужен) стоит 261 USD. В зависимости от количества зашифрованных файлов, возможно, потребуется проверить 10 или более секунд. Если у вас много файлов для восстановления, еженедельная или ежемесячная аренда будет дешевле.

Примечание: Эти затраты предполагают, что всё выполняется идеально. Любые ошибки или необходимость повторения процессов могут значительно увеличить затраты. В общей сложности, включая все мои эксперименты и тесты, я потратил около $1,200.

Vast.ai

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

Используя vast AI, стоимость полного перебора может быть снижена вдвое, но это зависит от вашей удачи в получении машины. Первые несколько машин, которые я тестировал, не работали (тайм-аут сети примерно через 10 минут ожидания). У меня также возникли проблемы с загрузкой образов Docker из docker.io (мне пришлось выбрать другой шаблон из другого репозитория Docker).

Остальная работа

Теперь, когда я нашел значения t3 и t4, я могу попытаться найти значения для t1 и t2. Значение t1 должно быть меньше t3, а смещение времени меньше 10 миллионов наносекунд. Это можно быстро найти за несколько минут, используя один GPU.

Алгоритм разделения блоков

Вот алгоритм, используемый для разделения файла на части:

  • enc_block_size: для каждой части/блока, это количество байт для шифрования. Первые 0xFFFFF будут зашифрованы с использованием KCipher2, а остальные с использованием ChaCha8

  • part_size: размер блока

  • encrypted_parts: сколько блоков шифровать

void compute_blocks(uint64_t filesize,
    uint8_t percent,
    uint64_t *enc_block_size,
    uint64_t *part_size,
    uint64_t *encrypted_parts)
{
    int parts = 3;
    if (percent > 49u)
        parts = 5;
    uint64_t enc_size = filesize * (uint64_t)percent / 100;
    *enc_block_size = enc_size / parts;
    *encrypted_parts = parts - 1;
    *part_size = (filesize - *enc_block_size * (*encrypted_parts)) / parts;
}

Детали шифрования

Вредоносное ПО использует 8-раундовый вариант ChaСha, называемый СhaСha8, а не ChaCha20, как сообщалось на многих сайтах.

  • Для KCipher2 мы шифруем первые 65535 байт (да, не 65536). Это означает, что один байт останется от первого блока, и его нужно использовать для следующего блока

  • Для ChaCha20 мы просто отбрасываем остаток потока шифрования при начале нового блока

Шаги восстановления

Чтобы восстановить ваши файлы без оплаты, это не так просто, как запустить общий дешифратор. Вам потребуется:

  • получить временные метки ваших файлов

  • получить шифротекст и открытый текст для ваших файлов

  • арендовать GPU

Примечание о коде

Честно говоря, я изначально написал этот код для одноразового использования, специально для этого конкретного клиента. Общий код наполнен экспериментальной логикой, быстрыми хаками и не имеет надлежащего тестирования.

У меня нет мотивации дальше его очищать, кроме удаления некоторых специфических для клиента тестовых случаев и комментариев. Он функционален для предполагаемой цели.

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

У меня нет специальной системы для управления несколькими GPU. Вместо этого я полагаюсь на базовые сценарии оболочки и пользовательский скрипт, который отправляет сообщение в Telegram, когда найдено совпадение. Код "достаточно хорош для меня" и просто "работает для меня".

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

Сборка кода

См. README.md в репозитории, там также есть пример конфигурационного файла для проверки работоспособности. Также предоставлены примеры зашифрованных файлов и конфигурационных файлов.

Получение временных меток

Надеюсь, вы не трогали файлы, поскольку все шансы на восстановление будут потеряны, если временные метки неизвестны. Используйте stat filename для получения временной метки модификации. Используйте find /vmfs/volumes -exec stat {} \; > /tmp/stats.txt для получения временных меток всех файлов.

Файл shell.log может помочь определить минимальную временную метку для использования.

Получение шифротекстов

Получите шифротекст, как объяснено выше:

  • Для flat-vmdk вам нужно извлечь это из той же ОС, которую вы используете (включая точный метод установки, например: использование BIOS/UEFI)

  • Для файла sesparse используйте заголовок 0x00000000cafebabe

  • Для других файлов смотрите то, что я написал выше

Измерение скорости сервера

Вы всегда можете просто использовать диапазон смещения 1,5-5 миллионов, но это может быть неправильным диапазоном, если ваше оборудование слишком быстрое или слишком медленное. Вы можете измерить это, проверив папки timing-patch-1 и timing-patch-2 в моем GitHub репозитории.

Первая измеряет только временные диапазоны, вызывая функцию напрямую. Вторая используется для шифрования каталога, но она модифицирована так, чтобы записывать точное время, когда временная метка используется в качестве начального значения, в /tmp/log.bin.

Разделение работы

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

Аренда GPU

Если вы хотите очень быструю и простую настройку, используйте runpod или другой сервис. Если вы хотите сэкономить, используйте vast.ai или запустите на собственном оборудовании (~ 1K USD за один RTX 3090, который вы можете перепродать позже).

Запуск брутфорса KCipher2

Первый брутфорс нужен для поиска t3 и t4 для Kcipher.

./akira-bruteforce run2 config.json

Добавьте индекс GPU, если у вас несколько GPU

./akira-bruteforce run2 config.json 1

Я рекомендую запускать его внутри tmux, чтобы вы были в безопасности в случае отключения сети.

Если нам повезет, для каждого найденного t3/t4 будет сгенерирован output.txt.

Запуск брутфорса ChaCha8

Это не обязательно для маленьких файлов, но необходимо для больших файлов. Для каждого найденного смещения создайте конфигурацию с t3, найденным на предыдущем шаге. На моей целевой машине расстояние между t1 и t3 менее 10 миллионов, а между t1 и t2 около 1,5–5 миллионов. Брутфорс должен занять около 10 минут.

Расшифровка файлов

Обратите внимание, что в дешифраторе жестко закодирован процент 15%, поэтому, пожалуйста, измените это перед запуском дешифратора в случае, если злоумышленник использует другое значение.

После получения t1, t2, t3 и t4 запустите дешифратор:

./decrypt filename.vmdk <t1> <t2> <t3> <t4>

Процесс расшифровки не оптимизирован, поэтому расшифровка займет некоторое время.

Run the cracking process

Для запуска брутфорса:

./anti-akira run config.json <gpuindex>

Как объяснено выше: это может занять дни, поэтому, пожалуйста, убедитесь, что:

  • все конфигурационные файлы хорошие

  • Вы используете правильный индекс GPU

  • убедитесь, что все работает

  • проверьте с помощью nvidia-smi (с runpod мы также можем просматривать статус GPU через веб)

  • создайте систему уведомлений, чтобы узнать, если output.txt создан/обновлен

Заключение

Вероятно, в 99,9% случаев, когда вы получаете программу-вымогатель, ее невозможно восстановить без ключа. Но если вам повезет, иногда можно найти решение. Мне потребовалось гораздо больше времени, чем я ожидал, чтобы решить эту проблему. Я думал, что это займет неделю, но мне потребовалось почти три недели, пока мы не восстановили целый набор файлов VM.

Я также хотел бы добавить, что нашел тему на Reddit о вымогателе Akira. Не был уверен, что штамм программы, который у меня есть, такой же, как у них, и поэтому я просто продолжил свое собственное исследование и сделал его с открытым исходным кодом. Я надеюсь, что мой опыт и код будут полезны для кого-то еще.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Какие превентивные меры против программ-вымогателей вы считаете наиболее эффективными?
87.5% Регулярное резервное копирование данных на внешние носители21
4.17% Использование современного антивирусного ПО и фаервола1
8.33% Внимательность при открытии вложений и переходе по ссылкам2
0% Своевременное обновление операционной системы и программ0
0% Шифрование важных данных и использование надежных паролей0
Проголосовали 24 пользователя. Воздержались 6 пользователей.
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
+16
Комментарии2

Публикации

Истории

Работа

Ближайшие события

25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань
20 – 22 июня
Летняя айти-тусовка Summer Merge
Ульяновская область