Если помните, недавно у нас выходила статья про молодой, но уже подающий надежды data stealer Loki. Тогда мы подробно рассмотрели этот экземпляр (версия 1.8), получили представление о работе бота и освоили инструмент, облегчающий реагирование на события, связанные с этим ВПО. Для более полного понимания ситуации, давайте разберем еще одно шпионское ПО и сравним исследованных ботов. Сегодня мы обратим внимание на Pony — более старый, но не менее популярный образец data stealer’а. Никита Карпов, аналитик CERT-GIB, расскажет, как бот проникает на компьютер жертвы и как вычислить похищенные данные, когда заражение уже произошло.
Впервые Pony был замечен в 2011 году и все еще продолжает использоваться. Как и в ситуации с Loki, популярность этого ВПО обусловлена тем, что несколько версий бота вместе с панелью администратора можно без проблем найти в сети. Например, здесь.
Экземпляр Pony, который мы будем изучать, защищен тем же самым упаковщиком, что и Loki, рассмотренный в предыдущей статье. По этой причине не будем еще раз останавливаться на процессе получения чистого ВПО и перейдем сразу к более интересным моментам. Единственное, что следует упомянуть перед разбором ВПО, — ссылка на сервер, по которой мы определяем нужный PE-файл, оканчивается на gate.php, и это один из индикаторов Pony.
При исследовании дизассемблированного кода Pony обратим внимание на участок, содержащий главные функции. Интерес представляют две из них — Initialize_Application и CnC_Func (названия функций переименованы в соответствии с их содержанием).
Ниже представлена функция Initialize_Application. Она отвечает за инициализацию необходимых элементов (библиотеки, привилегии и т.д.) и за похищение данных. В процессе работы ВПО несколько раз использует значение 7227 — пароль к данному экземпляру бота. В Initialize_Application это значение используется для шифрования буфера, содержащего данные приложений, алгоритмом RC4.
Далее перейдем к декомпилированному коду функции CnC_Func и разберем ее алгоритм:
Буфер, полученный в результате работы функции Initialize_Application, передается в функцию BuildPacket, где собирается пакет данных для передачи на сервер.
По каждому URI из списка бот отправляет данные и ожидает подтверждения со стороны сервера. Если сервер не ответил 3 раза — бот идет дальше.
После завершения первого списка CnC бот пытается загрузить и запустить дополнительное ВПО.
В общем доступе находится готовый билдер, который подтверждает функционал, полученный в процессе статического анализа декомпилированного кода. Пользователю предлагается ввести список URI, куда будут выгружаться похищенные данные, и список, откуда будет выгружаться дополнительное ВПО. Также пользователь может изменить пароль бота и имя дополнительного ВПО.
Pony атакует более сотни приложений, и, хотя у него есть функционал загрузчика, в основном Pony используется именно для похищения пользовательских данных. В таблице ниже перечислены все приложения, из которых бот может похитить данные.
ID |
Приложение |
ID |
Приложение |
ID |
Приложение |
0 |
System Info |
45 |
FTPGetter |
90 |
Becky! |
1 |
FAR Manager |
46 |
ALFTP |
91 |
Pocomail |
2 |
Total Commander |
47 |
Internet Explorer |
92 |
IncrediMail |
3 |
WS_FTP |
48 |
Dreamweaver |
93 |
The Bat! |
4 |
CuteFTP |
49 |
DeluxeFTP |
94 |
Outlook |
5 |
FlashFXP |
50 |
Google Chrome |
95 |
Thunderbird |
6 |
FileZilla |
51 |
Chromium / SRWare Iron |
96 |
FastTrackFTP |
7 |
FTP Commander |
52 |
ChromePlus |
97 |
Bitcoin |
8 |
BulletProof FTP |
53 |
Bromium (Yandex Chrome) |
98 |
Electrum |
9 |
SmartFTP |
54 |
Nichrome |
99 |
MultiBit |
10 |
TurboFTP |
55 |
Comodo Dragon |
100 |
FTP Disk |
11 |
FFFTP |
56 |
RockMelt |
101 |
Litecoin |
12 |
CoffeeCup FTP / Sitemapper |
57 |
K-Meleon |
102 |
Namecoin |
13 |
CoreFTP |
58 |
Epic |
103 |
Terracoin |
14 |
FTP Explorer |
59 |
Staff-FTP |
104 |
Bitcoin Armory |
15 |
Frigate3 FTP |
60 |
AceFTP |
105 |
PPCoin (Peercoin) |
16 |
SecureFX |
61 |
Global Downloader |
106 |
Primecoin |
17 |
UltraFXP |
62 |
FreshFTP |
107 |
Feathercoin |
18 |
FTPRush |
63 |
BlazeFTP |
108 |
NovaCoin |
19 |
WebSitePublisher |
64 |
NETFile |
109 |
Freicoin |
20 |
BitKinex |
65 |
GoFTP |
110 |
Devcoin |
21 |
ExpanDrive |
66 |
3D-FTP |
111 |
Frankocoin |
22 |
ClassicFTP |
67 |
Easy FTP |
112 |
ProtoShares |
23 |
Fling |
68 |
Xftp |
113 |
MegaCoin |
24 |
SoftX |
69 |
RDP |
114 |
Quarkcoin |
25 |
Directory Opus |
70 |
FTP Now |
115 |
Worldcoin |
26 |
FreeFTP / DirectFTP |
71 |
Robo-FTP |
116 |
Infinitecoin |
27 |
LeapFTP |
72 |
Certificate |
117 |
Ixcoin |
28 |
WinSCP |
73 |
LinasFTP |
118 |
Anoncoin |
29 |
32bit FTP |
74 |
Cyberduck |
119 |
BBQcoin |
30 |
NetDrive |
75 |
Putty |
120 |
Digitalcoin |
31 |
WebDrive |
76 |
Notepad++ |
121 |
Mincoin |
32 |
FTP Control |
77 |
CoffeeCup Visual Site Designer |
122 |
Goldcoin |
33 |
Opera |
78 |
FTPShell |
123 |
Yacoin |
34 |
WiseFTP |
79 |
FTPInfo |
124 |
Zetacoin |
35 |
FTP Voyager |
80 |
NexusFile |
125 |
Fastcoin |
36 |
Firefox |
81 |
FastStone Browser |
126 |
I0coin |
37 |
FireFTP |
82 |
CoolNovo |
127 |
Tagcoin |
38 |
SeaMonkey |
83 |
WinZip |
128 |
Bytecoin |
39 |
Flock |
84 |
Yandex.Internet / Ya.Browser |
129 |
Florincoin |
40 |
Mozilla |
85 |
MyFTP |
130 |
Phoenixcoin |
41 |
LeechFTP |
86 |
sherrod FTP |
131 |
Luckycoin |
42 |
Odin Secure FTP Expert |
87 |
NovaFTP |
132 |
Craftcoin |
43 |
WinFTP |
88 |
Windows Mail |
133 |
Junkcoin |
44 |
FTP Surfer |
89 |
Windows Live Mail |
Рассмотрим подробнее сетевое взаимодействие Pony. Как мы уже говорили, Pony сначала выгружает похищенные данные приложений на удаленный сервер, и индикатором такой коммуникации служит gate.php. После этого Pony просматривает второй список ссылок, откуда он пытается загрузить дополнительное ВПО на зараженный компьютер.
Для подтверждения того, что сервер получил и прочитал данные, бот должен получить в ответ строку STATUS-IMPORT-OK, иначе бот считает, что сервер не получил данные.
Данные, передаваемые на сервер, надежно защищаются шифрованием и компрессией. Защиту данных определяет заголовок, который идет перед ними. Стандартная защита пакета выглядит так:
Данные в чистом виде с заголовком PWDFILE0.
Сжатые данные с заголовком PKDFILE0. Для сжатия используется библиотека aPLib, работа которой основана на алгоритме компрессии LZW.
Зашифрованные данные с заголовком CRYPTED0 и ключом в виде пароля, например, 7227 или PA$$. Для шифрования используется алгоритм RC4.
Зашифрованные алгоритмом RC4 данные, ключ указан в первых 4 байтах.
Размер |
Значение |
Описание |
0x4 |
rc_4key |
Ключ для верхнего уровня шифрования |
0x12 |
REPORT_HEADER (PWDFILE0/ PKDFILE0/ CRYPTED0) |
Заголовок отчета о похищенных данных (normal/packed/crypted) 8 байт — заголовок, и 4 байта — контрольная сумма CRC32 |
0x4 |
Версия отчета |
Версия отчета о похищенных данных (константное значение 01.0) |
0x4 |
Размер модуля |
Заголовок модуля, присутствует у каждого модуля |
0x8 |
ID заголовка модуля (chr(2).chr(0).”MODU”.chr(1).chr(1)) 2 байта, ключевое слово MODU, 1 байт, 1 байт |
|
0x2 |
ID модуля |
|
0x2 |
Версия модуля |
|
– |
Название системы пользователя |
Модуль “module_systeminfo” (module id = 0x00000000) Содержит информацию о системе пользователя |
0x2 |
Система x32 или x64 |
|
– |
Страна пользователя |
|
– |
Язык системы пользователя |
|
0x2 |
Является ли пользователь администратором |
|
– |
Значение MachineGuid из приложения WinRAR |
|
– |
Список модулей всех приложений |
По аналогии с модулем “module_systeminfo” записаны данные всех приложений |
Как и для Loki, напишем парсер на Python, используя следующие библиотеки:
Dpkt для поиска пакетов, принадлежащих Pony, и работы с ними.
aPLib для декомпрессии данных.
Hexdump для представления данных пакета в хексе.
JSON для записи найденной информации в удобном виде.
Рассмотрим основные части алгоритма работы скрипта:
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 != "":
pkt = httpheader + tcp.data
req += 1
request = dpkt.http.Request(pkt)
parsed_payload['Network'].update({'Request method': request.method})
uri = request.headers['host'] + request.uri
parsed_payload['Network'].update({'CnC': uri})
parsed_payload['Network'].update({'User-agent': request.headers['user-agent']})
if uri.find("gate.php") != -1:
parsed_payload['Network'].update({'Traffic Purpose': "Exfiltrate Stolen Data"})
parse(tcp.data, debug)
elif uri.find(".exe") != -1:
parsed_payload['Network'].update({'Traffic Purpose': "Download additional malware"})
print(json.dumps(parsed_payload, ensure_ascii=False, sort_keys=False, indent=4))
parsed_payload['Network'].clear()
parsed_payload['Malware Artifacts/IOCs'].clear()
parsed_payload['Compromised Host/User Data'].clear()
parsed_payload['Applications'].clear()
print("----------------------")
if tcp.sport == 80 and len(tcp.data) > 0: # HTTP RESPONCE
resp += 1
response = dpkt.http.Response(tcp.data)
if response.body.find(b'STATUS-IMPORT-OK') != -1:
AdMalw = True
print('Data imported successfully')
else:
print('C2 did not receive data')
print("----------------------")
except(dpkt.dpkt.NeedData, dpkt.dpkt.UnpackError):
continue
print("Requests: " + str(req))
print("Responces: " + str(resp))
Поиск пакетов, связанных с Pony, аналогичен поиску пакетов Loki. Ищем все HTTP-пакеты. Парсим запросы, в которых находится информация бота. Остальные запросы фиксируются, но данные в них не обрабатываются. Если в ответ на запрос получена строка STATUS-IMPORY-OK — отмечаем успешную выгрузку данных. Во всех других случаях считаем, что сервер не получил данные. Если после выгрузки данных найдены HTTP-запросы с URI, оканчивающимся на .exe — отмечаем загрузку дополнительного ВПО.
Рассмотрим функцию, отвечающую за снятие всей защиты с данных и импорт модулей:
def process_report_data(data, debug):
index = 0
if len(str(data)) == 0:
return False
elif len(str(data)) < 12:
return False
elif len(str(data)) > REPORT_LEN_LIMIT:
return False
elif len(str(data)) == 12:
return True
if verify_new_file_header(data):
rand_decrypt(data)
report_id = read_strlen(data, index, 8)
index += 8
if report_id == REPORT_CRYPTED_HEADER:
parsed_payload['Malware Artifacts/IOCs'].update({'Crypted': report_id.decode('utf-8')})
decrypted_data = rc4DecryptText(report_password, data[index:len(str(data))])
data = decrypted_data
index = 0
report_id = read_strlen(data, index, 8)
index += 8
if report_id == REPORT_PACKED_HEADER:
parsed_payload['Malware Artifacts/IOCs'].update({'Packed': report_id.decode('utf-8')})
unpacked_len = read_dword(data, index)
index += 4
leng = read_dword(data, index)
index += 4
if leng < 0:
return False
if not leng:
return ""
if index + leng > len(str(data)):
return False
packed_data = data[index:index + leng]
index += leng
if unpacked_len > REPORT_LEN_LIMIT or len(str(packed_data)) > REPORT_LEN_LIMIT:
return False
if not len(str(packed_data)):
return False
if len(str(packed_data)):
data = unpack_stream(packed_data, unpacked_len)
if not len(str(data)):
return False
if len(str(data)) > REPORT_LEN_LIMIT:
return False
index = 0
report_id = read_strlen(data, index, 8)
index += 8
if report_id != REPORT_HEADER:
print("No header")
return False
version_id = read_strlen(data, index, 3)
index += 8
if version_id != REPORT_VERSION:
return False
parsed_payload['Malware Artifacts/IOCs'].update({'Data version': version_id.decode('utf-8')})
hexdump.hexdump(data)
report_version_id = version_id
parsed_payload['Applications'].update({'Quantity': 0})
while index < len(data):
index = import_module(data, index, debug)
return data
После снятия обязательного шифрования, определяем метод снятия следующего уровня защиты — в зависимости от заголовка. Если присутствует дополнительное шифрование с заголовком CRYPTED0 — скрипт пытается подставить стандартный ключ, и при несоответствия ключа запрашивает файл ВПО, в котором находит используемый в этом боте пароль. Если заголовок данных PWDFILE0 — начинаем импорт модулей приложений.
Для расшифровки мы использовали алгоритм RC4:
def rc4DecryptHex(key, pt):
if key == '':
return pt
s = list(range(256))
j = 0
for i in range(256):
j = (j + s[i] + key[i % len(key)]) % 256
s[i], s[j] = s[j], s[i]
i = j = 0
ct = []
for char in pt:
i = (i + 1) % 256
j = (j + s[i]) % 256
s[i], s[j] = s[j], s[i]
ct.append(chr(char ^ s[(s[i] + s[j]) % 256]))
decrypted_text = ''.join(ct)
data = decrypted_text.encode('raw_unicode_escape')
return data
Результат работы парсера представлен ниже. Парсер успешно снял шифрование, произвел декомпрессию и нашел похищенные данные. Следует отметить, что у каждого модуля есть несколько типов представления данных, в зависимости от найденной ботом информации. В нашем примере бот похитил данные Outlook и записал их с типом 7. На первый запрос сервер ответил боту, а остальные коммуникации не несли полезной информации.
В заключение давайте сравним исследованные data stealer’ы Pony и Loki и подведем итог. Список атакуемых приложений и у Pony, и у Loki примерно одинаков, но функционал Loki, особенно в новых версиях, шире, чем у Pony. Pony защищает все передаваемые данные в несколько уровней, что не дает определить без специального инструмента, какие именно данные похитил бот. Loki, в свою очередь, передает все данные в открытом виде, но без знания структуры запросов разобрать эти данные тоже довольно сложно.
Надеемся, эти две статьи помогли разобраться, какую опасность несут данные data stealer’ы и как можно упростить реагирование на инциденты с помощью реализованных нами инструментов.
Apple возобновила переговоры с OpenAI о возможности внедрения ИИ-технологий в iOS 18, на основе данной операционной системы будут работать новые…
Конкурсный управляющий российской «дочки» Google подготовил 23 иска к участникам рекламного рынка. Общая сумма исков составляет 16 млрд рублей –…
Google завершил обновление основного алгоритма March 2024 Core Update. Раскатка обновлений была завершена 19 апреля, но сообщил об этом поисковик…
У частных продавцов на Авито появилась возможность составлять текст объявлений с помощью нейросети. Новый функционал доступен в категории «Обувь, одежда,…
24 апреля 2024 года в Москве состоялась церемония вручения наград международного конкурса Workspace Digital Awards. В этом году участниками стали…
27 июня Яндекс проведет гик-фестиваль Young Con для студентов и молодых специалистов, которые интересуются технологиями и хотят работать в IT.…