August 2

Исследуем образец Nanocore RAT

Сегодня с вами разберем модуль удаленного управления семейства Nanocore RAT, MD5 8b6071a9344b21469ca5a4b62a0a855b. Для доставки данного модуля используется фишинг, в качестве вложения архив c исполняемым файлом внутри.

Основная нагрузка загружается в 3 этапа. На первом этапе неизвестный загрузчик расшифровывает нагрузку, обходит AMSI и запускает в памяти. Нагрузка второго этапа обфусцирована виртуализатором KoiVM, основная задача которого расшифровать основную нагрузку и запустить в созданном процессе методом Process Hollowing.

Больше информации об информационной безопасности, реверсу малвари и расследованию инцидентов в канале https://t.me/threathunt_pedia.

Исспользуемые утилиты:

  1. x64Debug - бинарный отладчик с открытым исходным кодом для Windows
  2. Die - утилита для определения типов файлов..
  3. PeStudio - утилита поиска артефактов в исполняемом файле.
  4. ResourceHacker - редактор ресурсов исполняемого файла Windows.
  5. dnSpy - отладчик и редактор сборок .NET.

Исследование нагрузки первого этапа - Загрузчик

Загрузим исследуемый файл в DIE.

Исследуемый образец собран для 64-ых разрядных систем, разработан на языке программирования C#. Декомпилируем файл с помощью dnSpy и разберем функционал.

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

В обозревателе сборок dnSpy загруженный файл имеет имя DefaultChareFixedBufferILOnly. Нажмем правой кнопкой мыши->Перейти к точке входа. Точка входа указывает на статичную функцию WriteUInt32LittleEndianOperational.

В данной функции создается класс GetIndexOfFirstNonAsciiChargetSyntax и программа завершается. Перейдем в реализацию класса и увидим, что основные действия происходят в деструкторе (Dispose).

Все данные используемые загрузчиком зашифрованы, функция поиска реализована в lpstrSchemai8 класса GetIndexOfFirstNonAsciiChargetSyntax. На вход данной функции передается идентификатор извлекаемых данных. Поиск осуществляется в исполняемом файле.

Список идентификаторов извлекаемых данных:

  • 1 - ключ AES
  • 2 - вектор инициализации IV
  • 3 - основная нагрузка nanocoreRAT
  • 4 - строка подключаемых функций и динамический библиотек, разделенных символом $
  • 5 - Opcode xor rcx, rcx; для атаки на AmsiOpenSession
  • 6 - opcode для атаки на AmsiScanBuffer
Фнукция извлечение данных по идентификатору

Извлеченные данные зашифрованы алгоритмом AES в режиме CBC.

Разработаем скрипт для расшифрования строк и нагрузки следующего этапа. На вход функции ExtractPayload передается исследуемый исполняемый файл и идентификатор данных.

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

def ExtractPayload(filename,number):
    f = open(filename,'rb')
    data = f.read()
    f.close()
    s_const = "f1rMzUVfquat2n49jUfMfBiG6K9UO"
    l = []
    for i in range(0,len(data)-len(s_const)):
        flag = True
        for j in range(0,len(s_const)):
            if data[i+j] != ord(s_const[j]):
                flag = False
                break
        if flag:
            l.append(i)
    
    num = l[number-1] + len(s_const)
    num2 = l[number] - num
    result = data[num:num+num2]
    return result

def DecryptPayload(number):
    key = ExtractPayload('nanocore.exe',1)
    iv = ExtractPayload('nanocore.exe',2)
    payload = ExtractPayload('nanocore.exe',number)
    cipher = AES.new(key, AES.MODE_CBC,iv=iv)
    plaintext = cipher.decrypt(payload)
    plaintext = unpad(plaintext, AES.block_size)
    return plaintext

print('Список используемых функций ', DecryptPayload(4))
print('Оппкод для атаки на AmsiOpenSession ', DecryptPayload(5))
print('Оппкод для атаки на AmsiScanBuffer ', DecryptPayload(6))
print('KEY - ',ExtractPayload('nanocore.exe',1))
payload = DecryptPayload(3)
w = open('nanocore_payload2.exe','wb')
w.write(payload)
w.close()
Расшифрованные данные

Для атаки на Amsi (Antivalware scan interface) модуль патчит функции AmsiScanBuffer и AmsiOpenSession.

Подробнее об атаках на интерфейс AMSI в статье AMSI bypass — От истоков к Windows 11.

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

Исследование нагрузки второго этапа - Инжектор

