Статья ASM – работа с базами SQLite (часть 1. Формат файла)

Традиционно, для работы с СУБД используются скриптовые языки типа: Python, SQL, Tcl, Perl и прочие. Это вполне оправдано, поскольку их синтаксис максимально приближен к человеческой речи, а огромный набор рычагов и предметно-ориентированных модулей превращает решение вполне серьёзных проблем, чуть-ли не в игру. Единственным недостатком скриптов является скорость выполнения ими задач, т.к. в отличии от компилируемых программ они работают через интерпретатор – т.е. сначала построчный анализ текста, перевод его в байт-код, и лишь потом исполнение.

Однако бывают ситуации, когда с базами нужно совершить элементарные действия, например не создавать её с нуля забивая данными, а тупо прочитать пару-тройку столбцов уже имеющейся в ней информации. Это могут быть кукисы в браузерах, пароли и прочая инфа личного характера. Именно в таких случаях преимущество ассма (да и любого компилируемого языка) становится очевидным. Чтобы полностью исключить криминал, мы потренируемся строго на своей машине, не совершая атак на удалённые сервера. Здесь идеальным вариантом будет разбор встраиваемой базы SQLite – на ней и остановимся. Материал получился обширным, поэтому я разделил его на три части:


Содержание часть(1):
1. Оффтоп..
2. Внутренний формат базы-данных SQLite;
• практика – чтение заголовка базы;

Содержание часть(2):
• практика – вытаскиваем "cookies" браузеров;

Содержание часть(3):
4. Функции DPAPI из библиотеки crypt32.dll;
• практика – принцип шифрования паролей Chrome;

5. Постскриптум.
-------------------------------------------------------


1. Общие сведения

Ещё в 80-х годах прошлого столетия "человек-разумный" придумал СУБД, или систему управления базами данных. В обозримом будущем, схема свои позиции сдавать не собирается, а потому представляет интерес на всех уровнях. Огромные корпорации доверяют базам конфиденциальную информацию, а кража этих баз вторыми лицами – худшее, что только может случиться с компанией.

Всё-бы ничего, однако достоянием общественности становятся и личные сведения о нас с вами, а это уже настораживает. Базы почтовых серверов и социальных сетей с завидной периодичностью утекают из (якобы) защищённых периметров и админы здесь только пожимают плечами. В результате ошибок переполнения буферов, всевозможных дыр в системе безопасности и прочих уязвимостей, вся эта конструкция держится на честном слове. По сути мы, как конечные пользователи, в войне хакеров с админами выступаем в роли сторонних наблюдателей, которые лёжа на своём диване способны лишь обсуждать/осуждать.

Но мир не без добрых людей. Некоторые энтузиасты по собственной инициативе и на бесплатной основе пытаются изменить его к лучшему. Так, в августе 2000-го, некто Ричард Хипп (северная Каролина, США) анонсировал СУБД под названием SQLite. Он точил её напильником долгих 4-года, чтобы привести в надлежащий вид и представить мировому сообществу как стабильный релиз SQLite3. На данный момент последней считается v3.35.5 от 19 апреля 2021-года. То-есть проект жив, и активно развивается.

В отличие от многих других систем управления базами данных типа: MySql, Oracle и прочих, SQLite не является клиент-серверной базой данных – это обычное хранилище, которое встраивается в клиентское приложение, ..например: веб-браузеры, аудио/видео проигрыватели, графические редакторы, мобильные телефоны и т.п. В результате, получаем достаточно мощную, компактную и простую СУБД, а как-говорится "где мало сложности – там мало ложности".

На данном этапе сразу определимся с инструментами, чтобы больше не возвращаться к ним.
Есть огромное кол-во редакторов SQLite, но лично мне приглянулся "Expert Personal". Продуманный его интерфейс позволяет без особого труда и в пару кликов создавать, просматривать и редактировать базы-данных, а бонусом – в установочном пакете сразу идёт и свежая библиотека sqlite3.dll. Дело в том, что полноценная поддержка SQLite включена только начиная с Win-10, так-что обладателям более старых систем ХР,7,8 придётся качать эту либу из репозитория автора. Получив редактор, мы убиваем сразу двух зайцев – вот линки:

