История про Avant и его bookmarks.dat
sharpc — 18.09.2022 Почти с появления у меня интернета в 2003-м и года до 2009-го я использовал в качестве основного браузера китайский Avant Browser. В нем были табы, жесты мышью, быстрое отключение скриптов/картинок/флэша и удобный органайзер избранного, и все это в инсталляторе размером 2 МБ.Какая ж няшка.
Где-то между 7-й и 11-й версией он начал хранить закладки пользователя в одном файле собственного формата bookmarks.dat. Когда я переходил на Chrome, этот файл я аккуратно забэкапил (несколько раз) рядом с избранным из IE, 4000 .url файлами. На днях у меня дошли до него руки.
Первым делом оказалось, с использованием Lister, libmagic и binwalk, что формат этого bookmarks.dat бинарный, высокоэнтропийный и ничего знакомого не содержит. Первые 4 байта были похожи на размер файла, но именно что похожи — для 4 файлов меньше на неодинаковое число байт, для 1 больше.
Запустим Avant Browser 11.7.46, завалявшийся на винте, под популярной в 2008-м операционной системой. Procmon говорит, что bookmarks.dat читается. Подложим вместо него самый большой bookmarks.dat из бэкапа и обнаружим, что его парсить Avant отказывается. Да если бы и прочитал, подопытная версия ни во что экспортировать не умеет.
Запустим Avant под x64dbg и получим Protection Error. PEId подскажет, что avant.exe написан на Delphi и накрыт ASProtect. К счастью, DecomAS умеет его снимать, хотя и не без потерь, полученный avant_u.exe падает на старте.
Про распакованный avant_u.exe PEId + KANAL сообщают, что внутри есть ADLER32, BASE64, CRC32, MD5, RIJNDAEL (AES), SHA-256 и ZLIB.
Заметим рядом bookmarks.dat.vdt с base64 от каких-то рандомных 32 байт. Возможно, это и есть AES-ключ, тогда, конечно, он не сохранился. Придем в отчаяние. Методом тыка обнаружим, что другой bookmarks.dat из бэкапа, поменьше, открывается. Уйдем из отчаяния.
В популярном дизассемблере пометим функции, использующие найденные адреса AES-таблиц. Загрузим FLIRT-сигнатуры b32vcl, чтобы превратить часть call нечитабельной индиректности в T-имена. Экспортируем из DeDe browser.map с адресами функций, содержащих имена контролов Delphi и импортируем его в дизассемблер, почертыхавшись с адаптированием чьего-то скрипта. Помедитировав на декомпилят, осознаем, что букмарки хранятся в XML, но лапищи автора не столь длинны, чтобы в 4-метровом exe найти, откуда берется ключ для перегонки одного TCustomMemoryStream в другой. Рядом светились какие-то странные длинные строковые константы типа sdfdsjh2983hn2938fh32789ry32jhbjsf2, но не с длиной 32, и key derivation function тоже отыскать не удалось.
На странице плагинов x64dbg находим ScyllaHide, обещающий нормальный анти-анти-дебаг. Как водится, никто не тестирует софт под популярную операционную систему с виртуальной машиной весом 1.5 ГБ (вместо 12 ГБ у более новой популярной операционной системы), поэтому свежий x64dbg ругается на K32GetProcessImageFileNameW и разработчик не спешит это чинить. Что ж, берем snapshot_2020-12-14_15-31.zip. Аналогичную процедуру проделываем с ScyllaHide, которая жаждет функцию CompareStringEx, появившуюся в Vista, у нее берем снапшот с похожей датой. И, о чудо, Avant Browser запускается под отладчиком. Да, он все еще вылетает с Protection Error, если ставить аппаратные брекпойнты, но можно поставить обычные на адреса всех 4 найденных функций, которые что-то делают с AES.
И вот он, долгожданный бряк на 4B2808. В EAX адрес первого блока bookmarks.dat[4:20], в EDX адрес ключа sdkl239r20dk22k3. Внезапно хитрый китаец использовал префикс читабельной паролеобразной строки в качестве ключа AES. Подбираем режим, им оказывается, без особых сюрпризов, MODE_ECB. Размер из 4 первых байт файла bookmarks.dat, очевидно, длина расшифрованных данных. Пристально смотрим на них, начало "PK\x03\x04" неоднозначно намекает на ZIP. Внутри него лежит bm.dat, XML с ложкой cp1251 в бочке utf-8 (суровые ЧПУ 15-летней давности) и достаточно простой онтологией
_rootfolder
_folder @_title (0 или больше вложений)
_link @_created @_title @_url @_visited
Что же с самым большим bookmarks.dat, который не открывался, и у которого расшифрованный размер больше его самого? Наверно, побился хвост, но, к счастью, небольшой, всего 110 байт из 32 КБ. Откроем расшифрованный ZIP в популярном hex-редакторе и обнаружим, что единственная PK-запись размером 36 байт предшествует надкушенному стриму со сжатием deflate. Подсмотрим в zipfile.py, что для распаковки стрима можно воспользоваться zlib.decompressobj(-15).decompress(s). Поле frUncompressedSize равно 98781, но zlib достает только 98736 байт. 45 байт потеряны безвозвратно!
Чтобы смягчить горечь утраты, смотрим на конец файла.
<_link _title="Пётр Маковецкий. Смотри в корень!. Книги. Наука и техника" _url="http://n-t.ru/ri/mk/s
Эта ссылка жива и теперь. Можно продолжить запись по образцу соседних записей и соседних файлов.
k.htm" _created="1194??????"/></_rootfolder>\x0A
Ровно 45 байт. Ну хорошо, дата добавления закладки потеряна навсегда. Будем считать ее навеки ушедшим ноябрем 2007-го.
Если вдруг найдутся еще горемыки с меньшим опытом в CTF, вот скрипт:
from io import BytesIO
from Crypto.Cipher import AES
filename = 'z:\\bookmarks.dat'
f = open(filename, 'rb').read()
dec_len = struct.unpack('I', f[:4])[0]
key = 'sdkl239r20dk22k3'
zipped = AES.new(key, AES.MODE_ECB).decrypt(f[4:])[:dec_len]
frSignature, frVersion, frFlags, frCompression, frFileTime, frFileDate, frCrc, frCompressedSize, frUncompressedSize = struct.unpack(', zipped[:0x1A])
try:
z = zipfile.ZipFile(BytesIO(zipped))
for fileinfo in z.filelist:
unpacked = z.read(fileinfo)
open(filename + '.xml', 'wb').write(unpacked)
except Exception as e:
print repr(e)
print 'Recovering...'
decompressor = zlib.decompressobj(-15)
dec = decompressor.decompress(zipped[0x24:])
open(filename + '.xml', 'wb').write(dec)
print 'Lost %d bytes' % (frUncompressedSize - len(dec))
А в это время все прогрессивное человечество ждало от меня прорывов в нейронауке! Ну спасибо тебе, Anderson Che!
|
</> |