В середине июня борьба с коронавирусом в Казахстане была в самом разгаре. Встревоженные ростом числа заболевших (тогда заразился даже бывший президент Нурсултан Назарбаев), местные власти решились вновь позакрывать все торгово-развлекательные центры, сетевые магазины, рынки и базары. И в этот момент ситуацией воспользовались киберпреступники, отправившие по российским и международным компаниям вредоносную рассылку.
Опасные письма, замаскированную под обращение министра здравоохранения Республики Казахстан, перехватила система Threat Detection System (TDS) Group-IB. Во вложении письма находились документы, при запуске которых на компьютер устанавливалась вредоносная программа из семейства Loki PWS (Password Stealer), предназначенная для кражи логинов и паролей с зараженного компьютера. В дальнейшем злоумышленники могут использовать их для получения доступа к почтовым аккаунтам для финансового мошенничества, шпионажа или продать на хакерских форумах.
В этой статье Никита Карпов, аналитик CERT-GIB, рассматривает экземпляр одного из самых популярных сейчас Data Stealerов Loki.
Сегодня мы рассмотрим одну из популярных версий бота 1.8. Она активно продается, а админ панель можно найти даже в открытом доступе: здесь.
Пример админ-панели:
Loki написан на языке C++ и является одним из самых популярных ВПО, используемых для похищения пользовательской информации с зараженного компьютера. Как и бич нашего времени вирусы-шифровальщики Data Stealer после попадания на компьютер жертвы с очень большой скоростью выполняют поставленную задачу ему не надо закрепляться и повышать свои привилегии в системе, он практически не оставляет времени на защиту от атаки. Поэтому в событиях с ВПО, которое похищает пользовательские данные, главную роль играет расследование инцидента.
Распаковка и получение работоспособного дампа ВПО
Распространение в большинстве случаев происходит через вложения в рассылках писем. Пользователь под видом легитимного файла загружает и открывает вложение, запуская работу ВПО.
По маркеру инжекта можно предположить о наличии Loader.
С помощью DIE получаем информацию, что исходный файл написан на VB6.
График энтропии свидетельствует о большом количестве зашифрованных данных.
При запуске первый процесс создает дочерний, совершает инжект и завершает свою работу. Второй процесс отвечает за работу ВПО. После небольшого промежутка времени останавливаем работу процесса и сохраняем дамп памяти. Чтобы подтвердить, что внутри дампа находится Loki, ищем внутри url командного центра, который в большинстве случаев оканчивается на fre.php.
Выполняем дамп фрагмента памяти, содержащего Loki, и производим корректировку PE-заголовка.
Работоспособность дампа проверим с помощью системы TDS Huntbox.
Функционал бота
В процессе исследования декомпилированного кода ВПО находим часть, содержащую четыре функции, идущие сразу после инициализации необходимых для работы библиотек. Разобрав каждую из них внутри, определяем их назначение и функциональность нашего ВПО.
Названия функций для удобства были переименованы в более информативные.
Функционал бота определяется двумя главными функциями:
- Data Stealer первая функция, отвечающая за похищение данных из 101 приложения и отправку на сервер.
- Downloader запрос от CnC (Command & Control) команд для исполнения.
Для удобства в таблице, приведенной ниже, представлены все приложения, из которых исследуемый экземпляр Loki пытается похитить данные.
ID функции | Приложение | ID функции | Приложение | ID функции | Приложение |
---|---|---|---|---|---|
1 | Mozilla Firefox | 35 | FTPInfo | 69 | ClassicFTP |
2 | Comodo IceDragon | 36 | LinasFTP | 70 | PuTTY/KiTTY |
3 | Apple Safari | 37 | FileZilla | 71 | Thunderbird |
4 | K-Meleon | 38 | Staff-FTP | 72 | Foxmail |
5 | SeaMonkey | 39 | BlazeFtp | 73 | Pocomail |
6 | Flock | 40 | NETFile | 74 | IncrediMail |
7 | NETGATE BlackHawk | 41 | GoFTP | 75 | Gmail notifier pro |
8 | Lunascape | 42 | ALFTP | 76 | Checkmail |
9 | Google Chrome | 43 | DeluxeFTP | 77 | WinFtp |
10 | Opera | 44 | Total Commander | 78 | Martin Prikryl |
11 | QTWeb Browser | 45 | FTPGetter | 79 | 32BitFtp |
12 | QupZilla | 46 | WS_FTP | 80 | FTP Navigator |
13 | Internet Explorer | 47 | Mail Client configuration files | 81 | Mailing (softwarenetz) |
14 | Opera 2 | 48 | Full Tilt Poker | 82 | Opera Mail |
15 | Cyberfox | 49 | PokerStars | 83 | Postbox |
16 | Pale Moon | 50 | ExpanDrive | 84 | FossaMail |
17 | Waterfox | 51 | Steed | 85 | Becky! |
18 | Pidgin | 52 | FlashFXP | 86 | POP3 |
19 | SuperPutty | 53 | NovaFTP | 87 | Outlook |
20 | FTPShell | 54 | NetDrive | 88 | Ymail2 |
21 | NppFTP | 55 | Total Commander 2 | 89 | Trojit |
22 | MyFTP | 56 | SmartFTP | 90 | TrulyMail |
23 | FTPBox | 57 | FAR Manager | 91 | .spn Files |
24 | sherrod FTP | 58 | Bitvise | 92 | To-Do Desklist |
25 | FTP Now | 59 | RealVNC TightVNC |
93 | Stickies |
26 | NexusFile | 60 | mSecure Wallet | 94 | NoteFly |
27 | Xftp | 61 | Syncovery | 95 | NoteZilla |
28 | EasyFTP | 62 | FreshFTP | 96 | Sticky Notes |
29 | SftpNetDrive | 63 | BitKinex | 97 | KeePass |
30 | AbleFTP | 64 | UltraFXP | 98 | Enpass |
31 | JaSFtp | 65 | FTP Now 2 | 99 | My RoboForm |
32 | Automize | 66 | Vandyk SecureFX | 100 | 1Password |
33 | Cyberduck | 67 | Odin Secure FTP Expert | 101 | Mikrotik WinBox |
34 | Fullsync | 68 | Fling |
Сетевое взаимодействие
Для записи сетевого взаимодействия необходимо решить две проблемы:
- Командный центр доступен только на момент проведения атаки.
- Wireshark не фиксирует коммуникации бота в loopback, поэтому нужно пользоваться другими средствами.
Самое простое решение переадресовать адрес CnC, с которым Loki будет устанавливать коммуникацию, на localhost. Для бота сервер теперь доступен в любое время, хоть и не отвечает, но для записи коммуникаций бота это и не нужно. Для решения второй проблемы воспользуемся утилитой RawCap, которая позволяет записать в pcap необходимые нам коммуникации. Далее записанный pcap будем разбирать уже в Wireshark.
Перед каждой коммуникацией бот проверяет доступность CnC и, если он доступен открывает socket. Все сетевые коммуникации проходят на транспортном уровне по протоколу TCP, а на прикладном используется HTTP.
В таблице ниже представлены заголовки пакета, которые стандартно использует Loki.
Поле | Значение | Описание |
---|---|---|
User-agent | Mozilla/4.08 (Charon; Inferno) | Характерный юзерагент для Loki |
Accept | */* | |
Content-Type | application/octet-stream | |
Content-Encoding | binary | |
Content-Key | 7DE968CC | Результат хеширования предыдущих заголовков (хеширование происходит кастомным алгоритмом CRC с полиномом 0xE8677835) |
Connection | close |
- Структура записанных данных зависит от версии бота, и в более ранних версиях отсутствуют поля, которые отвечают за опции шифрования и компрессии.
- По типу запроса сервер определяет, как обрабатывать полученные
данные. Существует 7 типов данных, которые может прочитать
сервер:
- 0x26 Похищенные данные кошельков
- 0x27 Похищенные данные приложений
- 0x28 Запрос команд от сервера
- 0x29 Выгрузка похищенного файла
- 0x2A POS
- 0x2B Данные кейлоггера
- 0x2C Скриншот
- В исследованном экземпляре присутствовали только 0x27, 0x28 и 0x2B.
- В каждом запросе есть общая информация о боте и зараженной системе, по которой сервер идентифицирует все отчеты по одной машине, а после идет информация, которая зависит от типа запроса.
- В последней версии бота реализовано только сжатие данных, а поля с шифрованием заготовлены на будущее и не обрабатываются сервером.
- Для сжатия данных используется открытая библиотека APLib.
При формировании запроса с похищенными данными бот выделяет буфер размером 0x1388 (5000 байт). Структура запросов 0x27 представлена в таблице ниже:
Смещение | Размер | Значение | Описание |
---|---|---|---|
0x0 | 0x2 | 0x0012 | Версия бота |
0x2 | 0x2 | 0x0027 | Тип запроса (отправка похищенных данных) |
0x4 | 0xD | ckav.ru | Binary ID (также встречается значение XXXXX11111) |
0x11 | 0x10 | - | Имя пользователя |
0x21 | 0x12 | - | Имя компьютера |
0x33 | 0x12 | - | Доменное имя компьютера |
0x45 | 0x4 | - | Разрешение экрана (ширина и высота) |
0x49 | 0x4 | - | |
0x4D | 0x2 | 0x0001 | Флаг прав пользователя (1, если администратор) |
0x4F | 0x2 | 0x0001 | Флаг идентификатора безопасности (1, если установлен) |
0x51 | 0x2 | 0x0001 | Флаг разрядности системы (1, если x64) |
0x53 | 0x2 | 0x0006 | Версия Windows (major version number) |
0x55 | 0x2 | 0x0001 | Версия Windows (minor version number) |
0x57 | 0x2 | 0x0001 | Дополнительная информация о системе (1 = VER_NT_WORKSTATION) |
0x59 | 0x2 | - | |
0x5B | 0x2 | 0x0000 | Отправлялись ли похищенные данные |
0x5D | 0x2 | 0x0001 | Использовалось ли сжатие данных |
0x5F | 0x2 | 0x0000 | Тип сжатия |
0x61 | 0x2 | 0x0000 | Использовалось ли шифрование данных |
0x63 | 0x2 | 0x0000 | Тип шифрования |
0x65 | 0x36 | - | MD5 от значения регистра MachineGuid |
0x9B | - | - | Сжатые похищенные данные |
Размер буфера: 0x2BC (700 байт)
Смещение | Размер | Значение | Описание |
---|---|---|---|
0x0 | 0x2 | 0x0012 | Версия бота |
0x2 | 0x2 | 0x0028 | Тип запроса (запрос команд от командного центра) |
0x4 | 0xD | ckav.ru | Binary ID (также встречается значение XXXXX11111) |
0x11 | 0x10 | - | Имя пользователя |
0x21 | 0x12 | - | Имя компьютера |
0x33 | 0x12 | - | Доменное имя компьютера |
0x45 | 0x4 | - | Разрешение экрана (ширина и высота) |
0x49 | 0x4 | - | |
0x4D | 0x2 | 0x0001 | Флаг прав пользователя (1, если администратор) |
0x4F | 0x2 | 0x0001 | Флаг идентификатора безопасности (1, если установлен) |
0x51 | 0x2 | 0x0001 | Флаг разрядности системы (1, если x64) |
0x53 | 0x2 | 0x0006 | Версия Windows (major version number) |
0x55 | 0x2 | 0x0001 | Версия Windows (minor version number) |
0x57 | 0x2 | 0x0001 | Дополнительная информация о системе (1 = VER_NT_WORKSTATION) |
0x59 | 0x2 | 0xFED0 | |
0x5B | 0x36 | - | MD5 от значения регистра MachineGuid |
Размер буфера: 0x10 (16 байт) + 0x10 (16 байт) за каждую команду в пакете.
HTTP- заголовок (начало данных) | \r\n\r\n | [0D 0A 0D 0A] | 4 байта | ||
Суммарная длина данных | - | - | 4 байта | ||
Количество команд | 2 | [00 00 00 02] | 4 байта | ||
Команда | Пропускаемые байты 4 байта |
Команды 4 байта |
Пропускаемые байты 4 байта |
Длина передаваемой строки 4 байта |
Передаваемая строка (пример) |
---|---|---|---|---|---|
#0 Загрузка и запуск EXE-файла |
[00 00 00 00] | [00 00 00 00] | [00 00 00 00] | [00 00 00 23] | www.notsogood.site/malicious.exe |
#1 Загрузка библиотеки DLL |
[00 00 00 00] | [00 00 00 01] | [00 00 00 00] | [00 00 00 23] | www.notsogood.site/malicious.dll |
#2 Загрузка EXE-файла |
[00 00 00 00] | [00 00 00 02] | [00 00 00 00] | [00 00 00 23] | www.notsogood.site/malicious.exe |
#8 Удаление базы данных хешей (HDB file) |
[00 00 00 00] | [00 00 00 08] | [00 00 00 00] | [00 00 00 00] | - |
#9 Старт кейлоггера |
[00 00 00 00] | [00 00 00 09] | [00 00 00 00] | [00 00 00 00] | - |
#10 Похищение данных и отправка на сервер |
[00 00 00 00] | [00 00 00 0A] | [00 00 00 00] | [00 00 00 00] | - |
#14 Завершение работы Loki |
[00 00 00 00] | [00 00 00 0E] | [00 00 00 00] | [00 00 00 00] | - |
#15 Загрузка новой версии Loki и удаление старой |
[00 00 00 00] | [00 00 00 0F] | [00 00 00 00] | [00 00 00 23] | www.notsogood.site/malicious.exe |
#16 Изменение частоты проверки ответа от сервера |
[00 00 00 00] | [00 00 00 10] | [00 00 00 00] | [00 00 00 01] | 5 |
#17 Удалить Loki и завершить работу |
[00 00 00 00] | [00 00 00 11] | [00 00 00 00] | [00 00 00 00] | - |
Парсер сетевого трафика
Благодаря проведенному анализу у нас есть вся необходимая информация для парсинга сетевых взаимодействий Loki.
Парсер реализован на языке Python, на вход получает pcap-файл и в нем находит все коммуникации, принадлежащие Loki.
Для начала воспользуемся библиотекой dkpt для поиска всех TCP-пакетов. Для получения только http-пакетов поставим фильтр на используемый порт. Среди полученных http-пакетов отберем те, что содержат известные заголовки Loki, и получим коммуникации, которые необходимо распарсить, чтобы извлечь из них информацию в читаемом виде.
for ts, buf in pcap: eth = dpkt.ethernet.Ethernet(buf) if not isinstance(eth.data, dpkt.ip.IP): ip = dpkt.ip.IP(buf) else: ip = eth.data if isinstance(ip.data, dpkt.tcp.TCP): tcp = ip.data try: if tcp.dport == 80 and len(tcp.data) > 0: # HTTP REQUEST if str(tcp.data).find('POST') != -1: http += 1 httpheader = tcp.data continue else: if httpheader != "": print('Request information:') pkt = httpheader + tcp.data httpheader = "" if debug: print(pkt) req += 1 request = dpkt.http.Request(pkt) uri = request.headers['host'] + request.uri parsed_payload['Network']['Source IP'] = socket.inet_ntoa(ip.src) parsed_payload['Network']['Destination IP'] = socket.inet_ntoa(ip.dst) parsed_payload_same['Network']['CnC'] = uri parsed_payload['Network']['HTTP Method'] = request.method if uri.find("fre.php"): print("Loki detected!") pt = parseLokicontent(tcp.data, debug) parsed_payload_same['Malware Artifacts/IOCs']['User-Agent String'] = request.headers['user-agent'] print(json.dumps(parsed_payload, ensure_ascii=False, sort_keys=False, indent=4)) parsed_payload['Network'].clear() parsed_payload['Compromised Host/User Data'].clear() parsed_payload['Malware Artifacts/IOCs'].clear() print("----------------------") if tcp.sport == 80 and len(tcp.data) > 0: # HTTP RESPONCE resp += 1 if pt == 40: print('Responce information:') parseC2commands(tcp.data, debug) print("----------------------") pt = 0 except(dpkt.dpkt.NeedData, dpkt.dpkt.UnpackError): continue
Во всех запросах Loki первые 4 байта отвечают за версию бота и тип запроса. По этим двум параметрам определяем, как будем обрабатывать данные.
def parseLokicontent(data, debug): index = 0 botV = int.from_bytes(data[0:2], byteorder=sys.byteorder) parsed_payload_same['Malware Artifacts/IOCs']['Loki-Bot Version'] = botV payloadtype = int.from_bytes(data[2:4], byteorder=sys.byteorder) index = 4 print("Payload type: : %s" % payloadtype) if payloadtype == 39: parsed_payload['Network']['Traffic Purpose'] = "Exfiltrate Application/Credential Data" parse_type27(data, debug) elif payloadtype == 40: parsed_payload['Network']['Traffic Purpose'] = "Get C2 Commands" parse_type28(data, debug) elif payloadtype == 43: parsed_payload['Network']['Traffic Purpose'] = "Exfiltrate Keylogger Data" parse_type2b(lb_payload) elif payloadtype == 38: parsed_payload['Network']['Traffic Purpose'] = "Exfiltrate Cryptocurrency Wallet" elif payloadtype == 41: parsed_payload['Network']['Traffic Purpose'] = "Exfiltrate Files" elif payloadtype == 42: parsed_payload['Network'].['Traffic Purpose'] = "Exfiltrate POS Data" elif payloadtype == 44: parsed_payload['Network']['Traffic Purpose'] = "Exfiltrate Screenshots" return payloadtype
Следующим в очереди будет разбор ответа от сервера. Для считывания только полезной информации ищем последовательность \r\n\r\n, которая определяет конец заголовков пакета и начало команд от сервера.
def parseC2commands(data, debug): word = 2 dword = 4 end = data.find(b'\r\n\r\n') if end != -1: index = end + 4 if (str(data).find('<html>')) == -1: if debug: print(data) fullsize = getDWord(data, index) print("Body size: : %s" % fullsize) index += dword count = getDWord(data, index) print("Commands: : %s" % count) if count == 0: print('No commands received') else: index += dword for i in range(count): print("Command: %s" % (i + 1)) id = getDWord(data, index) print("Command ID: %s" % id) index += dword type = getDWord(data, index) print("Command type: %s" % type) index += dword timelimit = getDWord(data, index) print("Command timelimit: %s" % timelimit) index += dword datalen = getDWord(data, index) index += dword command_data = getString(data, index, datalen) print("Command data: %s" % command_data) index += datalen else: print('No commands received') return None
На этом заканчиваем разбор основной части алгоритма работы парсера и переходим к результату, который получаем на выходе. Вся информация выводится в json-формате.
Ниже представлены изображения результата работы парсера, полученные из коммуникаций различных ботов, с разными CnC и записанные в разных окружениях.
Request information:Loki detected!Payload type: 39Decompressed data: {'Module': {'Mozilla Firefox'}, 'Version': {0}, 'Data': {'domain': {'https://accounts.google.com'}, 'username': {'none@gmail.com'}, 'password': {'test'}}}{'Module': {'NppFTP'}, 'Version': {0}, 'Data': {b'<?xml version="1.0" encoding="UTF-8" ?>\r\n<NppFTP defaultCache="%CONFIGDIR%\\Cache\\%USERNAME%@%HOSTNAME%" outputShown="0" windowRatio="0.5" clearCache="0" clearCachePermanent="0">\r\n <Profiles />\r\n</NppFTP>\r\n'}}{ "Network": { "Source IP": "-", "Destination IP": "185.141.27.187", "HTTP Method": "POST", "Traffic Purpose": "Exfiltrate Application/Credential Data", "First Transmission": true }, "Compromised Host/User Data": {}, "Malware Artifacts/IOCs": {}}
Выше представлен пример запроса на сервер 0x27 (выгрузка данных приложений). Для тестирования были созданы аккаунты в трех приложениях: Mozilla Firefox, NppFTP и FileZilla. У Loki существует три варианта записи данных приложений:
- В виде SQL-базы данных (парсер сохраняет базу данных и выводит все доступные строки в ней).
- В открытом виде, как у Firefox в примере.
- В виде xml-файла, как у NppFTP и FileZilla.
Request information:Loki detected!Payload type: 39No data stolen{ "Network": { "Source IP": "-", "Destination IP": "185.141.27.187", "HTTP Method": "POST", "Traffic Purpose": "Exfiltrate Application/Credential Data", "First Transmission": false }, "Compromised Host/User Data": {}, "Malware Artifacts/IOCs": {}}
Второй запрос имеет тип 0x28 и запрашивает команды от сервера.
Responce information:Body size: 26Commands: 1Command: 1Command ID: 0Command type: 9Command timelimit: 0Command data: 35
Пример ответа от CnC, который отправил в ответ одну команду на старт кейлоггера. И последующая выгрузка данных кейлоггера.
Request information:Loki detected!Payload type: : 43{ "Network": { "Source IP": "-", "Destination IP": "185.141.27.187", "HTTP Method": "POST", "Traffic Purpose": "Exfiltrate Keylogger Data" }, "Compromised Host/User Data": {}, "Malware Artifacts/IOCs": {}}
В конце работы парсер выводит информацию, которая содержится в каждом запросе от бота (информацию о боте и о системе), и количество запросов и ответов, связанных с Loki в pcap-файле.
General information:{ "Network": { "CnC": "nganyin-my.com/chief6/five/fre.php" }, "Compromised Host/User Description": { "User Name": "-", "Hostname": "-", "Domain Hostname": "-", "Screen Resolution": "1024x768", "Local Admin": true, "Built-In Admin": true, "64bit OS": false, "Operating System": "Windows 7 Workstation" }, "Malware Artifacts/IOCs": { "Loki-Bot Version": 18, "Binary ID": "ckav.ru", "MD5 from GUID": "-", "User-Agent String": "Mozilla/4.08 (Charon; Inferno)" }}Requests: 3Responces: 3
Полный код парсера доступен по ссылке: github.com/Group-IB/LokiParser
Заключение
В этой статье мы ближе познакомились с ВПО Loki, разобрали его функционал и реализовали парсер сетевого трафика, который значительно упростит процесс анализа инцидента и поможет понять, что именно было похищено с зараженного компьютера. Хотя разработка Loki все еще продолжается, была слита только версия 1.8 (и более ранние), именно с этой версией специалисты по безопасности сталкиваются каждый день.
В следующей статье мы разберем еще один популярный Data Stealer, Pony, и сравним эти ВПО.
Indicator of Compromise (IOCs):
Urls:
- nganyin-my.com/chief6/five/fre.php
- wardia.com.pe/wp-includes/texts/five/fre.php
- broken2.cf/Work2/fre.php
- 185.141.27.187/danielsden/ver.php
- MD5 hash: B0C33B1EF30110C424BABD66126017E5
- User-Agent String: Mozilla/4.08 (Charon; Inferno)
- Binary ID: ckav.ru