Сайт разработчика "SQLite Expert Personal":
Исходники и библиотека от автора СУБД:
Описание функций sqlite3.dll:
Навороченный HEX-редактор "010 Editor":


SQLite_Expert.png


010_Editor.png



2. Знакомство с базой SQLite3

SQLite имеет свой формат и обслуживается своей подсистемой. Передвижение запросов на выполнение той или иной операции назвали "транзакациями". Вся база-данных разбивается на страницы одинакового размера, где и хранится полезная нагрузка в виде двумерной матрицы строк и столбцов. Размер одной страницы "Page" варьируется в диапазоне от 512 до 65536 байт, а под максимальное кол-во страниц в одной базе отводится 32 бита = 4'294'967'294. При таких значениях, простой арифметикой можно получить допустимый размер базы-данных: 4'294'967'294 * 65'536 = 281'474'976'645'120, или 281 терабайт.


2.1.0. Режим[1] – Rollback, или "журнал отката" состояния

При таких гигантских объёмах в 1/4 петабайта, СУБД обязана гарантировать целостность своих данных, иначе в использовании баз просто теряется смысл. Поэтому, при операциях записи в таблицу, ядро SQLite сначала копирует содержимое модифицируемых страниц в т.н. "журнал отката", и только потом производит запись в основную базу. Журнал Rollback – это обычный дисковый файл, который всегда находится рядом с базой и имеет оригинальное имя, с добавлением суффикса --journal. Если транзакация на запись проходит успешно, то журнал удаляется, иначе ядро откатывает базу в прежнее состояние с сообщением об одной из следующих возможных ошибок:


Код:
SQLITE_OK          = 0x00    ;// Успешный результат
SQLITE_ERROR       = 0x01    ;// Ошибка или отсутствие базы данных
SQLITE_INTERNAL    = 0x02    ;// Внутренняя логическая ошибка
SQLITE_PERM        = 0x03    ;// В доступе отказано
SQLITE_ABORT       = 0x04    ;// процедура обратного вызова запросила прерывание
SQLITE_BUSY        = 0x05    ;// Файл базы данных заблокирован
SQLITE_LOCKED      = 0x06    ;// Таблица в базе данных заблокирована
SQLITE_NOMEM       = 0x07    ;// Ошибка malloc()
SQLITE_READONLY    = 0x08    ;// Попытка записи в базу данных только для чтения
SQLITE_INTERRUPT   = 0x09    ;// Операция завершена sqlite3_interrupt()
SQLITE_IOERR       = 0x0A    ;// Произошла ошибка дискового ввода-вывода
SQLITE_CORRUPT     = 0x0B    ;// Образ диска базы данных искажен
SQLITE_NOTFOUND    = 0x0C    ;// Неизвестный код операции в sqlite3_file_control()
SQLITE_FULL        = 0x0D    ;// Вставка не удалась, потому что база данных заполнена
SQLITE_CANTOPEN    = 0x0E    ;// Невозможно открыть файл базы данных
SQLITE_PROTOCOL    = 0x0F    ;// Ошибка протокола блокировки базы данных
SQLITE_EMPTY       = 0x10    ;// База данных пуста
SQLITE_SCHEMA      = 0x11    ;// Схема базы данных изменена
SQLITE_TOOBIG      = 0x12    ;// Строка или BLOB превышает ограничение на размер
SQLITE_CONSTRAINT  = 0x13    ;// Прерывание из-за нарушения ограничения
SQLITE_MISMATCH    = 0x14    ;// Несоответствие типов данных
SQLITE_MISUSE      = 0x15    ;// Некорректное использование библиотеки
SQLITE_NOLFS       = 0x16    ;// Использует функции ОС, не поддерживаемые на хосте
SQLITE_AUTH        = 0x17    ;// В авторизации отказано
SQLITE_FORMAT      = 0x18    ;// Ошибка формата вспомогательной базы данных
SQLITE_RANGE       = 0x19    ;// 2-й параметр sqlite3_bind вне допустимого диапазона
SQLITE_NOTADB      = 0x1A    ;// Открыт файл, не являющийся файлом базы данных
SQLITE_ROW         = 0x64    ;// sqlite3_step() подготовил еще одну строку
SQLITE_DONE        = 0x65    ;// sqlite3_step() завершил выполнение

