Отслеживание SQL-запросов от приложения к файлу SQLite базы данных

К примеру, есть программа, из которой необходимо экспортировать какие-то данные. Это может быть, например каталог товаров конкурента, база вопросов для тестирования и пр.
Обычно разработчики дают файлам с данными вполне осмысленные имена, и найти их несложно (расширение файла значения не имеет, важен внутренний формат). Здесь я рассмотрю вариант с базой данных на SQLite. Выбор среди расширений для защиты SQLite баз данных небольшой. К тому же некоторые из них коммерческие. Компания Hwaci, Inc., отвечающая за разработку SQLite предлагает пару таких коммерческих расширений:
  • SQLite EncryptionExtension (SEE) шифрует БД по мере того как она пишется на диск, по-видимому эффективная штука.
  • Compressed and Encrypted Read-Only Database (CEROD) это расширение пошло дальше и не только шифрует БД, но еще и обеспечивает сжатие. Сжатие естественно уменьшает занимаемое базой данных место, но есть побочный эффект база данных становится read-only. Предлагают использовать при распространении лицензионных материалов.
Первый шаг определить зашифрована БД или нет. При моем первом знакомстве с SQLite я использовал программу SQLabs SQLiteManager. А эта программа иногда при открытии БД показывала сообщение «Database file seems to be encrypted. Please enter the encryption key». Казалось, что БД защищена каким-нибудь SEE или CEROD. Однако БД нормально открывалась стандартным sqlite3.exe или другой тулзой например SQLite Administrator. Далее если схема БД нетривиальная, то разобраться что к чему довольно сложно. Или названия таблиц и полей могут быть сокращены до неузнаваемости. В таких случаях, хорошо бы посмотреть на SQL-запросы, которые программа делает к БД. Способ, который далее описывается, работает, только если SQLite представлен в программе в виде внешней библиотеки (sqlite3.dll). Идея в том чтобы скомпилировать свой SQLite с логированием запросов и подменить им тот, который используется программой. Я буду использовать компилятор Visual C++ 2010, но в принципе это неважно. 1) Открываем MSVC и создаем новый Win32 Project. В Application Wizard выбираем DLL и ставим галочку "Empty project". 2) Добавим в проект файлы sqlite3.h и sqlite3.c из исходников SQLite. 3) Также скопируем в папку с проектом файл sqlite3.def из дистрибутива SQLite. .def файл можно сгенерировать из файла sqlite3.dll с помощью утилиты dumpbin. 4) В свойствах проекта, добавим sqlite3.def в Linker -> Input -> Module definition file. 5) Также в С/С++ -> Preprocessor -> Preprocessor Definitions можно добавить:
  • WIN32
  • _DEBUG
  • _WINDOWS
  • _USRDLL
  • SQLITE3_EXPORTS
  • SQLITE_ENABLE_RTREE
  • SQLITE_ENABLE_COLUMN_METADATA
  • SQLITE_ENABLE_ATOMIC_WRITE
  • SQLITE_ENABLE_FTS3
  • SQLITE_ENABLE_FTS3_PARENTHESIS
  • SQLITE_ENABLE_FTS4
  • SQLITE_ENABLE_IOTRACE
  • SQLITE_ENABLE_LOCKING_STYLE
  • SQLITE_ENABLE_MEMORY_MANAGEMENT
  • SQLITE_ENABLE_MEMSYS3
  • SQLITE_ENABLE_MEMSYS5
  • SQLITE_ENABLE_STAT2
  • SQLITE_ENABLE_STAT3
  • SQLITE_ENABLE_UNLOCK_NOTIFY
  • SQLITE_ENABLE_UPDATE_DELETE_LIMIT
6) Изменим код в файле sqlite3.c, тут:

SQLITE_API int sqlite3_prepare_v2(
  sqlite3 *db,              /* Database handle. */
  const char *zSql,         /* UTF-8 encoded SQL statement. */
  int nBytes,               /* Length of zSql in bytes. */
  sqlite3_stmt **ppStmt,    /* OUT: A pointer to the prepared statement */
  const char **pzTail       /* OUT: End of parsed string */
){
  int rc;
  
  FILE *fp;
  fp = fopen("c:\\sqlite3_prepare_v2.txt","a+");
  fprintf(fp, "%s\n\n", zSql);
  fclose(fp); 
  
  rc = sqlite3LockAndPrepare(db,zSql,nBytes,1,0,ppStmt,pzTail);
  assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 );  /* VERIFY: F13021 */
  return rc;
}

и тут:

SQLITE_API int sqlite3_exec(
  sqlite3 *db,                /* The database on which the SQL executes */
  const char *zSql,           /* The SQL to be executed */
  sqlite3_callback xCallback, /* Invoke this callback routine */
  void *pArg,                 /* First argument to xCallback() */
  char **pzErrMsg             /* Write error messages here */
){
  int rc = SQLITE_OK;         /* Return code */
  const char *zLeftover;      /* Tail of unprocessed SQL */
  sqlite3_stmt *pStmt = 0;    /* The current SQL statement */
  char **azCols = 0;          /* Names of result columns */
  int nRetry = 0;             /* Number of retry attempts */
  int callbackIsInit;         /* True if callback data is initialized */
  
  FILE *fp;
  fp = fopen("c:\\sqlite3_exec.txt","a+");
  fprintf(fp, "%s\n\n", zSql);
  fclose(fp);

7) Теперь остается сделать билд (Ctrl + Shift + B) и подменить оригинальный sqlite3.dll используемый программой нашим новым sqlite3.dll. Далее, когда программа будет обращаться к БД через эти функции то все запросы будут попадать в лог. После получения SQL запросов можно увидеть что-нибудь типа:
 
SELECT customFunction(); 

что означает использование самописных расширений. Они могут быть вшиты в программу или быть "загружаемыми" (loadable) DLL. Загружаемое расширение можно загрузить в свою программу с помощью функции:

int sqlite3_load_extension( sqlite3 *db, const char *ext_name, const char *entry_point, char **error )