Реверс и анализ Keyzetsu Clipper

Недавно я увидел новость о появлении на GitHub фальшивых репозиториев, которые обманом заставляют жертв скачивать вредонос, угрожающий безопасности их криптоактивов. Вредонос называется Keyzetsu Clipper, и в тот момент мне очень захотелось узнать, как работают настоящие вирусы. До этого у меня только был опыт учатсия в разных CTF. И тут я понял, что пришло время испытать свои силы на реальном примере.

В данной статье я провел полный анализ и реверс Keyzetsu Clipper, начиная от распаковки и расшифровки до анализа функций персистенца, коммуникации и замены кошельков.

Инструменты

В ходе исследования я использовал следующие инструменты:

  • IDA Free — дизассемблер и декопмилятор для статического анализа исполняемых файлов,

  • Python — язык программирования,

  • de4dot — утилита для деобфускации исполняемых файлов .NET,

  • dnSpy — декомпилятор для .NET,

  • VirusTotal — сайт для проверки ресурсов на наличие вредоносных программ.

Поиск экземпляра

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

Keyzetsu Clipper на MalwareBazaar

Скачиваем исследуемый вредонос и меняем его расширение на .bin. Этот прием позволяет нам случайно на запустить его, а IDA всё также может распознать наш файл как исполняемый.

Первым делом загрузим наш файл на VirusTotal и посмотрим, что он нам скажет.

Результат анализа VirusTotal

Результат анализа VirusTotal

Как ни странно, 59 из 72 вендоров считают этот файл опасным. Перейдем во вкладку Community и посмотрим, что нам об этом скажут другие исследователи.

Отчет во вкладке Community

Отчет во вкладке Community

В графе Threat Name видим название нашего вредоноса — Keyzetsu Clipper, значит мы скачали то, что нам нужно. Также заметим, что вирус стучался к домену API Telegram. Предпологаем, что скорее всего в качестве C2 используется бот в Telegram.

Распаковка

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

Открываем наш файл в IDA Free. IDA сама перемещает нас на функцию start. Нажимаем кнопку F5 и смотрим на декомпилированный код.

Декопмиляция функции start

Декопмиляция функции start

Сразу видим функцию sub_4013FF, которой передаются аргументы функции main. Возвращенное ей значение передается функции exit. Принимаем эту функцию за main и продолжаем анализ.

Декомпиляция функции main

Декомпиляция функции main

Я осмотрел все три функции и скажу сразу, что нас интересует вторая, которую я назвал func_unpack . В ней расположен алгоритм распаковки нашего вируса. Переходим сразу туда.

Массивы в функции func_unpack

Массивы в функции func_unpack

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

Один из указателей в функции main

Один из указателей в функции main

Дальше видим сам алгоритм распаковки.

Алгоритм распаковки в функции main

Алгоритм распаковки в функции main

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

Функция func_decrypt_string

Функция func_decrypt_string

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

Я написал скрипт для расшифровки строк на Python.

def decrypt(key, data):
    result = []
    for i in range(len(data)):
        result.append(chr(ord(key[i % len(key)]) ^ data[i]))
            # Обычный XOR и преобразование к ASCII символам
    return "".join(result)

key = "t:4u]i(g5+9(i=i&,.k)8@cb0udc[1]m"

strings = [
    [32, 95, 89, 5], 
    [32, 91, 71, 30, 21, 6, 91, 19, 120, 74, 87, 73, 14,
        88, 27, 117, 73, 92, 29, 64, 91, 37, 77, 7, 72, 16],
    [48, 85, 70, 30, 125, 58, 77, 6, 71, 72, 81, 77, 27, 29, 44, 124, 2, 75, 19, 76]
]

# Извлеченные строки в десятичном формате, так как их лечге так копировать из IDA
# Первая строка повторялась несколько раз

for string in strings:
    print(decrypt(key, string))

После выполнения скрипта мы получили следующие строки.

Расшифрованные строки

Расшифрованные строки

