Изображение


Содержание


1. Введение: Мобильный браузер как ключевой источник криминалистических данных
2. Архитектура хранения данных мобильных браузеров
3. Криминалистически безопасное извлечение данных браузера
4. Google Chrome на Android: полная карта артефактов
5. Анализ SQLite-баз Chrome: History, Cookies, Web Data
6. Yandex Browser на Android: особенности и артефакты
7. Safari на iOS: структура данных и методы извлечения
8. Анализ Property List файлов Safari (iOS)
9. Восстановление удалённых данных из WAL-журналов SQLite
10. Облачная синхронизация: Google Sync, Yandex Sync, iCloud
11. Инструменты криминалистического анализа мобильных браузеров
12. Построение хронологии событий по данным браузера
13. Продвинутые техники: кэш, сессии и токены аутентификации
14. Сравнительный анализ: Chrome vs Yandex vs Safari
15. Оформление результатов для судебного заключения
16. Часто задаваемые вопросы (FAQ)
17. Заключение: Мобильная браузерная криминалистика в 2026 году


Введение: Мобильный браузер как ключевой источник криминалистических данных


Смартфон сегодня — это персональный компьютер, который всегда рядом с пользователем. И центральное место в цифровой жизни большинства людей занимает браузер: именно через него совершаются покупки, ведётся переписка через веб-интерфейсы, осуществляется поиск информации, происходит авторизация в сервисах. По данным StatCounter, в 2025 году более 58% мирового веб-трафика приходится на мобильные устройства — и эта доля продолжает расти.

Для специалиста по цифровой криминалистике мобильный браузер представляет собой исключительно богатый источник артефактов. В отличие от настольных систем, мобильные браузеры, как правило, используются без режима инкогнито большую часть времени — пользователи реже задумываются о приватности на телефоне. Это означает, что следы реальной активности накапливаются систематически и могут охватывать месяцы или годы.

Объём и разнообразие хранимых данных часто удивляют даже опытных криминалистов. История посещений — очевидный артефакт, известный всем. Но браузер также хранит: точные временны́е метки каждого посещения с миллисекундной точностью, количество посещений каждого URL (visit_count), переходы между страницами (referrer chains), сохранённые данные форм включая частично введённые, куки сессий и аутентификации, данные об автозаполнении (логины, адреса, платёжные данные), кэшированные версии страниц, данные сервис-воркеров и web storage, историю загрузок с локальными путями к файлам.

В 2026 году три браузера занимают доминирующее положение на российском мобильном рынке. Google Chrome является браузером по умолчанию на большинстве Android-устройств и сохраняет лидерство по общей установленной базе. Yandex Browser занимает значительную долю именно в российском сегменте — по данным Яндекса, аудитория мобильной версии превышает 40 миллионов пользователей. Safari является единственным полнофункциональным браузером для iOS/iPadOS и имеет особый статус в части доступа к движку WebKit.

Это руководство охватывает полную методологию криминалистического исследования этих трёх браузеров: от технических основ структуры хранения данных до практических SQL-запросов для извлечения конкретных категорий артефактов и построения хронологии событий.

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



Архитектура хранения данных мобильных браузеров


Понимание архитектуры хранения данных — фундамент грамотного криминалистического анализа. Разные браузеры используют разные форматы, разные схемы баз данных и разные механизмы синхронизации — что определяет стратегию извлечения и анализа.

SQLite как универсальный формат хранения

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

С криминалистической точки зрения SQLite имеет несколько важных свойств. Журнал опережающей записи (WAL — Write-Ahead Log) содержит незафиксированные и недавно удалённые транзакции — это ценный источник для восстановления удалённых данных. Свободные страницы (freelist) в файле базы данных могут содержать фрагменты ранее удалённых записей. Временны́е метки в SQLite хранятся как Unix timestamp (секунды или микросекунды с 1 января 1970) или в формате WebKit (микросекунды с 1 января 1601) — в зависимости от браузера и таблицы.

Property List (plist) — формат iOS

Safari и другие iOS-приложения активно используют Property List (.plist) — формат сериализации данных от Apple. Существует два варианта: XML plist (читаемый текстовый формат) и Binary plist (bplist00 — бинарный формат для экономии места). Бинарные plist требуют конвертации перед анализом — инструментами plutil, plistutil или соответствующими Python-библиотеками.

Кэш браузеров: структура и содержимое

Веб-кэш хранит загруженные ресурсы для ускорения повторных загрузок. В Chrome и Yandex Browser на Android кэш организован в специальной директории с индексным файлом и блоками данных. Кэш содержит HTML-страницы, изображения, JavaScript и CSS — что позволяет восстановить содержимое посещённых страниц даже после их удаления с серверов.

В Safari на iOS кэш хранится в SQLite-базе Cache.db с таблицами cfurl_cache_response и cfurl_cache_blob_data.

Структура песочницы приложений

На обеих платформах браузеры работают в изолированной «песочнице» (sandbox). На Android данные Chrome хранятся в `/data/data/com.android.chrome/` — доступ без root-прав ограничен. На iOS данные Safari хранятся в `/private/var/mobile/Library/` и связанных директориях — доступ требует полного физического извлечения или резервной копии iTunes/Finder.



Криминалистически безопасное извлечение данных браузера


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

Уровни извлечения данных с мобильных устройств

Криминалистическое извлечение данных с мобильных устройств осуществляется на нескольких уровнях, от наименее до наиболее инвазивного.

Логическое извлечение — получение данных через стандартные интерфейсы устройства (ADB для Android, iTunes backup для iOS). Не требует root/jailbreak, быстро, но даёт ограниченный набор данных. Браузерные данные в логическом извлечении часто неполны.

Расширенное логическое извлечение (ADB backup / iTunes encrypted backup) — зашифрованная резервная копия содержит больше данных, включая keychain (iOS) и некоторые защищённые данные приложений. Для iOS-резервной копии с паролем — всё содержимое keychain расшифровывается.

Физическое извлечение — побитовый образ всего хранилища устройства. Максимальный объём данных включая удалённые записи, неаллоцированное пространство. Требует root (Android) или соответствующих инструментов (Cellebrite, Oxygen, UFED).

Файловая система — извлечение всей файловой системы через специализированные инструменты или эксплойты. Золотая середина между логическим и физическим.

Процедура снятия образа и документирование

До начала работы с устройством:

bash
<h2 id="android-fiksatsiya-sostoyaniya-ustroystva-pered-nachalom-raboty">Android: фиксация состояния устройства перед началом работы</h2>
adb shell getprop ro.product.model
adb shell getprop ro.build.version.release
adb shell date
adb shell uptime

<h2 id="sozdat-kriminalisticheski-korrektnyy-obraz-cherez-adb-esli-est-root">Создать криминалистически корректный образ через ADB (если есть root)</h2>
adb shell "su -c 'dd if=/dev/block/dm-0 bs=4096'" | \
tee /evidence/android_image.img | \
sha256sum > /evidence/android_image.sha256

<h2 id="dlya-faylovoy-sistemy-dannye-prilozheniy">Для файловой системы (данные приложений)</h2>
adb shell "su -c 'tar czf - /data/data/com.android.chrome/'" > \
/evidence/chrome_data.tar.gz
sha256sum /evidence/chrome_data.tar.gz | tee /evidence/chrome_data.sha256


Для iOS через libimobiledevice:

bash
<h2 id="sozdat-zashifrovannuyu-rezervnuyu-kopiyu-soderzhit-bolshe-dannyh">Создать зашифрованную резервную копию (содержит больше данных)</h2>
idevicebackup2 -u DEVICE_UDID backup --full /evidence/ios_backup/

<h2 id="poluchit-informatsiyu-ob-ustroystve">Получить информацию об устройстве</h2>
ideviceinfo -u DEVICE_UDID | grep -E "ProductType|ProductVersion|SerialNumber|UniqueDeviceID"

<h2 id="vychislit-heshi-dlya-aktualnoy-rezervnoy-kopii">Вычислить хэши для актуальной резервной копии</h2>
find /evidence/ios_backup/ -type f -exec sha256sum {} \; > /evidence/ios_backup_hashes.txt




Google Chrome на Android: полная карта артефактов


Chrome на Android хранит данные в директории приложения. Без root-прав доступ к этим данным возможен только через ADB-бэкап (с ограничениями) или при наличии физического образа.

Основные пути к файлам Chrome

Базовая директория: `/data/data/com.android.chrome/`