Мы с вами получили файл нагрузки, получим о нем информацию, для этого загрузим в утилиту Die.

Как видно из рисунка выше исполняемый файл собран для 64-х разрядных систем, разработан на языке программирования C#. Декомпилируем файл в dnSpy.

Исследуемый файл защищен протектором KoiVM, об этом нам говорит поток KoiVM.Runtime. KoiVM - это виртуальная машина, созданная для работы на ConfuserEx, которая превращает коды операций .NET в новые, известные только машине KoiVM.

Метод девирутализации модулей, защищенных KoiVM, описан в докладе Девиртуализация объекта защищенного KoiVM.

Попробуем восстановить исходный алгоритм программы с помощью инструмента OldRod. Соберем утилиту.

git clone -q --branch=master https://github.com/Washi1337/OldRod.git
git checkout -qf 56fc436807c46f94ffc1c790b9d8426dae7be047
git submodule update --init

Загруженный проект открываем в Visual Studio и собираем релиз. Запускаем файл для анализа.

К сожалению для нас, мы имеем дело с кастомной версией KoiVM, которая модифицировала протектор таким образом, что его не просто девиртуализировать. Первоначальная реализация KoiVM определяет 119 константных переменных, которые используются для виртуализации кода. Эти константы используются для определения регистров, флагов, кодов операций и т. д. Назначенные значения этих констант используются для правильного выполнения виртуализированного кода и также необходимы для процесса девиртуализации. Чтобы воспользоваться инструментом OldRod необходимо сформировать список констант и соответсвующие им опкоды, которые загружаются в формате json. В исследуемом модуле все константы обфусцированы арифметическими вычислениями, поэтому данный подход непригоден для получения результатов за разумное время.

Загрузим файл в PeStudio и проанализируем функции импорта (таблица ImplMap).

Таблица функций импорта

В исследуемом модуле доступ к функциям WinAPI и структурам осуществляется с помощью метода PInvoke, его нельзя запутать.

Мы можем идентифицировать данные функции и определить поведение инжектора KoiVM.

В таблице ImplMap обнаружены функции: CreateProcess, ReadProcess, WriteProcessMemory и т.д, которые используются для загрузки полезной нагрузки в памяти методом Process Hollowing.

Все вышеперчисленные функции определяются в классе _dc.

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

Далее перейти в HEX представление памяти, в переменной this._kb указан адрес расшифрованной полезной нагрузки.

Также получить основную нагрузку можно с помощью отладчика x64dbg. После загрузки файла в отладчик, нажимаем Ctrl+G и вводим имя функции WriteProcessMemory.

Далее начинаем отладку (F9) и попадаем на вызов интересующей нас функции.
На регистр r8 нажмем правой кнопкой мыши->Перейти к дампу. Из окошка дампа нажимаем правой кнопкой мыши и переходим в карту памяти, сохраняем участок памяти, находим заголовок MZ и копируем полученные данные в отдельный файл.

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

Исследование основной нагрузки - Nanocore RAT

Финальная нагрузка представляет собой 32-ух разрядный исполняемый файл, разработанный на C#.

MD5:fed1d5379855c7f3a50cd2e79f9e51c1
SHA1:7f4119ba8c8799b340c7a7aa0b7ec41ad69e8c0f
SHA256:b524e2e967e9727b6bf5d2e51f4fff102800ad77395f3c41235ac690608174f8

Исследуемый файл защищен обфускатором Eazfuscator, попробуем запустить de4dot и преобразовать исполняемый код в читаемый вид.

de4dot nanocore_payload.exe

Загрузим файл в dnSpyx32 и приступим к анализу.

Точка входа исполняемого файла

Имя сборки NanoCore Client.

После запуска Nanocore приступает к извлечению конфигурации. Функционал по извлечению конфигурации реализован в классе Class8.

Зашифрованная конфигурация расположена в ресурсе исполняемого файла и имеет следующую структуру.

Первые четыре байта (0x00000010) это размер зашифрованного ключа. Следующие байты содержат зашифрованный ключ. Далее четыре байта (0x15f58) содержат размер конфигурации.

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

GUID исполняемого файла
Расшифрование ключа

С помощью сформированного криптографического ключа расшифровывается извлеченный из конфигурации ключ по алгоритму Rijndael.

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

Сообществом уже создана реализация алгоритма расшифрования конфигурации NanocoreRAT. Загрузим утилиту и расшифруем настройки модуля.

py nanocore_extract_settings.py -f nanocore_payload.exe

