В середине июня борьба с коронавирусом в Казахстане была в самом разгаре. Встревоженные ростом числа заболевших (тогда заразился даже бывший президент Нурсултан Назарбаев), местные власти решились вновь позакрывать все торгово-развлекательные центры, сетевые магазины, рынки и базары. И в этот момент ситуацией воспользовались киберпреступники, отправившие по российским и международным компаниям вредоносную рассылку.
Опасные письма, замаскированную под обращение министра здравоохранения Республики Казахстан, перехватила система 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 | Trojita |
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: 39
Decompressed 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: 39
No 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: 26
Commands: 1
Command: 1
Command ID: 0
Command type: 9
Command timelimit: 0
Command 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: 3
Responces: 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»