text
/data/data/com.android.chrome/
├── app_chrome/
│ └── Default/ ← Основной профиль пользователя
│ ├── History ← SQLite: история посещений + поиск
│ ├── History-journal ← WAL-журнал History
│ ├── Cookies ← SQLite: все куки браузера
│ ├── Cookies-journal ← WAL-журнал Cookies
│ ├── Web Data ← SQLite: автозаполнение, адреса, карты
│ ├── Web Data-journal
│ ├── Login Data ← SQLite: сохранённые пароли (зашифрованы)
│ ├── Login Data For Account ← Пароли из Google аккаунта
│ ├── Bookmarks ← JSON: закладки
│ ├── Preferences ← JSON: настройки профиля
│ ├── Favicons ← SQLite: иконки сайтов
│ ├── Top Sites ← SQLite: часто посещаемые сайты
│ ├── Visited Links ← Бинарный: посещённые ссылки (Bloom filter)
│ ├── Network Action Predictor ← SQLite: предсказатель URL
│ ├── Shortcuts ← SQLite: shortcuts для омнибара
│ ├── Extension Cookies ← Куки расширений
│ ├── Local Storage/ ← Директория: localStorage данные
│ │ └── leveldb/ ← LevelDB формат
│ ├── Session Storage/ ← SessionStorage (не персистентен)
│ ├── IndexedDB/ ← IndexedDB базы данных сайтов
│ ├── databases/ ← Web SQL Database (устаревший)
│ ├── Cache/ ← Директория кэша
│ │ ├── Cache_Data/
│ │ └── index ← Индекс кэша
│ ├── GPUCache/ ← GPU шейдерный кэш
│ ├── Download Service/ ← История загрузок
│ └── shared_proto_db/ ← Protocol Buffers данные
├── files/
├── shared_prefs/ ← XML настройки приложения
│ ├── com.google.android.apps.chrome_preferences.xml
│ └── ChromePreferences.xml
└── databases/ ← Дополнительные SQLite базы


Схемы ключевых таблиц Chrome

Понимание схем таблиц необходимо для правильного написания запросов и интерпретации данных.

Файл History — содержит несколько взаимосвязанных таблиц:

sql
-- Таблица urls: все URL когда-либо посещённые
-- Просмотр схемы
.schema urls
-- Результат:
CREATE TABLE urls (
id INTEGER PRIMARY KEY,
url TEXT NOT NULL, -- Полный URL
title TEXT DEFAULT '', -- Заголовок страницы
visit_count INTEGER DEFAULT 0 NOT NULL, -- Счётчик посещений
typed_count INTEGER DEFAULT 0 NOT NULL, -- Сколько раз URL набирался вручную
last_visit_time INTEGER NOT NULL, -- WebKit timestamp последнего посещения
hidden INTEGER DEFAULT 0 NOT NULL -- 1 = скрыт из истории
);

-- Таблица visits: каждый отдельный визит
CREATE TABLE visits (
id INTEGER PRIMARY KEY,
url INTEGER NOT NULL, -- FK на urls.id
visit_time INTEGER NOT NULL, -- WebKit timestamp визита
from_visit INTEGER DEFAULT 0, -- FK на visits.id (откуда пришли)
transition INTEGER DEFAULT 0 NOT NULL, -- Тип перехода
segment_id INTEGER DEFAULT 0,
visit_duration INTEGER DEFAULT 0 -- Длительность в микросекундах
);
-- transition: битовая маска
-- 0 = LINK (клик по ссылке)
-- 1 = TYPED (набран в адресной строке)
-- 2 = AUTO_BOOKMARK
-- 3 = AUTO_SUBFRAME
-- 4 = MANUAL_SUBFRAME
-- 5 = GENERATED (из поиска)

-- Таблица downloads: история загрузок
CREATE TABLE downloads (
id INTEGER PRIMARY KEY,
guid TEXT NOT NULL,
current_path TEXT NOT NULL, -- Локальный путь к файлу
target_path TEXT NOT NULL, -- Изначальный целевой путь
start_time INTEGER NOT NULL, -- WebKit timestamp начала
received_bytes INTEGER NOT NULL,
total_bytes INTEGER NOT NULL,
state INTEGER NOT NULL, -- 1=IN_PROGRESS, 4=COMPLETE, 5=CANCELLED
danger_type INTEGER NOT NULL,
interrupt_reason INTEGER NOT NULL,
end_time INTEGER NOT NULL,
opened INTEGER NOT NULL, -- Был ли файл открыт
referrer TEXT NOT NULL, -- URL страницы-источника
by_ext_id TEXT NOT NULL,
by_ext_name TEXT NOT NULL,
etag TEXT NOT NULL,
last_modified TEXT NOT NULL,
mime_type TEXT NOT NULL,
original_mime_type TEXT NOT NULL
);

-- Таблица keyword_search_terms: поисковые запросы
CREATE TABLE keyword_search_terms (
keyword_id INTEGER NOT NULL,
url_id INTEGER NOT NULL, -- FK на urls.id
lower_term TEXT NOT NULL, -- Поисковый запрос (нижний регистр)
term TEXT NOT NULL -- Оригинальный поисковый запрос
);




Анализ SQLite-баз Chrome: History, Cookies, Web Data


Практическая часть: SQL-запросы для извлечения ключевых категорий данных.

Конвертация временны́х меток

Chrome использует формат WebKit Timestamp — количество микросекунд с 1 января 1601 года. Для конвертации в стандартный Unix timestamp или читаемую дату:

sql
-- Конвертация WebKit timestamp в дату (SQLite)
-- WebKit epoch offset: 11644473600 секунд (разница между 1601 и 1970)
SELECT
datetime(
(last_visit_time / 1000000) - 11644473600,
'unixepoch',
'localtime'
) AS visit_datetime,
url,
title,
visit_count
FROM urls
ORDER BY last_visit_time DESC
LIMIT 50;


Python-функция для конвертации:

python
from datetime import datetime, timezone, timedelta

def webkit_to_datetime(webkit_ts: int) -> datetime:
"""
Конвертация WebKit timestamp в datetime.
WebKit timestamp: микросекунды с 01.01.1601
"""
WEBKIT_EPOCH_OFFSET = 11644473600 # секунды
unix_ts = (webkit_ts / 1_000_000) - WEBKIT_EPOCH_OFFSET
return datetime.fromtimestamp(unix_ts, tz=timezone.utc)

def unix_to_webkit(unix_ts: float) -> int:
"""Обратная конвертация Unix → WebKit"""
WEBKIT_EPOCH_OFFSET = 11644473600
return int((unix_ts + WEBKIT_EPOCH_OFFSET) * 1_000_000)

<h2 id="primer">Пример</h2>
ts = 13345678901234567
print(f"Дата: {webkit_to_datetime(ts)}")


Ключевые SQL-запросы для истории посещений

sql
-- Запрос 1: Полная история с деталями переходов
SELECT
datetime((v.visit_time / 1000000) - 11644473600, 'unixepoch', 'localtime') AS visit_datetime,
u.url,
u.title,
u.visit_count AS total_visits,
CASE (v.transition & 0xFF)
WHEN 0 THEN 'LINK (клик по ссылке)'
WHEN 1 THEN 'TYPED (набран вручную)'
WHEN 2 THEN 'AUTO_BOOKMARK'
WHEN 5 THEN 'GENERATED (из поиска)'
ELSE 'OTHER (' || (v.transition & 0xFF) || ')'
END AS transition_type,
v.visit_duration / 1000000.0 AS duration_seconds,
(SELECT u2.url FROM urls u2
JOIN visits v2 ON u2.id = v2.url
WHERE v2.id = v.from_visit) AS came_from_url
FROM visits v
JOIN urls u ON v.url = u.id
WHERE v.visit_time > 0
ORDER BY v.visit_time DESC;

-- Запрос 2: Поисковые запросы с разбивкой по поисковику
SELECT
datetime((v.visit_time / 1000000) - 11644473600, 'unixepoch', 'localtime') AS search_datetime,
kst.term AS search_query,
u.url AS search_url,
CASE
WHEN u.url LIKE '%google%' THEN 'Google'
WHEN u.url LIKE '%yandex%' THEN 'Яндекс'
WHEN u.url LIKE '%bing.com%' THEN 'Bing'
WHEN u.url LIKE '%mail.ru%' THEN 'Mail.ru'
ELSE 'Другой'
END AS search_engine
FROM keyword_search_terms kst
JOIN urls u ON kst.url_id = u.id
JOIN visits v ON v.url = u.id
ORDER BY v.visit_time DESC;