Можем предположить, что вредонос распаковывает себя в папку Temp и создает исполняемые файлы с именами TaskHostManagerService.exe и Dork Searcher EZ.exe.

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

Алгоритм распаковки

Алгоритм распаковки

Здесь мы сначала мы смотрим сравниваем строки Temp и CurrentDirectory (я все перепроверил, именно сравниваем строки), потом мы составляем полный путь до нового файла, который будет расположен в папке Temp, и сохраняем его в указатель ptr_full_filename. Затем мы записываем расшифрованное содержимое исполняемого файла. В конце мы запускаем распакованный файл. За 2 итерации мы распаковываем 2 файла, TaskHostManagerService.exe и Dork Searcher EZ.exe.

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

def decrypt(key, data):
    result = []
    for i in range(len(data)):
        result.append(
            ord(key[i % len(key)]) ^ data[i])
    return result

def unpack_file(file_offset, file_size, unpacked_file_name):
    with open("sample.bin", "rb") as file: # sample.bin - исследуемый файл
        file.seek(file_offset)
        file_data = file.read()
        decrypted_data = decrypt(key, file_data)
        with open(unpacked_file_name, "wb") as unpacked_file:
            for i in range(file_size):
                unpacked_file.write(
                    decrypted_data[i].to_bytes(1, "little"))
            unpacked_file.close()
        file.close()

key = "t:4u]i(g5+9(i=i&,.k)8@cb0udc[1]m"

first_file_offset = 0xc41
seconde_file_offset = 0x1dce5c
# Смещения запакованных файлов относительно начала исследуемого файла

first_file_size = 1950208
second_file_size = 14200626
# Размеры запакованных файлов из массива

unpack_file(first_file_offset, first_file_size, "TaskHostManagerService.bin")
unpack_file(seconde_file_offset, second_file_size, "Dork Searcher EZ.bin")

По той же логике что и в начале меняем расширения файлов на .bin. После распаковки получаем два новых исполняемых файла. Dork Searcher EZ.bin является SFX архивом, который содержит соответствующую утилиту, нас он не интересует. А вот TaskHostManagerService.bin это и есть наш Keyzetsu Clipper.

Анализ Keyzetsu Clipper

Итак, преступаем к анализу самого вредоноса. Начнем с VirusTotal. Загружаем наш файл и во вкладке Community видим, что это действительно Keyzetsu Clipper, значит мы на верном пути.

Отчет с VirusTotal

Отчет с VirusTotal

Перейдем к статическому анализу. Запускаем IDA Free и загружаем туда наш файл, но сразу видим ошибку.

Ошибка IDA

Ошибка IDA

Тип cli скорее всего означает, что наш вирус написан на C#. Для статического анализа файлов .NET есть несколько решений. Изначально я использовал декомпилятор от JetBrains под названием dotPeek. Его главным минусом является невозможность переименовывать классы, методы, переменные и пространства имен, что делает его практически бесполезным для наших целей. Поискав немного в Интернете я нашел альтернативу, под название dnSpy, декомпилятор .NET с возможностью переименовывать объекты языка.

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

Обфусцированные модули файла TaskHostManagerService.bin

Обфусцированные модули файла TaskHostManagerService.bin

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

Использование de4dot

Использование de4dot

Загружаем уже новый файл и сравниваем.

Деобфусцированные модули файла TaskHostManagerService.bin

Деобфусцированные модули файла TaskHostManagerService.bin

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

Анализ кода

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

Метод Decrypt

Метод Decrypt

Здесь мы видим простой алгоритм расшифровки строк, которые встречаются по всему телу программы. Строка конвертируется из base64 в байтовый массив и «разжимается» с помощью gzip.

Для расшифровки я написал еще один скрипт на Python.

import base64
import gzip

def decrypt(string):
    decoded_string = base64.b64decode(string)
    return gzip.decompress(decoded_string).decode()

while (1):
    encrypted_string = input()
    print(decrypt(encrypted_string))

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

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

Фрагмент класса конфигурации

Фрагмент класса конфигурации

