История про Avant и его bookmarks.dat

Sep 18, 2022 00:05

Почти с появления у меня интернета в 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 байт потеряны безвозвратно!

Чтобы смягчить горечь утраты, смотрим на конец файла.

\x0A

Ровно 45 байт. Ну хорошо, дата добавления закладки потеряна навсегда. Будем считать ее навеки ушедшим ноябрем 2007-го.

Если вдруг найдутся еще горемыки с меньшим опытом в CTF, вот скрипт:

import struct, zipfile, zlib
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!

software

Previous post Next post
Up