Отметим, что откат происходит прозрачно для пользователя, а вся ответственность ложится на неэкспортируемую служебную процедуру, которую запускает т.н. "триггер". В зависимости от конфигурации базы, триггеры могут вызываться как для каждой операции записи, так и один раз на глобальное изменение базы (в последнем случае его называют "табличным").


2.1.1. Режим[2] – WAL, или "журнал упреждающей записи" (Write-Ahead Logging)

Традиционный откат после неудачных попыток записи подразумевает сохранение резервных копий исходных страниц в отдельный файл, и восстановлении их в случае сбоя. В более новых версиях SQLite ввели ещё один режим под названием "упреждающая запись" WAL. Этот альтернативный режим представляет собой инверсный вариант обычного отката. Теперь, запись производится не напрямую в базу, а наоборот во-временный файл с суффиксом -–wal. Это напоминает отложенную запись в кэш процессора WriteBack, не в пример сквозной записи в память WriteThrought.

Размер временного WAL-файла на диске как-правило 1000 страниц, а транзакацию сброса их в базу назвали "контрольной точкой". Таким образом, в традиционном откате имеем две примитивные операции "чтение/запись", а в усовершенствованном WAL добавляется ещё одна операция "сброс" (контрольная точка). Обновление исходной базы страницами WAL может происходить или по запросу пользователя, или-же автоматически, при переполнении временного файла.


2.1.2. VACUUM – дефрагментация базы-данных

Ещё одним немаловажным моментом является поддержание базы в компактном/сжатом состоянии. Для этих целей, в доспехах SQLite припрятан механизм под названием "Vacuum". Суть его в том, что если мы удаляем строки из базы, то информация фактически остаётся на своём месте, а соответствующая строка просто помечается свободной. В результате, в таблице появляются паразитные (вакуумные) строки, которые не несут абсолютно никакой полезной инфы, зато занимают пространство и размер базы.

Команда "VACUUM" очищает основную базу путём копирования её содержимого во временный файл, и перезагрузки исходной базы из этой копии. При этом механизм удаляет пустые страницы, выравнивает данные (делает их смежными) и назначает каждой строке новый порядковый номер RowID. Ядро отвергнет команду, если есть активная транзакция.

Кстати это типичная ситуация в базах "cookies" браузеров. Как-правило, время жизни кукисов определяет значение в столбце "expires_utc" (time counter), по истечении которого "правильный" кукис авто-уничтожается системой. В итоге, если посмотреть на такую базу в редакторе, она будет напоминать зубы хоккеиста, как в примере ниже:


Vacuum.png



2.1.3. Cache Page – кэширование страниц

Подсистема SQLite может иметь несколько страниц для кэша дискового ввода-вывода. Рассмотрим ситуацию, когда мы изменили несколько строк в таблице и дали команду на их сохранение. При этом, если транзакацию на запись ядро выполнит сразу, то это займёт много времени, и мы рискуем получить отклик через пару секунд. Запись на жёсткий диск довольно длительная операция, и лучше подкопить все модифицированные данные сначала в буферной памяти (а точнее в странице кэш), и сохранять их на диск в какой-нибудь момент бездействия. Такой алгоритм практикуют все версии операционных систем Windows при файловых операциях ввода-вывода, и подсистема SQLite не исключение, хотя на практике применяет редко.


2.1.4. Формат схемы базы-данных

В настоящее время определены 4 разновидности формата схемы:


1. Legacy (проприетарный) – понимается всеми версиями SQLite, до v3.0.0 (18.06.2004).
2. Добавляет фишку, когда строки в одной таблице могут иметь различное кол-во столбцов (v3.1.3).
3. Незначительные усовершенствования формата(2).
4. Введено ключевое слово "DESC" в объявлениях индексов, которое позволяет сортировать таблицу или в порядке возрастания "ASC" (ascending), или убывания "DESC" (descending). Обратная сортировка не поддерживалась в схемах(1..3). Все современные базы-данных SQLite в дефолте используют формат(4).


2.1.5. Заголовок "Header" базы

Любая база-данных SQLite3 начинается с заголовка "Header", который занимает первые 100 байт (см. скрин 010-Editor выше). В нём указываются фундаментальные сведения о базе, её схема, кол-во и размер страниц, счётчик изменений таблицы и т.д.