ИЛИ 

py nanocore_extract_settings.py -f nanocore_payload.exe --verbose
Конфигурация модуля

В конфигурации модуля содержатся плагин keyloger, адреса управляющих серверов, адреса DNS серверов для разыменования доменных имен и многое другое.

После расшифрования конфигурации модуль настраивает классы, создает рабочий каталог в AppData\\Roaming с именем MachineGuid, считанным из ключа реестра SOFTWARE\\Microsoft\\Cryptography\\MachineGuid.

Рабочий каталог модуля

В файл run.dat записывается время запуска модуля. В файл task.dat записывается путь исполняемого файла с целью дальнейшего создания задачи в планировщике задач.

Также в данном каталоге создаеюся файлы storage.dat, который хранит загруженные плагины из С2, и settings.bin, зашифрованные DES.

Давайте разберем протокол взаимодействия с управляющим сервером и разработаем правило детектирования.

Код протокола взаимодействия реализован в классе Client.

После получения конфигурации полезная нагрузка NanoCore запрашивает доменное имя, указанное в параметре PrimaryConnectionHost конфигурации. В качестве DNS сервера указаны 8.8.8.8 (PrimaryDnsServer) и 8.8.4.4 (BackupDnsServer). После получения адреса управляющего сервера начинается процесс взаимодействия с управляющим сервером.

Модуль NanoCore поддерживает 3 типа команд:

  1. BaseCommand (0x0)- базовый тип команд, в которую входит отправка информации о зараженной системе, готовность модуля, обновление конфигурации.
  2. PluginCommand (0x1) - обновление плагинов и загрузка новых.
  3. FileCommand (0x2) - выгрузка, поиск файлов, вычисление хэш-суммы MD5 .
Типы команд взаимодействия с C2

У каждого типа команды есть идентификатор, который определяет как обрабатывать отправляемые данные. На рисунке ниже представлены тип команды BaseType с различными идентификаторами. Идентификатор 0x0 - отправляет первичную информацию о системе: значение MachineGuid, имя компьютера, имя пользователя, значение DefaultGroup из конфигурации модуля и версию NanocoreRAT. Идентификатор 0x6 - команда о готовности модуля к работе (heartbeat).

Базовый тип команд

Далее отправляемая информация на C2 формируется в поток байт и шифруется алгоритмом DES в режиме CBC с ключом и вектором инициализации равным 0x722018788C294897. Данный ключ получен на этапе расшифрования конфигурации.

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

Зашифрованный пакет информации о системе

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

После отправки данных о зараженном компьютере, модуль отсылает пакет о готовности к получению команд, который содержит значение 0x600.

Длина пакета готовности модуля равна 8 байт.

Зашифрованный пакет готовности модуля

Расшифруем пакет готовности.

На данном этапе мы с вами разобрали протокол взаимодействия с управляющим сервером. Длина отправляемых данных о системе и готовности к получению команд всегда одинакова и равны 0x40, 0x8 байт соответственно.

Разработаем сетевые правила для детектирования работы модуля в сетевом трафике.

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

alert tcp any any -> any any (msg: "Nanocore RAT length packet 0x8 Heartbeat"; flags: PA; dsize:12; depth:4; content:"|08 00 00 00|"; sid: 1000001;)

Правило для поиска пакета о зараженном устройстве.

alert tcp any any -> any any (msg: "Nanocore RAT length packet 0x40 Info System"; flags: PA; dsize:68; depth:4; content:"|40 00 00 00|"; sid: 1000002;)

При анализе пакета смотрим на tcp флаги PSH (Push) и ACK (Acknowledgment), глубина от начала 4 байта, размеры пакетов указаны в параметре dsize (4 байта + длина пакета).

Также одним из индикаторов компрометации является файл run.dat, содержащий временную метку запуска модуля, находящийся в рабочем каталоге исследуемого модуля C:\\Users\\<user>\\AppData\\Roaming\\<MachineGuid>.

Заключение

Мы с вами разобрали вредоносный файл семейства Nanocore RAT, который имеет обширный функционал. Модуль был разработан в 2015 году, но актуальность свою на сегодняшний день не потерял. Для обхода детектирования основная нагрузка упакована в 3 этапа. Большую трудность при анализе составило исследования инжектора, защищенного кастомным KoiVM. Разработали сетевые правила детектирования для Suricata и индикаторы компрометации, в которые входят: адрес управляющего сервера, порт, мьютекс и созданные в рабочем каталоге файлы.