-- Запрос 3: Активность по часам суток (хронологический профиль)
SELECT
strftime('%H', datetime((visit_time / 1000000) - 11644473600, 'unixepoch')) AS hour_of_day,
COUNT(*) AS visit_count
FROM visits
GROUP BY hour_of_day
ORDER BY hour_of_day;

-- Запрос 4: Топ доменов по посещениям
SELECT
REPLACE(REPLACE(
SUBSTR(url, INSTR(url, '://') + 3),
SUBSTR(SUBSTR(url, INSTR(url, '://') + 3),
INSTR(SUBSTR(url, INSTR(url, '://') + 3), '/')),
''),
'www.', '') AS domain,
SUM(visit_count) AS total_visits,
COUNT(DISTINCT id) AS unique_pages
FROM urls
WHERE hidden = 0
GROUP BY domain
ORDER BY total_visits DESC
LIMIT 30;

-- Запрос 5: История загрузок
SELECT
datetime((start_time / 1000000) - 11644473600, 'unixepoch', 'localtime') AS download_start,
datetime((end_time / 1000000) - 11644473600, 'unixepoch', 'localtime') AS download_end,
target_path AS file_path,
total_bytes / 1024.0 / 1024.0 AS size_mb,
mime_type,
referrer AS source_page,
CASE state
WHEN 1 THEN 'В процессе'
WHEN 4 THEN 'Завершено'
WHEN 5 THEN 'Отменено'
ELSE 'Неизвестно'
END AS download_state
FROM downloads
ORDER BY start_time DESC;


Анализ файла Cookies

sql
-- Подключиться к файлу Cookies и извлечь данные
-- Файл: /data/data/com.android.chrome/app_chrome/Default/Cookies

-- Схема таблицы cookies Chrome
-- .schema cookies даёт:
-- CREATE TABLE cookies (
-- creation_utc INTEGER NOT NULL, -- WebKit timestamp создания
-- host_key TEXT NOT NULL, -- Домен (с точкой: .example.com)
-- name TEXT NOT NULL, -- Имя куки
-- value TEXT NOT NULL, -- Значение (может быть зашифровано)
-- path TEXT NOT NULL,
-- expires_utc INTEGER NOT NULL, -- WebKit timestamp истечения
-- is_secure INTEGER NOT NULL, -- HTTPS only
-- is_httponly INTEGER NOT NULL, -- Недоступен из JS
-- last_access_utc INTEGER NOT NULL,
-- has_expires INTEGER NOT NULL,
-- is_persistent INTEGER NOT NULL, -- Или сессионный
-- priority INTEGER NOT NULL,
-- encrypted_value BLOB DEFAULT '', -- Зашифрованное значение
-- samesite INTEGER NOT NULL,
-- source_scheme INTEGER NOT NULL,
-- source_port INTEGER NOT NULL,
-- is_same_party INTEGER NOT NULL
-- );

-- Извлечение всех куки с читаемыми датами
SELECT
datetime((creation_utc / 1000000) - 11644473600, 'unixepoch', 'localtime') AS created,
datetime((last_access_utc / 1000000) - 11644473600, 'unixepoch', 'localtime') AS last_accessed,
datetime((expires_utc / 1000000) - 11644473600, 'unixepoch', 'localtime') AS expires,
host_key AS domain,
name AS cookie_name,
CASE LENGTH(value)
WHEN 0 THEN '[ЗАШИФРОВАНО — см. encrypted_value]'
ELSE value
END AS cookie_value,
path,
CASE is_secure WHEN 1 THEN 'Да' ELSE 'Нет' END AS https_only,
CASE is_httponly WHEN 1 THEN 'Да' ELSE 'Нет' END AS httponly,
CASE is_persistent WHEN 1 THEN 'Постоянный' ELSE 'Сессионный' END AS persistence
FROM cookies
ORDER BY last_access_utc DESC;

-- Поиск аутентификационных куки (часто содержат 'session', 'auth', 'token')
SELECT host_key, name, value, is_secure, is_httponly
FROM cookies
WHERE name LIKE '%session%'
OR name LIKE '%auth%'
OR name LIKE '%token%'
OR name LIKE '%login%'
OR name LIKE '%sid%'
ORDER BY last_access_utc DESC;


Анализ Web Data: автозаполнение и платёжные данные

sql
-- Файл: /data/data/com.android.chrome/app_chrome/Default/Web Data

-- Автозаполнение форм (введённые данные)
SELECT
name AS field_name, -- Имя поля формы (email, address и т.д.)
value AS field_value, -- Введённое значение
count AS times_used, -- Сколько раз использовалось
datetime(date_created, 'unixepoch', 'localtime') AS first_used,
datetime(date_last_used, 'unixepoch', 'localtime') AS last_used
FROM autofill
ORDER BY count DESC;

-- Сохранённые адреса
SELECT
first_name, middle_name, last_name,
company_name,
street_address,
city, state, zipcode, country_code,
phone_number, email
FROM autofill_profiles ap
JOIN autofill_profile_names apn ON ap.guid = apn.guid
LEFT JOIN autofill_profile_addresses apa ON ap.guid = apa.guid
LEFT JOIN autofill_profile_phones app ON ap.guid = app.guid
LEFT JOIN autofill_profile_emails ape ON ap.guid = ape.guid;




Yandex Browser на Android: особенности и артефакты


Yandex Browser построен на движке Chromium (как и Chrome), но имеет существенные отличия в структуре хранения данных, наборе дополнительных функций и интеграции с сервисами Яндекса.

Базовые пути и структура

text
/data/data/com.yandex.browser/
├── app_tabs/ ← Данные вкладок
├── app_icons/ ← Кэш иконок
├── app_webview/ ← WebView данные
│ └── Default/ ← Профиль пользователя
│ ├── History ← SQLite: история (схема как Chrome)
│ ├── Cookies ← SQLite: куки
│ ├── Web Data ← SQLite: автозаполнение
│ ├── Login Data ← SQLite: пароли
│ ├── Bookmarks ← JSON: закладки
│ ├── Preferences ← JSON: настройки
│ ├── Favicons ← SQLite: иконки
│ └── Local Storage/ ← LocalStorage данные
├── databases/
│ ├── yandex_browser.db ← SQLite: специфические данные Яндекс
│ ├── zen.db ← Яндекс Дзен данные (если используется)
│ └── smartbox.db ← Данные умной строки (Smartbox)
├── shared_prefs/
│ ├── com.yandex.browser_preferences.xml
│ ├── YandexBrowserPreferences.xml ← Настройки, включая авторизацию
│ └── user_state.xml ← Состояние пользователя
└── files/
├── yandex_browser_data/
└── tab_thumbnails/ ← Превью вкладок (PNG файлы)


Специфические артефакты Yandex Browser

В отличие от Google Chrome, Yandex Browser хранит ряд данных в дополнительных базах, специфичных для сервисов Яндекса.

Файл yandex_browser.db содержит данные, не характерные для стандартного Chrome: информацию о сессиях входа через Яндекс.ID, данные Яндекс.Кошелька (при использовании), интеграцию с Яндекс.Картами (сохранённые маршруты из браузера), данные Яндекс.Маркета (просмотренные товары).

sql
-- Анализ yandex_browser.db
-- Просмотр таблиц
SELECT name FROM sqlite_master WHERE type='table';

-- Данные авторизации (если пользователь был залогинен в Яндекс)
-- Конкретные имена таблиц зависят от версии браузера
SELECT * FROM sessions LIMIT 20;

-- Данные Smartbox (умная строка Яндекса)
-- Хранит предложения и подсказки
SELECT * FROM smartbox_queries ORDER BY rowid DESC LIMIT 50;


Превью вкладок — уникальная особенность Yandex Browser. В директории `files/tab_thumbnails/` хранятся PNG-файлы с превью каждой открытой вкладки. Имена файлов содержат идентификатор вкладки. Это ценный артефакт: даже если история была очищена, превью могут свидетельствовать о посещённых сайтах.

bash
<h2 id="izvlechenie-prevyu-vkladok">Извлечение превью вкладок</h2>
adb pull /data/data/com.yandex.browser/files/tab_thumbnails/ \
/evidence/yandex_tab_thumbnails/