Отличительной особенностью заголовка является прямой порядок байт, что придаёт ему нетрадиционную форму черепа. Например, числа в оперативной памяти ОЗУ наших компьютеров с процессорами х86/64 хранятся младшим байтом вперёд – такой порядок называют ещё "Little-Endial" и число 00112233h будет представлено в ячейках памяти как 33.22.11.00 hex. В заголовке-же базы SQLite нужно воспринимать их в привычном нам виде слева-направо, т.е. "Big-Endian". Отметим, что для таких случаев в системе-команд процессоров Intel/AMD имеется специальная инструкция bswap, которая отражает порядок байт в числе
(Byte Swap).

Header.png


В таблице выше представлены поля заголовка, а для удобства их программного чтения, мы создадим одноименную структуру такого содержания:

C-подобный:
struct SQLITE_HEADER          ;// <----- https://sqlite.org/fileformat2.html
  Magic            dq  0,0    ;// 16-байтная строка "SQLite format 3"
  PageSize         dw  0      ;// размер страницы в байтах
  WriteWAL         db  0      ;// способ обработки ошибок записи: 1 = откат, 2 = WAL
  ReadWAL          db  0      ;// способ обработки ошибок чтения: 1 = откат, 2 = WAL
  PageReserved     db  0      ;// всего резервных страниц
  Reserved1        db  0,0,0  ;// устарело..
  WriteCount       dd  0      ;// кол-во изменений базы
  TotalPage        dd  0      ;// размер базы в страницах -1
  FreePageOffset   dd  0      ;// линк на первую свободную
  TotalFreePage    dd  0      ;// всего свободных
  CookieFile       dd  0      ;// счётчик изменений формата схемы
  SchemeFormat     dd  0      ;// формат схемы базы
  CachePageSize    dd  0      ;// размер страницы кэш для дискового в/в
  VacuumRootPage   dd  0      ;// первая страница вакуума
  BaseCP           dd  0      ;// кодировка: 1=UTF8, 2=UTF16_le, 3=UTF16_be (big-endian)
  Reserved2        dd  0      ;//
  IncVacuum        dd  0      ;// инкрементный вакуум включён =1, или выключен =0
  AppId            dd  0      ;// ID создавшего базу приложения
  Reserved3        db  20 dup(0)
  SqliteVer        dd  0      ;// счётчик версий
  SqliteVerNum     dd  0      ;// значение номера версии
ends


2.2. Практика – чтение заголовка базы

Чтобы удовлетворить свой интерес, можно написать небольшой парсер заголовка базы-данных.
Если посмотреть на структуру хидера выше, то в самом его хвосте можно обнаружить поле "SqliteVerNum" с версией библиотеки sqlite3.dll того приложения, которое последним имела доступ к данной базе. Счётчик в предпоследнем поле считает эти изменения версий.

Идентификатор библиотеки указывается в виде 32-битного значения, и чтобы привести его в приятный для восприятия вид типа "v3.35.5", я написал отдельную процедуру. На первом этапе она делит значение поля на 1.000.000 (так мы получим версию 3), потом остаток на 1.000 (обновление, в данном случае 35) и всё-что останется – будет сборкой типа 5. Такой алго расшифровки данного поля рекомендует сам автор SQLite, Ричард Хипп.

На входе, программа ниже запрашивает имя файла базы-данных, поэтому нужно предварительно скопировать её от куда-нибудь. Как вариант, источниками могут послужить базы вашего интернет-браузера. Но у каждого он свой, так-что выкладывают лист возможных директорий для поиска.