Увидев токен для бота Telegram я сразу решил посмотреть, как выглядит переписка. На GitHub я нашел проект, написанный для дампа информации с Telegram ботов. Вставляем расшифрованный токен бота и получаем информацию о боте и список сообщений. Самые ранние из них датируются мартом 2023 года. Все сообщения выглядят примерно вот так:

Одно из сообщений

Одно из сообщений

Теперь давайте погрузимся в работу нашего зловреда. Для этого переходим к точке входа, метод Main.

Точка входа

Точка входа

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

Инициализация

Здесь мы рассматриваем метод Init() класса Initializer.

Начало метода Init

Начало метода Init

Инициализация проходит только если файл не запущен из папки ProgramData, то есть при первом запуске. Тут запускается несколько потоков. 3 из них направлены на установку персистенcа, а последний работает с файловой системой.

Вредонос копирует себя в папку ProgramData и добавляет рандомное количество байт к концу файла. Это сделано, чтобы было сложнее получить его хэш. Далее вирус добавляет ключ в реестр для запуска после загрузки операционной системы. В зависимости от того, запустил ли вредонос администратор или обычный пользователь, ключ добавляется либо в HKEY_LOCAL_MACHINE либо в HKEY_LOCAL_USER. После этого вредонос добавляет себя в планировщик задач. За это отвечают первые 3 класса и их методы.

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

Вторая часть метода Init

Вторая часть метода Init

В этом фрагменте вредонос отправляет сообщение C2 о том, что была взломана очередная жертва. Потом он запускает копию себя, которая уже находится в папке ProgramData, а сам завершает свою работу. После этого он удаляет файлы своего присутствия в папке, из которой он был изначально запущен. Делает он это путем создания .bat файла с командами для удаления и его запуском с задержкой в 7 секунд. Задержка нужна чтобы первый запущенный экземпляр успел завершить свою работу.

Теперь мы переходим к интересному — к замене самих кошельков в локальных файлах и буфере обмена. За это отвечают две различных группы классов. Начнем по порядку, с классов, отвечающих за файлы. За замену кошельков в файлах отвечает класс FilesReplacerClass и его метод ReplaceWalletsInFiles.

Замена кошельков в файлах

Фрагмент метода ReplaceWalletsInFiles класса FilesReplacerClass

Фрагмент метода ReplaceWalletsInFiles класса FilesReplacerClass

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

Фрагмент метода ReplaceWallets класса WalletClass

Фрагмент метода ReplaceWallets класса WalletClass

В методе ReplaceWallets мы ищем совпадение в тексте файла с регулярным выражением и если находим его — меняем на подставной кошелёк. Замена происходит в методе ReplaceWallet класса WalletReplacer, который просто меняет настоящий кошелёк на рандомный из списка подставных.

С файлами дела обстоят довольно просто, теперь давайте посмотрим, что там с буфером обмена.

Замена кошельков в буфере обмена

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

Конструктор класса ClipboardInit

Конструктор класса ClipboardInit

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

Фрагмент переопределенного метода WndProc

Фрагмент переопределенного метода WndProc

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

Метод ReplaceClipboard класса ClipboardScanner

Метод ReplaceClipboard класса ClipboardScanner

В этом методе мы также, как в случае с файлами, ищем совпадение с регулярным выражением и передаем его методу Run класса ClipboardReplacer, который заменит содержимое буфера обмена.

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

Опуская ненужную логику покажу самые главные фрагменты метода Run класса ClipboardReplacer.

Проход слева метода Run класса ClipboardReplacer

Проход слева метода Run класса ClipboardReplacer

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

Проход справа метода Run класса ClipboardReplacer

Проход справа метода Run класса ClipboardReplacer

После этого повторяем то же самое, но только в этот раз сравниваем символы с правого края. Аналогочно проходу слева, сравнение происходит в методе CalculateWalletsSimilarityRight.

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

Заключение

Итак, мы зареверсили и посмотрели, как работает Keyzetsu Clipper. Для меня это был очень полезный опыт, ведь до этого я не имел дела с настоящими вредоносами и приложениями, написанными .NET.

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

© Habrahabr.ru