<h2 id="poluchit-metadannye-izobrazheniy-data-sozdaniya-iz-exif">Получить метаданные изображений (дата создания из EXIF)</h2>
exiftool /evidence/yandex_tab_thumbnails/*.png | grep -E "File Name|Create Date|Modify Date"


Особенности анализа истории Yandex Browser

Структура базы History у Yandex Browser идентична Chrome (тот же движок Chromium), однако есть нюансы в интерпретации поисковых запросов — Яндекс Браузер может делать больше запросов к поисковой системе Яндекса для функции предпросмотра и подсказок.

sql
-- Поисковые запросы через Яндекс (специфично для Yandex Browser)
SELECT
datetime((v.visit_time / 1000000) - 11644473600, 'unixepoch', 'localtime') AS search_time,
kst.term AS query,
u.url
FROM keyword_search_terms kst
JOIN urls u ON kst.url_id = u.id
JOIN visits v ON v.url = u.id
WHERE u.url LIKE '%yandex.ru/search%'
OR u.url LIKE '%ya.ru%'
ORDER BY v.visit_time DESC;

-- Запросы к Яндекс Картам (геолокационные данные)
SELECT
datetime((last_visit_time / 1000000) - 11644473600, 'unixepoch', 'localtime') AS visit_time,
url,
title
FROM urls
WHERE url LIKE '%maps.yandex%'
OR url LIKE '%yandex.ru/maps%'
ORDER BY last_visit_time DESC;


Анализ SharedPreferences Yandex Browser

XML-файлы настроек содержат ценную информацию о конфигурации и состоянии браузера:

python
import xml.etree.ElementTree as ET
import os

def parse_yandex_prefs(prefs_dir: str) -> dict:
"""
Извлечение данных из SharedPreferences Yandex Browser.
"""
result = {}

for xml_file in os.listdir(prefs_dir):
if not xml_file.endswith('.xml'):
continue

filepath = os.path.join(prefs_dir, xml_file)
tree = ET.parse(filepath)
root = tree.getroot()

file_data = {}
for element in root:
name = element.get('name', '')
# Интересующие нас ключи
if any(keyword in name.lower() for keyword in [
'account', 'login', 'user', 'sync', 'last', 'session'
]):
value = element.get('value') or element.text or ''
file_data[name] = value

if file_data:
result[xml_file] = file_data

return result

<h2 id="poisk-dannyh-avtorizatsii">Поиск данных авторизации</h2>
prefs = parse_yandex_prefs('/evidence/yandex_browser/shared_prefs/')
for filename, data in prefs.items():
print(f"\n=== {filename} ===")
for key, value in data.items():
print(f" {key}: {value[:100]}")




Safari на iOS: структура данных и методы извлечения


Safari — встроенный браузер iOS и iPadOS, занимающий особое положение в криминалистике Apple-устройств. Его данные распределены по нескольким директориям и используют как SQLite, так и Binary plist форматы.

Пути к данным Safari на iOS

text
/private/var/mobile/Library/
├── Safari/
│ ├── History.db ← SQLite: история (формат отличается от Chrome!)
│ ├── BrowserState.db ← SQLite: состояние браузера, открытые вкладки
│ ├── Bookmarks.db ← SQLite: закладки
│ ├── RecentlyClosedTabs.db ← SQLite: недавно закрытые вкладки
│ ├── CloudTabs.db ← SQLite: вкладки iCloud
│ ├── Form Values ← SQLite: данные автозаполнения форм
│ ├── LastSession.plist ← Binary plist: последняя сессия
│ ├── SuspendedState.plist ← Binary plist: приостановленные вкладки
│ ├── PerSitePreferences/ ← Настройки по доменам
│ └── Databases/ ← Web SQL базы данных сайтов

/private/var/mobile/Containers/Data/Application/
└── [UUID Safari]/ ← Контейнер данных Safari
├── Library/
│ ├── Caches/
│ │ └── com.apple.Safari/
│ │ ├── Cache.db ← SQLite: веб-кэш
│ │ └── Thumbnails/ ← Превью вкладок
│ └── WebKit/ ← WebKit данные
└── tmp/

/private/var/mobile/Library/Caches/
└── com.apple.WebKit/ ← Кэш WebKit

/private/var/preferences/
└── com.apple.mobilesafari.plist ← Глобальные настройки Safari


Структура History.db Safari

Safari использует кардинально отличающуюся схему базы данных истории по сравнению с Chrome. Ключевое отличие: Safari разделяет историю на записи о посещениях (history_visits) и записи об элементах (history_items).

sql
-- Схема таблиц Safari History.db

-- Таблица history_items: уникальные URL
CREATE TABLE history_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
url TEXT NOT NULL UNIQUE, -- URL страницы
domain_expansion TEXT, -- Домен для группировки
visit_count INTEGER DEFAULT 0, -- Общее число посещений
daily_visit_counts TEXT, -- JSON: посещения по дням
weekly_visit_counts TEXT, -- JSON: посещения по неделям
autocomplete_triggers TEXT, -- Триггеры автодополнения
should_recompute_derived_visit_counts INTEGER DEFAULT 0,
visit_count_score REAL DEFAULT 0,
status_code INTEGER DEFAULT 0
);

-- Таблица history_visits: каждое отдельное посещение
CREATE TABLE history_visits (
id INTEGER PRIMARY KEY AUTOINCREMENT,
history_item INTEGER NOT NULL REFERENCES history_items(id) ON DELETE CASCADE,
visit_time REAL NOT NULL, -- Core Data timestamp (секунды с 01.01.2001)
title TEXT, -- Заголовок страницы на момент посещения
load_successful INTEGER DEFAULT 1,
http_non_get INTEGER DEFAULT 0, -- Был ли не-GET запрос (POST и т.д.)
synthesized INTEGER DEFAULT 0,
redirect_source INTEGER, -- Если это редирект — источник
redirect_destination INTEGER, -- Если это редирект — назначение
origin INTEGER DEFAULT 0, -- Тип источника перехода
generation INTEGER DEFAULT 0,
attributes INTEGER DEFAULT 0,
score REAL DEFAULT 0
);


Важно: Safari использует Core Data timestamp (Cocoa timestamp) — секунды с 1 января 2001 года, а не Unix timestamp (с 1970) и не WebKit timestamp (с 1601). Это частая ошибка при конвертации.

python
from datetime import datetime, timezone

def cocoa_to_datetime(cocoa_ts: float) -> datetime:
"""
Конвертация Core Data (Cocoa) timestamp в datetime.
Cocoa epoch: 01.01.2001 00:00:00 UTC
"""
COCOA_EPOCH_OFFSET = 978307200 # секунды между 1970 и 2001
unix_ts = cocoa_ts + COCOA_EPOCH_OFFSET
return datetime.fromtimestamp(unix_ts, tz=timezone.utc)

<h2 id="proverka">Проверка</h2>
ts = 757382400.0 # примерный Cocoa timestamp
print(f"Дата: {cocoa_to_datetime(ts)}")


SQL-запросы для Safari History.db

sql
-- Полная история Safari с читаемыми датами
SELECT
datetime(hv.visit_time + 978307200, 'unixepoch', 'localtime') AS visit_datetime,
hi.url,
hv.title,
hi.visit_count AS total_visit_count,
CASE hv.http_non_get
WHEN 1 THEN 'POST/PUT/DELETE'
ELSE 'GET (обычный переход)'
END AS request_type,
CASE hv.origin
WHEN 0 THEN 'Другое'
WHEN 1 THEN 'Загрузка страницы'
WHEN 2 THEN 'Переход назад/вперёд'
WHEN 3 THEN 'Переход вперёд'
WHEN 4 THEN 'Из закладок'
WHEN 5 THEN 'Набран вручную'
WHEN 6 THEN 'Из поиска'
WHEN 7 THEN 'По ссылке'
ELSE 'Неизвестно'
END AS navigation_type
FROM history_visits hv
JOIN history_items hi ON hv.history_item = hi.id
WHERE hv.load_successful = 1
ORDER BY hv.visit_time DESC;

-- Топ сайтов Safari
SELECT
hi.domain_expansion AS domain,
SUM(hi.visit_count) AS visits,
COUNT(DISTINCT hi.id) AS unique_pages,
MAX(datetime(hv.visit_time + 978307200, 'unixepoch', 'localtime')) AS last_visit
FROM history_items hi
JOIN history_visits hv ON hi.id = hv.history_item
GROUP BY hi.domain_expansion
ORDER BY visits DESC
LIMIT 30;


Анализ BrowserState.db (открытые вкладки)

sql
-- Файл: BrowserState.db
-- Показывает вкладки, открытые в момент извлечения (или последнего закрытия)

-- Просмотр таблиц
SELECT name FROM sqlite_master WHERE type='table';

-- Открытые вкладки
SELECT
datetime(last_viewed_time + 978307200, 'unixepoch', 'localtime') AS last_viewed,
title,
url,
uuid,
is_private_tab -- 1 = приватная вкладка
FROM tabs
ORDER BY last_viewed_time DESC;




Анализ Property List файлов Safari (iOS)


Property List файлы хранят конфигурацию и состояние приложения. Для Safari они содержат критически важные данные о сессиях, настройках и истории взаимодействия.

Инструменты работы с plist

bash
<h2 id="konvertatsiya-binary-plist-v-xml-dlya-chteniya">Конвертация Binary plist в XML для чтения</h2>
plutil -convert xml1 LastSession.plist -o LastSession_readable.xml

<h2 id="ili-na-linux-cherez-libplist-utils">Или на Linux через libplist-utils</h2>
plistutil -i LastSession.plist -o LastSession_readable.xml

<h2 id="prosmotr-soderzhimogo-bez-konvertatsii">Просмотр содержимого без конвертации</h2>
plutil -p LastSession.plist


Python для работы с plist:

python
import plistlib
from pathlib import Path
from datetime import datetime, timezone

def analyze_safari_plist(plist_path: str) -> dict:
"""
Анализ plist-файлов Safari.
Поддерживает как XML, так и Binary форматы.
"""
with open(plist_path, 'rb') as f:
data = plistlib.load(f)

return data

def extract_last_session(session_plist_path: str) -> list:
"""
Извлечение данных о последней сессии Safari из LastSession.plist.
Содержит URL и заголовки всех вкладок последней сессии.
"""
data = analyze_safari_plist(session_plist_path)
sessions = []

# Структура LastSession.plist содержит массив сессий/групп вкладок
if isinstance(data, dict):
# Поиск списков URL в структуре
def extract_urls(obj, depth=0):
if isinstance(obj, str) and (obj.startswith('http') or obj.startswith('file')):
sessions.append({'url': obj, 'depth': depth})
elif isinstance(obj, dict):
for key, value in obj.items():
if key in ('URL', 'url', 'NavigationEntry'):
sessions.append({'key': key, 'value': str(value)})
else:
extract_urls(value, depth + 1)
elif isinstance(obj, list):
for item in obj:
extract_urls(item, depth)

extract_urls(data)

return sessions

<h2 id="ispolzovanie">Использование</h2>
session_data = extract_last_session('/evidence/safari/LastSession.plist')
for item in session_data:
print(item)


com.apple.mobilesafari.plist — глобальные настройки

Этот файл содержит настройки Safari и косвенные индикаторы активности:

python
def analyze_safari_global_prefs(plist_path: str) -> dict:
"""
Анализ глобальных настроек Safari.
Раскрывает конфигурацию и некоторые исторические данные.
"""
data = analyze_safari_plist(plist_path)

interesting_keys = {
'WebKitDefaultTextEncodingName': 'Кодировка по умолчанию',
'SearchEngineChoice': 'Поисковик по умолчанию',
'DefaultBrowserKey': 'Браузер по умолчанию',
'ReadingListEnabled': 'Список чтения включён',
'PrivateBrowsingEnabled': 'Приватный просмотр включён',
'SafariSuggestEnabled': 'Предложения Safari',
'LastCrashExitType': 'Тип последнего завершения',
}

result = {}
for key, description in interesting_keys.items():
if key in data:
result[description] = data[key]

return result




Восстановление удалённых данных из WAL-журналов SQLite


WAL (Write-Ahead Log) — механизм SQLite для обеспечения атомарности транзакций. С криминалистической точки зрения WAL-файлы содержат данные о недавних изменениях, включая удалённые записи.

Как работает WAL

При режиме WAL (который использует Chrome начиная с версии ~40) изменения сначала записываются в файл -wal, а не непосредственно в основную базу. Контрольная точка (checkpoint) периодически переносит данные из WAL в основную базу. Если WAL-файл существует и не пуст — он содержит изменения, ещё не перенесённые в основной файл, включая потенциально удалённые или изменённые записи.

bash
<h2 id="proverit-nalichie-wal-faylov">Проверить наличие WAL-файлов</h2>
ls -la /evidence/chrome/Default/History*
<h2 id="primer-vyvoda">Пример вывода:</h2>
<h2 id="history-osnovnoy-fayl">History (основной файл)</h2>
<h2 id="history-wal-wal-zhurnal-mozhet-soderzhat-udalyonnye-dannye">History-wal (WAL-журнал, может содержать удалённые данные)</h2>
<h2 id="history-shm-shared-memory-index">History-shm (shared memory index)</h2>

<h2 id="prinuditelnaya-kontrolnaya-tochka-dlya-korrektnogo-chteniya">Принудительная контрольная точка для корректного чтения</h2>
<h2 id="vazhno-tolko-na-kopii-ne-na-originale">ВАЖНО: только на копии, не на оригинале!</h2>
sqlite3 /evidence_copy/chrome/Default/History "PRAGMA wal_checkpoint(FULL);"


Поиск удалённых записей в свободных страницах

python
import sqlite3
import re
from pathlib import Path

def carve_deleted_from_sqlite(db_path: str, search_patterns: list) -> list:
"""
Поиск удалённых данных в файле SQLite.
Анализирует свободные страницы и WAL-журнал.

ВАЖНО: работать только с копией, не оригиналом!
"""
found_records = []
db_bytes = Path(db_path).read_bytes()

# Поиск паттернов в бинарном содержимом файла
for pattern in search_patterns:
if isinstance(pattern, str):
pattern_bytes = pattern.encode('utf-8')
else:
pattern_bytes = pattern

offset = 0
while True:
pos = db_bytes.find(pattern_bytes, offset)
if pos == -1:
break

# Извлечь контекст вокруг найденного паттерна
start = max(0, pos - 100)
end = min(len(db_bytes), pos + 300)
context = db_bytes[start:end]

found_records.append({
'offset': pos,
'pattern': pattern,
'context_hex': context.hex(),
'context_printable': ''.join(
chr(b) if 32 <= b < 127 else '.' for b in context
)
})

offset = pos + 1

return found_records

<h2 id="analiz-wal-fayla-chrome-history">Анализ WAL-файла Chrome History</h2>
wal_path = '/evidence_copy/chrome/Default/History-wal'
if Path(wal_path).exists():
# Поиск URL-паттернов в WAL
deleted = carve_deleted_from_sqlite(
wal_path,
['https://', 'http://', '.ru/', '.com/']
)

print(f"Найдено потенциальных удалённых записей: {len(deleted)}")
for record in deleted[:10]:
print(f" Смещение: {record['offset']}")
print(f" Контекст: {record['context_printable']}")


Инструмент SQLite Deleted Records Finder

bash
<h2 id="sqlitedump-spetsializirovannyy-instrument-dlya-izvlecheniya-udalyonnyh-zapisey">SQLiteDump — специализированный инструмент для извлечения удалённых записей</h2>
<h2 id="ustanovka">Установка</h2>
pip install sqlparse

<h2 id="ispolzovanie-python-sqlite3-dump">Использование python-sqlite3-dump</h2>
sqlite3 History .dump | grep -E "INSERT|VALUES" | grep -v "BEGIN\|COMMIT"

<h2 id="spetsializirovannyy-instrument-undark-sqlite-forensics">Специализированный инструмент — undark (SQLite forensics)</h2>
<h2 id="https-github-com-witwall-undark">https://github.com/witwall/undark</h2>
./undark -i History -s urls.schema > recovered_urls.txt

<h2 id="db-browser-for-sqlite-vizualnyy-instrument">DB Browser for SQLite — визуальный инструмент</h2>
<h2 id="file-open-database-history">File → Open Database → History</h2>
<h2 id="browse-data-view-show-deleted-rows">Browse Data → View → Show Deleted Rows</h2>




Облачная синхронизация: Google Sync, Yandex Sync, iCloud


Облачная синхронизация браузерных данных создаёт дополнительный источник артефактов, часто недооцениваемый при криминалистическом анализе.

Google Sync (Chrome)

При включённой синхронизации с Google-аккаунтом Chrome синхронизирует: историю посещений (опционально), закладки, открытые вкладки, пароли, данные автозаполнения, расширения. Данные доступны через Google Takeout (takeout.google.com) при наличии доступа к аккаунту, или через Google Admin Console в корпоративных средах.

python
def analyze_google_takeout_chrome(takeout_dir: str) -> dict:
"""
Анализ данных Chrome из Google Takeout.
Takeout экспортирует данные в JSON-формате.
"""
import json
from pathlib import Path

result = {}
chrome_dir = Path(takeout_dir) / 'Takeout' / 'Chrome'

if not chrome_dir.exists():
return {"error": "Директория Chrome Takeout не найдена"}

# Закладки
bookmarks_file = chrome_dir / 'Bookmarks.html'
if bookmarks_file.exists():
result['bookmarks_size'] = bookmarks_file.stat().st_size

# История (если включена в Takeout)
history_file = chrome_dir / 'BrowserHistory.json'
if history_file.exists():
with open(history_file, 'r', encoding='utf-8') as f:
history_data = json.load(f)

browser_history = history_data.get('Browser History', [])
result['history_entries'] = len(browser_history)

# Последние 20 посещений
recent = sorted(
browser_history,
key=lambda x: x.get('time_usec', 0),
reverse=True
)[:20]

result['recent_visits'] = [
{
'time': datetime.fromtimestamp(
entry['time_usec'] / 1_000_000,
tz=timezone.utc
).isoformat(),
'url': entry.get('url', ''),
'title': entry.get('title', ''),
'page_transition': entry.get('page_transition', '')
}
for entry in recent
]

return result


iCloud Sync (Safari)

Safari синхронизирует через iCloud: историю посещений (Safari History), закладки и списки чтения, открытые вкладки на всех устройствах аккаунта. Эти данные доступны через iCloud.com при наличии учётных данных, или через iCloud Drive если включён бэкап.

В резервных копиях iTunes/Finder (зашифрованных) данные Safari также присутствуют в базах:

bash
<h2 id="poisk-baz-safari-v-rezervnoy-kopii-itunes">Поиск баз Safari в резервной копии iTunes</h2>
find /path/to/ios_backup/ -name "*.db" -exec sqlite3 {} ".tables" \; 2>/dev/null | \
grep -i "history\|safari\|browser"

<h2 id="identifikator-fayla-v-backup-cherez-manifest-db">Идентификатор файла в backup (через manifest.db)</h2>
sqlite3 /path/to/ios_backup/Manifest.db \
"SELECT fileID, relativePath FROM Files WHERE relativePath LIKE '%safari%' OR relativePath LIKE '%History%';"




Инструменты криминалистического анализа мобильных браузеров


Бесплатные и open-source инструменты

ALEAPP (Android Logs Events And Protobuf Parser) — специализированный инструмент для анализа артефактов Android. Поддерживает извлечение и разбор данных Chrome, включая конвертацию временны́х меток, построение хронологии.

bash
<h2 id="ustanovka-aleapp">Установка ALEAPP</h2>
git clone https://github.com/abrignoni/ALEAPP.git
cd ALEAPP
pip install -r requirements.txt

<h2 id="zapusk-analiza">Запуск анализа</h2>
python aleapp.py -t fs -i /path/to/android/filesystem/ -o /output/aleapp_report/
<h2 id="t-fs-faylovaya-sistema-takzhe-podderzhivaet-tar-zip">-t fs = файловая система (также поддерживает tar, zip)</h2>


iLEAPP (iOS Logs Events And Protobuf Parser) — аналог ALEAPP для iOS:

bash
git clone https://github.com/abrignoni/iLEAPP.git
cd iLEAPP
pip install -r requirements.txt

python ileapp.py -t fs -i /path/to/ios/filesystem/ -o /output/ileapp_report/


DB Browser for SQLite — незаменимый инструмент для визуального анализа SQLite-баз. Функции: открытие баз в режиме «только чтение», редактор SQL-запросов, просмотр удалённых записей (опция View → Show Deleted Records). Доступен для Windows, macOS, Linux (sqlitebrowser.org).

Hindsight — специализированный инструмент для анализа Chrome-артефактов:

bash
pip install pyhindsight
hindsight -i /path/to/chrome/profile/ -o /output/hindsight_report/ -f xlsx


Autopsy — открытая цифровая криминалистическая платформа с модулями для мобильных браузеров. Плагины для Android и iOS позволяют автоматически извлекать и категоризировать браузерные артефакты.

Коммерческие инструменты

Cellebrite UFED и Cellebrite Physical Analyzer — де-факто стандарт в правоохранительной криминалистике. Поддерживает полный спектр операций с Android и iOS устройствами, включая анализ браузеров.

Magnet AXIOM — мощный инструмент с поддержкой облачных источников. Извлекает данные Google Sync, iCloud, позволяет корреляцию данных с нескольких устройств одного пользователя.

Oxygen Forensic Detective — особенно силён в анализе российских сервисов и приложений, включая Yandex Browser.

MOBILedit Forensic Express — специализированный инструмент для мобильных устройств.

Скрипты для автоматизации

python
#!/usr/bin/env python3
"""
mobile_browser_forensics.py
Автоматизированный сбор и анализ артефактов мобильных браузеров.
Требует: sqlite3, plistlib (стандартная библиотека), exifread (pip install)
"""

import sqlite3
import os
import json
import hashlib
import csv
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional, List, Dict

class MobileBrowserAnalyzer:
"""
Unified анализатор для Chrome (Android), Yandex Browser (Android) и Safari (iOS).
"""

WEBKIT_EPOCH_OFFSET = 11644473600 # секунд
COCOA_EPOCH_OFFSET = 978307200 # секунд

def __init__(self, output_dir: str, case_id: str):
self.output_dir = Path(output_dir) / case_id
self.case_id = case_id
self.output_dir.mkdir(parents=True, exist_ok=True)
self.report = {
"case_id": case_id,
"analysis_start": datetime.utcnow().isoformat(),
"artifacts": {}
}

def webkit_ts(self, ts: int) -> Optional[datetime]:
"""WebKit timestamp → datetime (Chrome/Yandex)"""
if not ts or ts <= 0:
return None
try:
unix = (ts / 1_000_000) - self.WEBKIT_EPOCH_OFFSET
return datetime.fromtimestamp(unix, tz=timezone.utc)
except (OSError, OverflowError, ValueError):
return None

def cocoa_ts(self, ts: float) -> Optional[datetime]:
"""Core Data timestamp → datetime (Safari)"""
if not ts or ts <= 0:
return None
try:
unix = ts + self.COCOA_EPOCH_OFFSET
return datetime.fromtimestamp(unix, tz=timezone.utc)
except (OSError, OverflowError, ValueError):
return None

def hash_file(self, filepath: str) -> Dict[str, str]:
"""Вычислить MD5 и SHA256 файла"""
md5 = hashlib.md5()
sha256 = hashlib.sha256()
with open(filepath, 'rb') as f:
for chunk in iter(lambda: f.read(65536), b''):
md5.update(chunk)
sha256.update(chunk)
return {
"md5": md5.hexdigest(),
"sha256": sha256.hexdigest()
}

def query_db(self, db_path: str, query: str, params: tuple = ()) -> List[dict]:
"""Безопасный запрос к SQLite в режиме только чтение"""
uri = f"file:{db_path}?mode=ro"
conn = sqlite3.connect(uri, uri=True)
conn.row_factory = sqlite3.Row

try:
cursor = conn.execute(query, params)
return [dict(row) for row in cursor.fetchall()]
finally:
conn.close()

def analyze_chrome(self, profile_dir: str) -> dict:
"""Анализ профиля Chrome"""
profile = Path(profile_dir)
results = {}

# История
history_db = profile / 'History'
if history_db.exists():
results['hashes'] = self.hash_file(str(history_db))

visits = self.query_db(str(history_db), """
SELECT
v.visit_time,
u.url,
hv.title,
u.visit_count,
v.transition
FROM visits v
JOIN urls u ON v.url = u.id
LEFT JOIN (
SELECT url_id, title FROM keyword_search_terms
) hv ON hv.url_id = u.id
ORDER BY v.visit_time DESC
LIMIT 1000
""")

results['history'] = [
{
row,
'visit_datetime': self.webkit_ts(row['visit_time']).isoformat()
if self.webkit_ts(row['visit_time']) else 'N/A'
}
for row in visits
]

results['history_count'] = len(results['history'])

# Загрузки
if history_db.exists():
downloads = self.query_db(str(history_db), """
SELECT start_time, end_time, target_path,
total_bytes, mime_type, referrer, state
FROM downloads
ORDER BY start_time DESC
""")
results['downloads'] = [
{
row,
'start_datetime': self.webkit_ts(row['start_time']).isoformat()
if self.webkit_ts(row['start_time']) else 'N/A'
}
for row in downloads
]

return results

def analyze_safari(self, safari_dir: str) -> dict:
"""Анализ данных Safari"""
safari = Path(safari_dir)
results = {}

history_db = safari / 'History.db'
if history_db.exists():
results['hashes'] = self.hash_file(str(history_db))

visits = self.query_db(str(history_db), """
SELECT hv.visit_time, hi.url, hv.title,
hi.visit_count, hv.origin
FROM history_visits hv
JOIN history_items hi ON hv.history_item = hi.id
WHERE hv.load_successful = 1
ORDER BY hv.visit_time DESC
LIMIT 1000
""")

results['history'] = [
{
row,
'visit_datetime': self.cocoa_ts(row['visit_time']).isoformat()
if self.cocoa_ts(row['visit_time']) else 'N/A'
}
for row in visits
]

return results

def export_to_csv(self, data: List[dict], filename: str):
"""Экспорт данных в CSV"""
if not data:
return

filepath = self.output_dir / filename
with open(filepath, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=data[0].keys())
writer.writeheader()
writer.writerows(data)

print(f"[+] Экспортировано: {filepath} ({len(data)} записей)")

def generate_report(self):
"""Генерация итогового JSON-отчёта"""
self.report["analysis_end"] = datetime.utcnow().isoformat()

report_path = self.output_dir / f"{self.case_id}_browser_report.json"
with open(report_path, 'w', encoding='utf-8') as f:
json.dump(self.report, f, indent=2, ensure_ascii=False, default=str)

print(f"[+] Отчёт: {report_path}")




Построение хронологии событий по данным браузера


Хронология (timeline) — один из важнейших продуктов браузерной криминалистики. Она позволяет восстановить последовательность действий пользователя и сопоставить их с другими событиями на устройстве.

Источники временны́х меток в браузере

Каждый артефакт браузера несёт временну́ю метку. Для построения полной хронологии необходимо объединить данные из всех источников:

sql
-- Унифицированная хронология Chrome (История + Загрузки + Куки)
-- Выполнять после ATTACH двух баз

ATTACH DATABASE '/evidence/chrome/Default/Cookies' AS cookies_db;

SELECT
datetime((visit_time / 1000000) - 11644473600, 'unixepoch', 'localtime') AS event_time,
'Посещение' AS event_type,
url AS detail,
title AS additional_info
FROM visits v
JOIN urls u ON v.url = u.id

UNION ALL

SELECT
datetime((start_time / 1000000) - 11644473600, 'unixepoch', 'localtime'),
'Загрузка файла',
target_path,
mime_type || ' (' || (total_bytes/1024) || ' KB)'
FROM downloads

UNION ALL

SELECT
datetime((creation_utc / 1000000) - 11644473600, 'unixepoch', 'localtime'),
'Создание куки',
host_key || ' — ' || name,
CASE is_persistent WHEN 1 THEN 'Постоянный' ELSE 'Сессионный' END
FROM cookies_db.cookies

ORDER BY event_time DESC;


Корреляция с другими источниками

python
def merge_timelines(browser_events: list, system_events: list,
sms_events: list = None) -> list:
"""
Объединение хронологии браузера с системными событиями устройства.
Позволяет построить полную картину действий пользователя.
"""
all_events = []

for event in browser_events:
all_events.append({
'timestamp': event.get('event_time', ''),
'source': 'BROWSER',
'type': event.get('event_type', ''),
'detail': event.get('detail', ''),
'additional': event.get('additional_info', '')
})

for event in system_events:
all_events.append({
'timestamp': event.get('timestamp', ''),
'source': 'SYSTEM',
'type': event.get('event_type', ''),
'detail': event.get('detail', ''),
'additional': ''
})

if sms_events:
for event in sms_events:
all_events.append({
'timestamp': event.get('timestamp', ''),
'source': 'SMS',
'type': 'SMS сообщение',
'detail': event.get('address', ''),
'additional': event.get('body', '')[:100]
})

# Сортировка по времени
all_events.sort(key=lambda x: x.get('timestamp', ''))
return all_events




Продвинутые техники: кэш, сессии и токены аутентификации


Анализ HTTP-кэша Chrome

Кэш Chrome хранится в директории Cache/Cache_Data/ в специальном формате. Для анализа кэша используется инструмент ChromeCacheView или скрипты на Python:

python
def list_chrome_cache(cache_dir: str) -> list:
"""
Инвентаризация файлов кэша Chrome.
Читает индексный файл для получения URL кэшированных ресурсов.
"""
cache_path = Path(cache_dir)
index_file = cache_path / 'index'

cached_resources = []

# Поиск всех data_ файлов в Cache_Data
for data_file in cache_path.glob('f_*'):
file_info = {
'filename': data_file.name,
'size': data_file.stat().st_size,
'modified': datetime.fromtimestamp(
data_file.stat().st_mtime,
tz=timezone.utc
).isoformat()
}

# Попытка прочитать HTTP-заголовки из начала файла
with open(data_file, 'rb') as f:
header_data = f.read(512)

# Поиск URL в бинарных данных
for url_pattern in [b'http://', b'https://']:
pos = header_data.find(url_pattern)
if pos != -1:
url_end = header_data.find(b'\x00', pos)
if url_end == -1:
url_end = pos + 200
url = header_data[pos:url_end].decode('utf-8', errors='replace')
file_info['cached_url'] = url
break

cached_resources.append(file_info)

return sorted(cached_resources, key=lambda x: x['modified'], reverse=True)


Извлечение токенов сессий из Cookies

Куки аутентификации — ценный криминалистический артефакт, позволяющий установить факт авторизации на конкретных сервисах:

sql
-- Поиск активных сессионных куки крупных сервисов
SELECT
datetime((creation_utc / 1000000) - 11644473600, 'unixepoch', 'localtime') AS created,
datetime((last_access_utc / 1000000) - 11644473600, 'unixepoch', 'localtime') AS last_used,
host_key AS service,
name AS cookie_name,
CASE
WHEN LENGTH(value) > 0 THEN SUBSTR(value, 1, 50) || '...'
ELSE '[зашифровано]'
END AS value_preview,
CASE expires_utc
WHEN 0 THEN 'Сессионный (истёк при закрытии)'
ELSE datetime((expires_utc / 1000000) - 11644473600, 'unixepoch', 'localtime')
END AS expires
FROM cookies
WHERE (
-- Крупные российские сервисы
host_key LIKE '%.sberbank.ru'
OR host_key LIKE '%.tinkoff.ru'
OR host_key LIKE '%.vtb.ru'
OR host_key LIKE '%.gosuslugi.ru'
OR host_key LIKE '%.nalog.ru'
OR host_key LIKE '%.yandex.ru'
OR host_key LIKE '%.vk.com'
OR host_key LIKE '%.ok.ru'
OR host_key LIKE '%.mail.ru'
-- Международные
OR host_key LIKE '%.google.com'
OR host_key LIKE '%.facebook.com'
OR host_key LIKE '%.instagram.com'
)
AND (
name LIKE '%session%' OR name LIKE '%auth%' OR name LIKE '%token%'
OR name LIKE '%login%' OR name LIKE '%sid%' OR name LIKE '%sso%'
OR name LIKE '%csrftoken%' OR name = 'PHPSESSID' OR name = 'JSESSIONID'
)
ORDER BY last_access_utc DESC;




Сравнительный анализ: Chrome vs Yandex vs Safari


ХарактеристикаChrome (Android)Yandex Browser (Android)Safari (iOS)
Основной форматSQLite + JSONSQLite + JSON + дополн. БДSQLite + Binary plist
Временны́е меткиWebKit (μs с 1601)WebKit (μs с 1601)Core Data (s с 2001)
ИсторияHistory (SQLite)History (SQLite)History.db (другая схема)
КукиCookies (SQLite)Cookies (SQLite)Cookies.binarycookies
КэшПроприетарныйПроприетарныйCache.db (SQLite)
Root требуетсяДа (полный доступ)Да (полный доступ)Нет (iTunes backup)
Облачный синкGoogle SyncYandex SynciCloud
Уникальные артефактыDownload ServiceTab thumbnails, SmartboxBrowserState.db, plist сессий
WAL-журналыДаДаДа
Доп. особенностиVisited Links (Bloom filter)Zen.db, дополн. сервисы ЯндексаCloudTabs.db

Оформление результатов для судебного заключения


Результаты криминалистического исследования браузеров оформляются по тем же требованиям, что и любое цифровое криминалистическое заключение.

Обязательные элементы заключения

Вводная часть содержит: основание для исследования (постановление/договор), сведения об эксперте, перечень исследуемых объектов с хэшами, описание применённых инструментов с версиями.

Исследовательская часть описывает: какие браузеры обнаружены, версии браузеров, периоды активности, методологию извлечения данных, описание обнаруженных артефактов с временны́ми метками.

Выводы: ответы на поставленные вопросы с ссылками на конкретные артефакты.

Приложения: таблицы данных, хронологии, скриншоты инструментов.

Шаблон описания методологии

text
МЕТОДОЛОГИЯ ИССЛЕДОВАНИЯ БРАУЗЕРНЫХ АРТЕФАКТОВ

1. Подготовка объекта исследования:
- Объект: [описание устройства]
- Серийный номер: [SN]
- Версия ОС: [версия]
- Криминалистическая копия создана: [дата/время]
- Инструмент копирования: [название, версия]
- SHA256 исходного файла: [хэш]
- SHA256 рабочей копии: [хэш]
- Хэши совпадают: ДА / НЕТ

2. Исследованные браузеры:
- [Браузер]: версия [X.X.X], профиль по пути [путь]

3. Применённые инструменты:
- DB Browser for SQLite [версия] — анализ SQLite баз
- ALEAPP/iLEAPP [версия] — автоматизированный анализ артефактов
- Hindsight [версия] — специализированный анализ Chrome

4. Конвертация временны́х меток:
- Chrome/Yandex Browser: WebKit timestamp (μs с 01.01.1601)
Формула: datetime = (webkit_ts / 1000000) - 11644473600 (Unix)
- Safari: Core Data timestamp (s с 01.01.2001)
Формула: datetime = cocoa_ts + 978307200 (Unix)
- Все даты приведены к часовому поясу: UTC / МСК (UTC+3)




Часто задаваемые вопросы (FAQ)


Вопрос 1: Как извлечь данные Chrome без root-прав на Android?

Без root-прав доступны несколько методов с разной полнотой данных. ADB backup (`adb backup -noapk com.android.chrome`) — в Android 12+ Google ограничила возможность бэкапа Chrome через ADB. ADB pull работает только для файлов в доступных директориях. Наиболее полный вариант без root — использовать adb shell с уязвимостью конкретной версии Android или применять коммерческие инструменты (Cellebrite, Oxygen), имеющие специальные агенты для извлечения. Для Android устройств производителей (Samsung, Huawei) могут существовать специфичные методы извлечения через диагностические интерфейсы.

Вопрос 2: Можно ли восстановить историю браузера если она была очищена?

Зависит от времени прошедшего с момента очистки и интенсивности использования устройства. При физическом образе возможно: анализ WAL-файлов (если они ещё не перезаписаны), анализ свободных страниц SQLite (carving), поиск остатков данных в неаллоцированном пространстве файловой системы. При логическом извлечении — значительно сложнее. Если включена облачная синхронизация — данные могут сохраняться в Google Sync или iCloud даже после локальной очистки.

Вопрос 3: Что такое Visited Links и какова его ценность?

Файл Visited Links в Chrome хранит хэши (Bloom filter) посещённых URL. Это позволяет браузеру окрашивать посещённые ссылки в пурпурный цвет. Криминалистическая ценность: наличие URL в Visited Links подтверждает посещение, даже если история была очищена — этот файл обновляется отдельно. Ограничение: файл хранит только хэши, не сами URL — можно проверить конкретный URL на наличие, но нельзя перечислить все посещённые адреса.

Вопрос 4: Чем отличается Safari на iPhone от Safari на Mac в части хранения данных?

iOS Safari хранит данные в `/private/var/mobile/Library/Safari/` и использует Binary plist для ряда файлов. macOS Safari хранит данные в `~/Library/Safari/`. Схема History.db идентична на обеих платформах (те же таблицы, тот же формат временны́х меток). Ключевое отличие: на iOS дополнительно существует CloudTabs.db с вкладками других устройств из iCloud, а файл доступен только при полном физическом извлечении или зашифрованном iTunes backup.

Вопрос 5: Как анализировать куки если значение зашифровано?

Chrome на Android шифрует значения куки с использованием Android Keystore. Поле `encrypted_value` содержит зашифрованные данные, поле `value` — пустое. Расшифровка в рамках криминалистики возможна только при наличии: образа системного раздела с ключами Keystore (при физическом извлечении), данных Keystore из TEE (Trusted Execution Environment) — крайне сложно. Практически: при live-анализе включённого устройства Cellebrite и некоторые другие инструменты могут расшифровать куки до извлечения. При анализе образа — зашифрованные куки часто остаются недоступными.

Вопрос 6: Как отличить ручной ввод URL от перехода по ссылке в Chrome?

В таблице visits поле `transition` содержит битовую маску. Младший байт определяет тип перехода: значение 1 (TYPED) означает ручной ввод в адресную строку, значение 0 (LINK) — клик по ссылке, значение 5 (GENERATED) — переход из результатов поиска. Дополнительно: поле `typed_count` в таблице urls показывает сколько раз конкретный URL был набран вручную. Эта информация важна для установления намеренности действий пользователя.

Вопрос 7: Какие данные Yandex Browser отсутствуют в стандартном Chrome?

Yandex Browser добавляет: базу данных smartbox.db с историей запросов к умной строке (включая запросы, не завершившиеся переходом), превью вкладок в files/tab_thumbnails/ (PNG), базу zen.db с историей просмотров Яндекс Дзен если пользователь его использовал, данные интеграции с сервисами Яндекса (Маркет, Карты, Погода). XML-файлы SharedPreferences также содержат больше информации об использованных функциях Яндекса.

Вопрос 8: Как анализировать данные LocalStorage и IndexedDB браузера?

LocalStorage в Chrome хранится в формате LevelDB в директории `Local Storage/leveldb/`. Для анализа используются инструменты: ldb (входит в leveldb-tools), ccl_chrome_indexeddb (Python библиотека). IndexedDB также хранится в LevelDB в директории `IndexedDB/`. Эти хранилища содержат данные веб-приложений — переписку в веб-версиях мессенджеров, данные веб-версий почтовых клиентов, корзины интернет-магазинов. Для Safari LocalStorage также использует SQLite.



Заключение: Мобильная браузерная криминалистика в 2026 году


Мобильный браузер в 2026 году — один из наиболее информативных источников криминалистических данных. Вся история цифрового взаимодействия человека с интернетом — поиск, покупки, коммуникации, финансы — оставляет следы в десятках файлов в директории браузера. Умение правильно работать с этими артефактами является фундаментальным навыком DFIR-специалиста.

Ключевые выводы

SQLite является центральным форматом для всех трёх браузеров. Понимание схем таблиц, форматов временны́х меток и особенностей WAL-журналов — обязательный технический фундамент для работы с браузерными артефактами.

Форматы временны́х меток критически различаются. WebKit timestamp (Chrome, Yandex) и Core Data timestamp (Safari) требуют принципиально разных формул конвертации. Ошибка в конвертации даёт неверную хронологию — что недопустимо в судебной экспертизе.

WAL-журналы и свободные страницы — часто недооцениваемый источник. При физическом извлечении анализ этих областей позволяет восстановить данные, удалённые относительно недавно.

Облачная синхронизация расширяет криминалистическую поверхность. Google Sync, Yandex Sync и iCloud могут содержать данные, отсутствующие на конкретном устройстве — что особенно важно при анализе поведения пользователя на нескольких устройствах.

Тренды 2026 года

Privacy Sandbox в Chrome меняет модель куки. Google постепенно переходит от сторонних куки к Privacy Sandbox API — что изменит состав артефактов в будущих версиях браузера. DFIR-специалистам необходимо отслеживать эти изменения.

Усиление шифрования артефактов. Android KeyStore и iOS Secure Enclave всё активнее защищают данные браузеров. Криминалистические инструменты вынуждены искать новые подходы к работе с зашифрованными данными.

Рост значимости PWA-артефактов. Progressive Web Apps оставляют артефакты в браузерном хранилище — IndexedDB, Cache API, Service Workers. Это новый слой данных, требующий отдельной методологии анализа.