C-подобный:
# Path list for chromium_browsers
# =================================
 [
    (u'7Star',            u'{LOCALAPPDATA}\7Star\7Star\User Data'),
    (u'amigo',            u'{LOCALAPPDATA}\Amigo\User Data'),
    (u'brave',            u'{LOCALAPPDATA}\BraveSoftware\Brave-Browser\User Data'),
    (u'centbrowser',      u'{LOCALAPPDATA}\CentBrowser\User Data'),
    (u'chedot',           u'{LOCALAPPDATA}\Chedot\User Data'),
    (u'chrome canary',    u'{LOCALAPPDATA}\Google\Chrome SxS\User Data'),
    (u'chromium',         u'{LOCALAPPDATA}\Chromium\User Data'),
    (u'chromium edge',    u'{LOCALAPPDATA}\Microsoft\Edge\User Data'),
    (u'coccoc',           u'{LOCALAPPDATA}\CocCoc\Browser\User Data'),
    (u'comodo dragon',    u'{LOCALAPPDATA}\Comodo\Dragon\User Data'),
    (u'elements browser', u'{LOCALAPPDATA}\Elements Browser\User Data'),
    (u'epic browser',     u'{LOCALAPPDATA}\Epic Privacy Browser\User Data'),
    (u'google chrome',    u'{LOCALAPPDATA}\Google\Chrome\User Data'),
    (u'kometa',           u'{LOCALAPPDATA}\Kometa\User Data'),
    (u'opera',            u'{APPDATA}\Opera Software\Opera Stable'),
    (u'orbitum',          u'{LOCALAPPDATA}\Orbitum\User Data'),
    (u'sputnik',          u'{LOCALAPPDATA}\Sputnik\Sputnik\User Data'),
    (u'torch',            u'{LOCALAPPDATA}\Torch\User Data'),
    (u'uran',             u'{LOCALAPPDATA}\uCozMedia\Uran\User Data'),
    (u'vivaldi',          u'{LOCALAPPDATA}\Vivaldi\User Data'),
    (u'yandexBrowser',    u'{LOCALAPPDATA}\Yandex\YandexBrowser\User Data')
]

# Firefox_browsers = [
    (u'firefox',   u'{APPDATA}\Mozilla\Firefox'),
    (u'blackHawk', u'{APPDATA}\NETGATE Technologies\BlackHawk'),
    (u'cyberfox',  u'{APPDATA}\8pecxstudios\Cyberfox'),
    (u'comodo',    u'{APPDATA}\Comodo\IceDragon'),
    (u'k-Meleon',  u'{APPDATA}\K-Meleon'),
    (u'icecat',    u'{APPDATA}\Mozilla\icecat'),
]

Открыв папку и выбрав в ней очередной файл, нажмите в тотале комбинацию [Ctrl+Q] – если файл начинается с сигнатуры "SQLite format 3", значит это точно база. Вот исходник парсера её заголовка с некоторыми комментариями:

C-подобный:
format pe console
include 'win32ax.inc'
include 'equates\sqlite3.inc'
entry start
;//------------
.data
fName       rb   128
buff        db   0
;//------------
.code
start:   invoke  SetConsoleTitle,<'*** SQLite3 Header info ***',0>

;//----- Запрашиваем имя файла
        cinvoke  printf,<10,' File name............:  ',0>
        cinvoke  scanf,<'%s',0>,fName

;//----- Пробуем открыть его
         invoke  _lopen,fName,0
         or      eax,eax               ;// ошибка?
         jns     @next
        cinvoke  printf,<10,' ERROR! File not found.',0>
         jmp     @exit
@next:   push    eax
         invoke  _lread,eax,buff,100   ;// читаем заголовок из базы в буфер
         pop     eax
         invoke  _lclose,eax           ;// закрыть файл базы

;//----- Проверим на принадлежность заголовка к SQLite
         call    @f
         db     'SQLite format 3'      ;// сигнатура,
@@:      pop     edi                   ;//   ..в регистре EDI
         mov     esi,buff              ;// где искать
         mov     ecx,15                ;// длина строки
         repe    cmpsb                 ;// сравнить!
         jecxz   @ok
        cinvoke  printf,<10,' ERROR! This is not SQLite3 database.',0>
         jmp     @exit

;//----- Заголовок корректный - выводим инфу..
@ok:    cinvoke  printf,<' Ok! Magic string.....:  %s',10,10,0>,buff  ;//<---- сигнатура заголовка

        cinvoke  printf,<10,' Data base info ***',0>
         mov     esi,buff
         mov     eax,[esi+SQLITE_HEADER.SqliteVerNum]
         bswap   eax
         call    GetSqlVersion
        cinvoke  printf,<10,'    Version...........:  %d.%d.%d',0>,eax,ebx,ecx

         mov     esi,buff
         mov     eax,[esi+SQLITE_HEADER.BaseCP]
         bswap   eax
         shl     eax,3
         mov     ebx,[esi+SQLITE_HEADER.SchemeFormat]
         bswap   ebx
         mov     ecx,[esi+SQLITE_HEADER.WriteCount]
         bswap   ecx
         mov     edx,[esi+SQLITE_HEADER.AppId]
         bswap   edx
        cinvoke  printf,<10,'    Text code-page....:  UTF-%d',\
                         10,'    Schema format.....:  %d',\
                         10,'    Modified counter..:  %d times',\
                         10,'    Application ID....:  %d',10,0>,eax,ebx,ecx,edx

;//--------------
        cinvoke  printf,<10,' Pages structure ***',0>
         mov     esi,buff
         movzx   ebp,[esi+SQLITE_HEADER.PageSize]
         rol     bp,8
         push    ebp
         mov     ebx,[esi+SQLITE_HEADER.TotalPage]
         bswap   ebx
         mov     ecx,[esi+SQLITE_HEADER.TotalFreePage]
         bswap   ecx
         pop     eax
         mul     ebx
         mov     edx,[esi+SQLITE_HEADER.FreePageOffset]
         bswap   edx
        cinvoke  printf,<10,'    Page size.........:  %d byte',\
                         10,'    Total pages.......:  %d',\
                         10,'    Database size.....:  %d byte',\
                         10,'    Free page offset..:  %d',\
                         10,'    Total free pages..:  %d',10,0>,ebp,ebx,eax,edx,ecx

;//--------------
        cinvoke  printf,<10,' Extended info *****',0>
         mov     esi,buff
         mov     eax,[esi+SQLITE_HEADER.CookieFile]
         bswap   eax
         mov     ebx,[esi+SQLITE_HEADER.CachePageSize]
         bswap   ebx
         movzx   ecx,[esi+SQLITE_HEADER.WriteWAL]
         bswap   ebx
         movzx   edx,[esi+SQLITE_HEADER.ReadWAL]
         bswap   ebx
        cinvoke  printf,<10,'    Schema modified...:  %d times',\
                         10,'    Cache page size...:  %d  byte',\
                         10,'    Write error mode..:  %d  (1 = Rollback, 2 = WAL)',\
                         10,'    Read  error mode..:  %d  (1 = Rollback, 2 = WAL)',0>,\
                         eax,ebx,ecx,edx

         mov     esi,buff
         mov     eax,[esi+SQLITE_HEADER.IncVacuum]
         bswap   eax
         mov     ebx,[esi+SQLITE_HEADER.VacuumRootPage]
         bswap   ebx
        cinvoke  printf,<10,'    Increment vacuum..:  %d',\
                         10,'    Vacuum page offset:  %d',0>,eax,ebx

@exit:  cinvoke  getch
        cinvoke  exit,0
;//------------
GetSqlVersion:
         xor     edx,edx
         mov     ebx,1000000
         div     ebx
         push    eax
         xchg    eax,edx
         mov     ebx,1000
         xor     edx,edx
         div     ebx
         xchg    ecx,edx
         xchg    ebx,eax
         pop     eax
ret
;//------------
section '.idata' import data readable
library  kernel32,'kernel32.dll',msvcrt,'msvcrt.dll'
import   msvcrt,printf,'printf',scanf,'scanf',getch,'_getch',exit,'exit'
include  'api\kernel32.inc'

Header_Prog.png



В следующей части рассмотрим функции библиотеки sqlite3.dll, способ работы с ними, типы запросов, ну и прочее в этом духе. В практической части попытаемся считать куки браузеров, что они представляют собой, и какого типа хранят информацию. В скрепку кладу инклуд "sqlite3.inc" и исполняемый файл приведённого выше кода. До скорого, пока!
 

Вложения

  • Sqlite_Ver.zip
    4,4 КБ · Просмотры: 323
Последнее редактирование:

rulet1337

One Level
14.05.2020
3
4
BIT
0
Marylin ты волшебник... преклоняюсь перед тобой великий и ужасный! )) Спасибо огромное, как только возникает какая-то тема, которую хрен нагуглишь, а у тебя уже целая статья есть по ней ))
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!