浏览器取证深度分析

浏览器取证深度分析

浏览器是现代数字取证中信息密度最高的用户行为数据源之一。每一次 URL 访问、每一次文件下载、每一次登录认证、每一个 Cookie 的设置与读取、每一次表单自动填充的触发,都会在浏览器的本地数据库中留下持久化记录。与系统日志不同,浏览器数据直接反映了用户的意图和行为——访问了什么、下载了什么、登录了哪些系统、在什么时候做了什么事情。

已有文章 浏览器痕迹与下载执行链分析 从下载执行链的角度分析了浏览器在钓鱼攻击中的取证价值,浏览器下载与LNK和JumpList用户操作链分析 覆盖了浏览器下载与操作系统层面用户操作链的关联分析。本文换一个角度:系统性地覆盖浏览器取证的完整技术体系,从数据库结构到历史记录分析、从 Cookie 取证到缓存恢复、从扩展分析到隐身模式痕迹检测、从同步数据提取到自动化脚本构建,构建一套完整的浏览器取证方法论。


0x01 浏览器取证概述

1. 浏览器在数字取证中的重要性

浏览器取证的核心价值在于:浏览器是用户与互联网交互的唯一入口,所有网络活动都会在浏览器中留下痕迹。在应急响应、内部威胁调查、数据泄露事件、网络犯罪侦查中,浏览器数据能够回答以下关键问题:

  • 用户访问了哪些网站? 历史记录提供了完整的 URL 访问时间线
  • 用户下载了什么文件? 下载记录提供了源 URL、落地路径、文件大小和时间戳
  • 用户登录了哪些系统? 保存的密码和 Cookie 提供了认证信息
  • 用户是否访问了恶意站点? 通过 URL 模式匹配和威胁情报关联判断
  • 用户的行为模式是什么? 通过历史记录和表单数据重建用户画像

与内存取证、文件系统取证、日志取证相比,浏览器取证的独特优势在于其语义丰富性。系统日志记录的是"发生了什么事件",文件系统记录的是"文件在哪里",而浏览器记录的是"用户想做什么"。这种语义层面的信息对于重建攻击链和理解攻击者的意图至关重要。

2. 主流浏览器架构

Chromium 内核浏览器

Google Chrome、Microsoft Edge(Chromium 版)、Brave、Vivaldi、Opera 等浏览器均基于 Chromium 开源项目构建。在取证层面,这些浏览器的数据结构高度一致:

特征说明
数据库格式SQLite
多进程架构浏览器进程、渲染进程、GPU 进程、扩展进程分离
数据隔离每个配置文件(Profile)独立存储数据
渲染引擎Blink(基于 WebKit)
JavaScript 引擎V8
网络栈独立的网络栈,支持 SPDY/HTTP3

Firefox 浏览器

Mozilla Firefox 使用独立的 Gecko 引擎,数据结构与 Chromium 系列有显著差异:

特征说明
数据库格式SQLite(共享内存模式 WAL)
进程模型Electrolysis(Fission)多进程架构
数据存储统一的 profile 目录
渲染引擎Gecko
JavaScript 引擎SpiderMonkey
扩展系统WebExtensions API

3. 浏览器数据存储位置

Windows 系统

浏览器默认存储路径
ChromeC:\Users\<username>\AppData\Local\Google\Chrome\User Data\Default\
EdgeC:\Users\<username>\AppData\Local\Microsoft\Edge\User Data\Default\
FirefoxC:\Users\<username>\AppData\Roaming\Mozilla\Firefox\Profiles\<profile>\

macOS 系统

浏览器默认存储路径
Chrome~/Library/Application Support/Google/Chrome/Default/
Edge~/Library/Application Support/Microsoft Edge/Default/
Firefox~/Library/Application Support/Firefox/Profiles/<profile>.default-release/

Linux 系统

浏览器默认存储路径
Chrome~/.config/google-chrome/Default/
Edge~/.config/microsoft-edge/Default/
Firefox~/.mozilla/firefox/<profile>.default-release/

关键取证要点:当存在多个 Profile 时,每个 Profile 都有独立的历史记录、Cookie 和配置。取证时必须检查所有 Profile 目录,否则可能遗漏关键证据。

4. 浏览器数据安全机制

加密机制

  • Chrome(Windows):使用 Windows DPAPI(Data Protection API)加密 Cookie 和保存的密码,密钥存储在 Local State 文件的 os_crypt.encrypted_key 字段中
  • Chrome(macOS):使用 Keychain 存储加密密钥
  • Chrome(Linux):使用 libsecret 或硬编码密钥 peanuts(v10 加密)或空密码(v11 加密)
  • Firefox:使用 NSS(Network Security Services)加密保存的密码,主密码(Master Password)可选启用

沙箱机制

Chromium 的沙箱架构将渲染进程限制在受限的沙箱环境中,限制了渲染进程对文件系统和网络的直接访问。这对取证的影响在于:渲染进程中的内存数据可能在浏览器关闭后立即释放,而浏览器主进程的数据会持久化到磁盘。

数据库锁机制

浏览器在运行时会锁定 SQLite 数据库文件,使用 WAL(Write-Ahead Logging)模式保证并发安全性。取证时需要处理以下情况:

  • 浏览器正在运行时,数据库文件可能处于锁定状态
  • WAL 文件(.db-wal.db-shm)可能包含未提交到主数据库的最新数据
  • 取证采集时应当首先关闭浏览器或使用只读方式挂载

5. 浏览器取证的法律和隐私考量

浏览器取证涉及高度敏感的个人隐私数据。在进行浏览器取证时,必须遵守以下原则:

  • 授权范围:取证范围必须在法律授权的范围之内,不得超范围搜索
  • 数据最小化:仅采集和分析与案件相关的浏览器数据
  • 数据保护:采集的浏览器数据应当加密存储,防止二次泄露
  • 链式保管:浏览器数据从采集到分析的全过程必须有完整的链式保管记录
  • 用户通知:根据当地法律,可能需要告知用户其浏览器数据被采集的事实
  • 跨境数据:浏览器同步数据可能存储在境外服务器上,涉及跨境数据传输的法律问题

0x02 浏览器数据库结构

1. SQLite 数据库在浏览器中的应用

SQLite 是几乎所有现代浏览器的核心数据存储引擎。其优势在于:零配置、单文件存储、事务安全、跨平台兼容。在取证层面,SQLite 数据库提供了结构化的查询能力,取证人员可以使用标准 SQL 语法从数据库中提取所需数据。

关键文件类型:

文件扩展名说明取证价值
.sqlite / .db主数据库文件包含所有持久化数据
-walWrite-Ahead Log包含未同步到主数据库的最新数据
-shmShared MemoryWAL 的共享内存索引

取证要点:WAL 文件可能包含主数据库文件中不存在的最新记录。例如,当浏览器正在运行时,最近的访问历史可能只存在于 WAL 文件中。如果只分析主数据库文件而忽略 WAL 文件,可能导致证据遗漏。

2. Chrome/Edge 数据库结构

Chrome 和 Edge(Chromium 版)共享相同的数据结构。核心数据库文件位于 Default(或对应 Profile)目录下:

History 数据库

Default/History(实际为 History-journal)

包含以下核心表:

表名说明取证价值
urls所有访问过的 URL完整的浏览历史
visits每次访问的详细信息访问时间、来源、停留时长
segmentsURL 分段信息高频访问的域名和路径
keyword_search_terms搜索关键词用户在搜索引擎中输入的查询
downloads下载记录文件来源、落地路径、时间
downloads_url_chains下载 URL 链下载过程中的重定向链

Cookies 数据库

表名说明
cookies所有 Cookie 信息
meta数据库元信息

Login Data 数据库

表名说明
logins保存的用户名和加密密码
stats登录统计信息

Web Data 数据库

表名说明
autofill自动填充的表单数据
autofill_profile_names姓名自动填充
autofill_profile_addresses地址自动填充
autofill_profile_phones电话号码自动填充
credit_cards保存的信用卡信息

Preferences 文件

JSON 格式的配置文件,包含浏览器设置、扩展配置、下载目录、首页设置等信息。取证时重点关注:

  • download.default_directory:默认下载目录
  • browser.show_home_button:首页按钮设置
  • extensions.installed:已安装的扩展列表
  • profile.name:配置文件名称

3. Firefox 数据库结构

Firefox 的数据存储结构与 Chrome 有显著差异:

places.sqlite

Firefox 的核心历史和书签数据库:

表名说明
moz_places所有访问过的 URL 和书签
moz_historyvisits历史访问记录
moz_bookmarks书签数据
moz_bookmarks_roots书签根目录
moz_inputhistory输入历史

cookies.sqlite

表名说明
moz_cookies所有 Cookie 信息

formhistory.sqlite

表名说明
moz_formhistory表单输入历史

logins.json + key4.db

Firefox 使用 logins.json 存储加密的登录信息,使用 key4.db(NSS 密钥数据库)存储解密密钥。

handlers.json

存储协议处理器配置,记录了哪些网站注册为特定协议的处理器。

4. 数据库字段解析

Chrome urls 表核心字段

CREATE TABLE urls (
    id INTEGER PRIMARY KEY,
    url TEXT NOT NULL,
    title TEXT NOT NULL,
    visit_count INTEGER NOT NULL DEFAULT 0,
    typed_count INTEGER NOT NULL DEFAULT 0,
    last_visit_time INTEGER NOT NULL DEFAULT 0,
    hidden INTEGER NOT NULL DEFAULT 0
);

关键字段解读:

  • visit_count:该 URL 被访问的总次数
  • typed_count:用户手动输入该 URL 的次数(区别于链接点击)
  • last_visit_time:最后一次访问时间(Chrome 时间戳,微秒级,从 1601-01-01 开始)
  • hidden:是否在 UI 中隐藏(通常为 0,但可能被恶意软件利用来隐藏痕迹)

Chrome visits 表核心字段

CREATE TABLE visits (
    id INTEGER PRIMARY KEY,
    url INTEGER NOT NULL,
    visit_time INTEGER NOT NULL,
    from_visit INTEGER,
    transition INTEGER NOT NULL DEFAULT 0,
    segment_id INTEGER,
    is_indexed INTEGER
);

transition 字段的取值含义:

含义取证意义
0TYPED用户手动输入 URL
1LINK通过链接点击
2BOOKMARK通过书签访问
3SUBFRAME框架内导航
4REDIRECT重定向
5RELOAD页面刷新
6KEYWORD关键词搜索
7GENERATE生成的 URL(如 JavaScript)

5. 数据库锁定和并发处理

浏览器在运行时会使用 SQLite 的 WAL 模式来处理并发读写。取证时需要注意以下问题:

数据库锁定

当浏览器正在运行时,SQLite 数据库文件可能处于锁定状态。取证工具尝试直接读取可能失败或获取到不一致的数据。建议的处理方式:

  1. 首先检查浏览器进程是否正在运行
  2. 如果可以,优雅地关闭浏览器后再采集
  3. 如果无法关闭浏览器,使用 SQLite 的 WAL 模式只读连接
  4. 使用 .timeout 设置等待时间

并发读取的 WAL 模式

WAL 模式下,数据库的读取操作不会阻塞写入操作,写入操作也不会阻塞读取操作。这使得取证工具可以在浏览器运行时读取数据库,但可能无法获取到最新的 WAL 中的数据。


0x03 历史记录分析

1. Chrome/Edge 历史记录

Chrome 和 Edge 的历史记录存储在 History 数据库的 urlsvisits 表中。urls 表存储所有访问过的 URL 及其元数据,visits 表记录每次访问的详细信息。

基础历史记录查询

SELECT
    u.url,
    u.title,
    u.visit_count,
    u.typed_count,
    u.last_visit_time,
    v.visit_time,
    v.transition
FROM urls u
JOIN visits v ON u.id = v.url
ORDER BY v.visit_time DESC
LIMIT 100;

Chrome 时间戳转换

Chrome 使用自 1601-01-01 00:00:00 UTC 以来的微秒数作为时间戳。转换为可读时间的 SQL 表达式:

SELECT
    datetime((last_visit_time / 1000000) - 11644473600, 'unixepoch', 'localtime') AS readable_time,
    url,
    title,
    visit_count
FROM urls
ORDER BY last_visit_time DESC;

2. Firefox 历史记录

Firefox 的历史记录存储在 places.sqlite 数据库中。与 Chrome 不同,Firefox 使用标准的 Unix 时间戳(秒级,从 1970-01-01 开始)。

基础历史记录查询

SELECT
    p.url,
    p.title,
    p.visit_count,
    p.last_visit_date,
    hv.visit_type,
    datetime(hv.visit_date / 1000000, 'unixepoch', 'localtime') AS visit_time
FROM moz_places p
JOIN moz_historyvisits hv ON p.id = hv.place_id
ORDER BY hv.visit_date DESC
LIMIT 100;

moz_historyvisitsvisit_type 字段:

含义
1普通访问
2重定向
3用户输入
4书签访问
5内嵌框架
6重载页面

3. 访问频率分析

访问频率分析可以揭示用户的行为模式和重点关注的网站。高频访问的网站可能包含重要的业务系统、社交平台或攻击者使用的工具。

SELECT
    u.url,
    u.title,
    u.visit_count,
    u.typed_count,
    datetime((u.last_visit_time / 1000000) - 11644473600, 'unixepoch', 'localtime') AS last_visit
FROM urls u
WHERE u.visit_count > 10
ORDER BY u.visit_count DESC
LIMIT 50;

域名级别的频率分析

SELECT
    CASE
        WHEN url LIKE '%://%' THEN
            SUBSTR(url,
                INSTR(url, '://') + 3,
                INSTR(SUBSTR(url, INSTR(url, '://') + 3), '/') - 1
            )
        ELSE url
    END AS domain,
    SUM(visit_count) AS total_visits,
    COUNT(*) AS unique_urls,
    MAX(last_visit_time) AS last_access
FROM urls
GROUP BY domain
ORDER BY total_visits DESC
LIMIT 30;

4. 时间线重建

时间线重建是将所有浏览器活动按时间顺序排列,识别关键时间节点和行为模式。

SELECT
    datetime((v.visit_time / 1000000) - 11644473600, 'unixepoch', 'localtime') AS visit_time,
    u.url,
    u.title,
    CASE v.transition
        WHEN 0 THEN 'TYPED'
        WHEN 1 THEN 'LINK'
        WHEN 2 THEN 'BOOKMARK'
        WHEN 3 THEN 'SUBFRAME'
        WHEN 4 THEN 'REDIRECT'
        WHEN 5 THEN 'RELOAD'
        WHEN 6 THEN 'KEYWORD'
        WHEN 7 THEN 'GENERATE'
        ELSE 'UNKNOWN'
    END AS transition_type
FROM visits v
JOIN urls u ON v.url = u.id
WHERE v.visit_time BETWEEN ? AND ?
ORDER BY v.visit_time ASC;

5. 关键词搜索分析

用户在搜索引擎中输入的关键词是高价值的取证数据,可以揭示用户的搜索意图。

SELECT
    ks.term,
    u.url,
    u.title,
    datetime((u.last_visit_time / 1000000) - 11644473600, 'unixepoch', 'localtime') AS search_time
FROM keyword_search_terms ks
JOIN urls u ON ks.url_id = u.id
ORDER BY u.last_visit_time DESC
LIMIT 100;

高频搜索词分析

SELECT
    term,
    COUNT(*) AS search_count
FROM keyword_search_terms
GROUP BY term
ORDER BY search_count DESC
LIMIT 50;

6. 隐藏历史记录检测

攻击者或恶意软件可能尝试隐藏浏览器历史记录,以规避取证分析。常见的隐藏手段包括:

直接修改数据库

攻击者可能直接修改 SQLite 数据库中的记录,将 hidden 字段设为 1,或直接删除特定记录。

使用浏览器的清除功能

浏览器的"清除浏览数据"功能会删除历史记录、Cookie 和缓存。但删除操作通常不会立即覆盖数据,而是将记录标记为可重用空间。

检测隐藏记录的方法

SELECT * FROM urls WHERE hidden != 0;

SELECT * FROM urls WHERE visit_count = 0 AND typed_count > 0;

SELECT * FROM urls WHERE last_visit_time = 0 AND visit_count > 0;

检测数据库被篡改的痕迹

检查 SQLite 数据库的 PRAGMA journal_modePRAGMA integrity_check

PRAGMA journal_mode;
PRAGMA integrity_check;
PRAGMA page_count;
PRAGMA freelist_count;

如果 freelist_count 的值异常高,可能表明数据库中的大量记录被删除。


Chrome 和 Edge 的 Cookie 存储在 Cookies SQLite 数据库中。每条 Cookie 记录包含域名、名称、值、过期时间等信息。

SELECT
    host_key,
    name,
    value,
    path,
    expires_utc,
    is_secure,
    is_httponly,
    samesite,
    datetime((expires_utc / 1000000) - 11644473600, 'unixepoch', 'localtime') AS expiry_time,
    datetime((creation_utc / 1000000) - 11644473600, 'unixepoch', 'localtime') AS creation_time
FROM cookies
ORDER BY creation_utc DESC
LIMIT 100;

关键字段解读:

字段说明取证意义
host_keyCookie 所属的域名识别 Cookie 来源
encrypted_value加密的 Cookie 值需要解密才能获取明文
is_secure是否仅通过 HTTPS 传输安全 Cookie 的标志
is_httponly是否禁止 JavaScript 访问防止 XSS 窃取的标志
samesiteSameSite 策略CSRF 防护的标志
persistent是否为持久化 Cookie区分 Session 和 Persistent

Firefox 的 Cookie 存储在 cookies.sqlite 数据库的 moz_cookies 表中:

SELECT
    host,
    name,
    value,
    path,
    expiry,
    isSecure,
    isHttpOnly,
    datetime(expiry, 'unixepoch', 'localtime') AS expiry_time,
    datetime(lastAccessed / 1000000, 'unixepoch', 'localtime') AS last_access
FROM moz_cookies
ORDER BY lastAccessed DESC
LIMIT 100;

Chrome Cookie 加密

Chrome 对 Cookie 的加密方式因操作系统而异:

操作系统加密方式说明
WindowsDPAPI使用 os_crypt.encrypted_key 中的密钥
macOSKeychain存储在 Chrome Safe Storage
LinuxPBKDF2密钥来自 libsecret 或默认密码

在 Windows 上解密 Chrome Cookie 的 Python 示例:

import sqlite3
import os
import json
import base64
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes

def get_encryption_key(browser_profile_path):
    local_state_path = os.path.join(
        os.path.dirname(browser_profile_path),
        os.path.basename(os.path.dirname(browser_profile_path)),
        os.path.join(browser_profile_path, '..', '..', 'Local State')
    )
    local_state_path = os.path.normpath(local_state_path)
    with open(local_state_path, 'r', encoding='utf-8') as f:
        local_state = json.load(f)
    encrypted_key = base64.b64decode(local_state['os_crypt']['encrypted_key'])
    encrypted_key = encrypted_key[5:]
    import subprocess
    result = subprocess.run(
        ['powershell', '-command',
         f'(ConvertTo-SecureString -String ([System.Text.Encoding]::Default.GetString([Convert]::FromBase64String("{base64.b64encode(encrypted_key).decode()}"))) -AsPlainText -Force | ConvertFrom-SecureString)'],
        capture_output=True, text=True
    )
    return result.stdout.strip()

def decrypt_cookie_value(encrypted_value, key):
    if encrypted_value[:3] == b'v10' or encrypted_value[:3] == b'v11':
        nonce = encrypted_value[3:15]
        ciphertext = encrypted_value[15:]
        aesgcm = AESGCM(key)
        return aesgcm.decrypt(nonce, ciphertext, None).decode('utf-8')
    return encrypted_value.decode('utf-8', errors='replace')

def extract_cookies(db_path):
    conn = sqlite3.connect(db_path)
    cursor = conn.cursor()
    cursor.execute("""
        SELECT host_key, name, encrypted_value, path,
               datetime((expires_utc / 1000000) - 11644473600, 'unixepoch', 'localtime'),
               is_secure, is_httponly
        FROM cookies
        WHERE host_key LIKE '%example.com%'
        ORDER BY expires_utc DESC
    """)
    cookies = []
    for row in cursor.fetchall():
        host, name, enc_value, path, expiry, secure, httponly = row
        cookies.append({
            'host': host,
            'name': name,
            'path': path,
            'expiry': expiry,
            'secure': bool(secure),
            'httponly': bool(httponly),
            'encrypted': bool(enc_value)
        })
    conn.close()
    return cookies
特征Session CookiePersistent Cookie
过期时间浏览器关闭后删除设定的过期时间
存储位置内存中磁盘上的 SQLite 数据库
取证价值只能从运行中的浏览器内存提取可以从磁盘数据库中提取
生命周期会话级别数天到数年

取证要点:Session Cookie 虽然在浏览器关闭后从内存中消失,但如果浏览器在运行时崩溃或被强制终止,Session Cookie 可能被写入磁盘的 Session Storage 中。

第三方 Cookie 由嵌入在主页面中的外部资源(如广告、追踪器、社交插件)设置。在取证分析中,第三方 Cookie 可以揭示:

  • 用户访问了哪些包含特定第三方追踪器的网站
  • 广告网络的追踪模式
  • 潜在的跨站追踪行为
SELECT
    host_key,
    COUNT(*) AS cookie_count,
    MIN(creation_utc) AS first_seen,
    MAX(expires_utc) AS last_expiry
FROM cookies
WHERE host_key NOT LIKE '%google.com%'
  AND host_key NOT LIKE '%microsoft.com%'
  AND host_key NOT LIKE '%mozilla.org%'
GROUP BY host_key
ORDER BY cookie_count DESC
LIMIT 30;

恶意 Cookie 可能用于持久化恶意代码、窃取会话信息或进行 C2 通信。检测恶意 Cookie 的特征包括:

  • 异常的 Cookie 值长度:超长的 Cookie 值可能包含编码后的恶意数据
  • Base64 编码的 Cookie 值:检查 Cookie 值是否为有效的 Base64 编码
  • 与已知恶意域名关联的 Cookie:通过威胁情报匹配 Cookie 的域名
  • 异常的 Cookie 属性组合:如设置了 HttpOnly 但没有 Secure 的 Cookie
SELECT
    host_key,
    name,
    LENGTH(encrypted_value) AS value_length,
    is_secure,
    is_httponly,
    datetime((creation_utc / 1000000) - 11644473600, 'unixepoch', 'localtime') AS created
FROM cookies
WHERE LENGTH(encrypted_value) > 1024
ORDER BY LENGTH(encrypted_value) DESC
LIMIT 20;

0x05 缓存分析

1. Chrome/Edge 缓存结构

Chrome 和 Edge 的缓存存储在 Default/Cache/Cache_Data 目录下。缓存使用自定义的索引格式(index 文件)和分块存储格式(.db.0.1.2 等文件)。

缓存文件命名规则:

文件说明
index缓存索引文件,记录所有缓存条目
data_0 - data_11缓存数据分块文件
data_sorted排序后的数据索引

缓存条目的元数据包含:

  • URL:缓存资源的完整 URL
  • HTTP 头部:Content-Type、Last-Modified、Cache-Control 等
  • 缓存时间:资源被缓存的时间
  • 文件大小:缓存资源的大小
  • 过期时间:缓存资源的过期时间

2. Firefox 缓存结构

Firefox 的缓存存储在 cache2 目录下:

路径说明
cache2/entries/缓存条目元数据
cache2/data/缓存数据文件

Firefox 的缓存条目使用类似于 MHTML 的格式存储,每个条目包含完整的 HTTP 响应(状态行、头部、正文)。

3. 缓存文件恢复

缓存文件即使在浏览器清除缓存后,也可能通过磁盘恢复工具提取。原因是浏览器清除缓存时通常只是删除索引文件,而不一定立即覆盖数据块。

Chrome 缓存恢复

Chrome 的缓存使用 disk_cache 库,其内部结构是一个 key-value 存储系统。恢复步骤:

  1. 提取 Cache_Data 目录下的所有文件
  2. 使用 chromecacheview(NirSoft)或 bulk_extractor 解析缓存
  3. 识别缓存中的 HTML 页面、JavaScript 文件、图像等资源

Firefox 缓存恢复

Firefox 的 cache2 目录中的条目可以直接复制。每个条目是一个独立的文件,包含完整的 HTTP 响应。使用 about:cache 可以查看 Firefox 的缓存内容。

4. 缓存时间线分析

缓存时间线可以揭示用户在特定时间段内访问了哪些网站的哪些资源。

import os
import struct
import datetime

def parse_chrome_cache_index(index_path):
    results = []
    with open(index_path, 'rb') as f:
        header = f.read(12)
        if len(header) < 12:
            return results
        signature = struct.unpack_from('<I', header, 0)[0]
        if signature != 0xC10343C2:
            return results
        flags = struct.unpack_from('<H', header, 4)[0]
        entry_count = struct.unpack_from('<I', header, 6)[0]
        table_size = struct.unpack_from('<I', header, 8)[0]
        f.seek(table_size * 4, 1)
        for _ in range(entry_count):
            entry_header = f.read(25)
            if len(entry_header) < 25:
                break
            hash_value = struct.unpack_from('<I', entry_header, 0)[0]
            ref_count = struct.unpack_from('<H', entry_header, 4)[0]
            state = struct.unpack_from('<B', entry_header, 6)[0]
            key_length = struct.unpack_from('<H', entry_header, 13)[0]
            key_data = f.read(key_length)
            try:
                url = key_data.decode('utf-8', errors='replace')
            except Exception:
                url = str(key_data)
            results.append({
                'hash': hash_value,
                'state': state,
                'url': url
            })
    return results

5. 已删除网页缓存恢复

当浏览器清除缓存后,数据块可能仍然存在于磁盘上。取证恢复的方法包括:

  1. 使用磁盘镜像工具:先对磁盘进行完整镜像,然后在镜像上进行文件恢复
  2. 分析 SQLite WAL 文件:WAL 文件可能包含缓存操作的记录
  3. 检查浏览器的 Session Storage:Session Storage 中可能包含当前会话中访问的页面数据
  4. 使用取证工具:Autopsy、FTK Imager 等工具可以从磁盘镜像中恢复已删除的缓存文件

0x06 下载记录分析

1. Chrome/Edge 下载记录

Chrome 和 Edge 的下载记录存储在 History 数据库的 downloadsdownloads_url_chains 表中。

下载记录查询

SELECT
    d.id,
    d.guid,
    d.target_path,
    d.target_bytes,
    d.start_time,
    d.finished_time,
    d.received_bytes,
    d.state,
    d.danger_type,
    d.interrupt_reason,
    d.opened,
    datetime((d.start_time / 1000000) - 11644473600, 'unixepoch', 'localtime') AS download_start,
    datetime((d.finished_time / 1000000) - 11644473600, 'unixepoch', 'localtime') AS download_end,
    duc.chain
FROM downloads d
JOIN (
    SELECT download_id,
           GROUP_CONCAT(url, ' -> ') AS chain
    FROM downloads_url_chains
    GROUP BY download_id
) duc ON d.id = duc.download_id
ORDER BY d.start_time DESC;

downloads 表关键字段:

字段说明取证意义
target_path下载文件保存路径文件落地位置
target_bytes文件总大小判断下载是否完整
received_bytes已接收字节数判断下载是否完成
start_time下载开始时间时间线分析
finished_time下载完成时间时间线分析
state下载状态0=进行中,1=完成,2=取消
danger_type危险类型0=无,1=URL,2=恶意软件
interrupt_reason中断原因识别下载异常
opened是否被打开过判断用户是否执行了下载文件

2. Firefox 下载记录

Firefox 的下载记录存储在 places.sqlite 数据库中。Firefox 105+ 版本使用 moz_downloads 表:

SELECT
    d.id,
    d.source,
    d.target,
    d.contentType,
    d.totalBytes,
    d.currentBytes,
    datetime(d.startTime / 1000000, 'unixepoch', 'localtime') AS start_time,
    datetime(d.endTime / 1000000, 'unixepoch', 'localtime') AS end_time,
    d.state,
    d.referrer,
    p.url AS page_url
FROM moz_downloads d
LEFT JOIN moz_places p ON d.place_id = p.id
ORDER BY d.startTime DESC;

3. 下载文件来源分析

下载来源分析是将下载记录与浏览器历史记录关联,还原用户从哪个页面触发了下载。

SELECT
    datetime((d.start_time / 1000000) - 11644473600, 'unixepoch', 'localtime') AS download_time,
    duc.chain AS url_chain,
    d.target_path,
    d.target_bytes,
    CASE d.state
        WHEN 0 THEN 'IN_PROGRESS'
        WHEN 1 THEN 'COMPLETE'
        WHEN 2 THEN 'CANCELLED'
        ELSE 'UNKNOWN'
    END AS download_state,
    CASE d.danger_type
        WHEN 0 THEN 'SAFE'
        WHEN 1 THEN 'DANGEROUS_URL'
        WHEN 2 THEN 'DANGEROUS_CONTENT'
        WHEN 3 THEN 'UNCOMMON'
        WHEN 4 THEN 'POTENTIALLY_UNWANTED'
        WHEN 5 THEN 'BLOCKED'
        WHEN 6 THEN 'POLICY'
        WHEN 7 THEN 'ACCEPTED'
        ELSE 'UNKNOWN'
    END AS danger_level
FROM downloads d
JOIN downloads_url_chains duc ON d.id = duc.download_id
WHERE d.danger_type > 0
ORDER BY d.start_time DESC;

4. 下载时间线重建

SELECT
    datetime((d.start_time / 1000000) - 11644473600, 'unixepoch', 'localtime') AS download_start,
    datetime((d.finished_time / 1000000) - 11644473600, 'unixepoch', 'localtime') AS download_end,
    ROUND((d.finished_time - d.start_time) / 1000000.0, 2) AS duration_seconds,
    d.target_path,
    ROUND(d.target_bytes / 1024.0, 2) AS size_kb,
    CASE d.opened
        WHEN 0 THEN 'NOT_OPENED'
        WHEN 1 THEN 'OPENED'
        ELSE 'UNKNOWN'
    END AS was_opened
FROM downloads d
WHERE d.state = 1
ORDER BY d.start_time ASC;

5. 下载文件哈希提取

Chrome 的下载记录不直接存储文件哈希,但可以通过以下方式获取:

  1. 从下载记录中获取文件落地路径
  2. 使用取证工具计算落地文件的 MD5/SHA256 哈希
  3. 将哈希与威胁情报平台(VirusTotal、AlienVault OTX)关联
SELECT
    d.id,
    d.target_path,
    d.target_bytes,
    datetime((d.start_time / 1000000) - 11644473600, 'unixepoch', 'localtime') AS download_time,
    u.url AS source_url
FROM downloads d
JOIN downloads_url_chains duc ON d.id = duc.download_id
JOIN urls u ON duc.url_id = u.id
WHERE d.danger_type > 0 OR d.interrupt_reason != 0
ORDER BY d.start_time DESC;

0x07 扩展分析

1. 浏览器扩展架构

浏览器扩展是浏览器取证中经常被忽视但极为重要的数据源。恶意扩展可以:

  • 读取和修改所有网页内容
  • 拦截和转发网络请求
  • 窃取 Cookie 和会话令牌
  • 注入恶意脚本到任意页面
  • 记录用户的键盘输入
  • 访问本地文件系统

Chrome 扩展的核心组件:

组件说明
manifest.json扩展的配置文件,声明权限、脚本、图标等
background.js后台脚本,在浏览器后台持续运行
content.js内容脚本,注入到网页中执行
popup.html/js弹出页面,用户点击扩展图标时显示
options.html/js选项页面,扩展的设置界面
IndexedDB扩展的本地数据存储

2. Chrome 扩展存储结构

Chrome 扩展的数据存储在 Default/Extensions/<extension_id>/ 目录下:

Default/Extensions/
    <extension_id>/
        <version>/
            manifest.json
            background.js
            content.js
            ...

扩展的安装记录存储在 Preferences 文件中:

SELECT json_extract(value, '$.name') AS extension_name,
       json_extract(value, '$.version') AS version,
       json_extract(value, '$.description') AS description
FROM preferences
WHERE path LIKE '%Extensions%';

扩展的安装时间可以从文件系统时间戳中获取:

ls -la ~/Library/Application\ Support/Google/Chrome/Default/Extensions/*/
stat -f "%Sm %N" ~/Library/Application\ Support/Google/Chrome/Default/Extensions/*/*

3. Firefox 扩展存储结构

Firefox 扩展存储在 extensions/ 目录和 storage/default/ 目录中:

路径说明
extensions/{extension_id}.xpi扩展包文件
storage/default/moz-extension+++<id>/扩展的本地存储
storage/default/moz-extension+++<id>/idb/IndexedDB 存储

4. 恶意扩展识别

识别恶意扩展的方法:

权限分析

检查 manifest.json 中声明的权限。以下权限组合高度可疑:

  • "<all_urls>" + "tabs" + "cookies":可以访问所有网站的数据
  • "webRequest" + "webRequestBlocking" + "<all_urls>":可以拦截和修改所有网络请求
  • "nativeMessaging":可以与本地程序通信
  • "debugger":可以调试任意标签页

代码分析

  • 检查 background.js 中是否包含 XMLHttpRequestfetch 调用外部 URL
  • 检查是否使用 eval()new Function() 动态执行代码
  • 检查是否访问 chrome.webRequest API 拦截请求

行为分析

  • 检查扩展是否频繁访问网络
  • 检查扩展是否读取 Cookie 数据
  • 检查扩展是否修改网页 DOM
find ~/Library/Application\ Support/Google/Chrome/Default/Extensions/ \
    -name "manifest.json" -exec grep -l "webRequest" {} \;

5. 扩展权限分析

import json
import os
import glob

def analyze_extension_permissions(extensions_dir):
    results = []
    manifest_files = glob.glob(os.path.join(extensions_dir, '**', 'manifest.json'), recursive=True)
    for manifest_path in manifest_files:
        try:
            with open(manifest_path, 'r', encoding='utf-8') as f:
                manifest = json.load(f)
            permissions = manifest.get('permissions', [])
            host_permissions = manifest.get('host_permissions', manifest.get('permissions', []))
            name = manifest.get('name', 'Unknown')
            version = manifest.get('version', 'Unknown')
            suspicious_combos = []
            if '<all_urls>' in permissions and 'cookies' in permissions:
                suspicious_combos.append('ALL_URLS + COOKIES')
            if 'webRequest' in permissions and 'webRequestBlocking' in permissions:
                suspicious_combos.append('WEB_REQUEST_INTERCEPT')
            if 'nativeMessaging' in permissions:
                suspicious_combos.append('NATIVE_MESSAGING')
            if 'debugger' in permissions:
                suspicious_combos.append('DEBUGGER_ACCESS')
            results.append({
                'name': name,
                'version': version,
                'permissions': permissions,
                'suspicious': suspicious_combos,
                'manifest_path': manifest_path
            })
        except (json.JSONDecodeError, IOError):
            continue
    return results

6. 扩展网络请求分析

Chrome 的扩展可以通过 webRequest API 发起网络请求。在取证中,可以检查扩展的网络活动:

  • 检查扩展是否将数据发送到外部服务器
  • 检查扩展是否拦截和转发用户的 HTTP 请求
  • 检查扩展是否修改 HTTP 请求头

通过 Chrome 的 net-internalschrome://net-internals/)可以查看扩展的网络活动日志。


0x08 隐身模式痕迹分析

1. 隐身模式的数据保留范围

隐身模式(Incognito Mode / Private Browsing)的设计目标是在浏览器关闭后不保留浏览历史、Cookie、缓存等数据。但隐身模式并非完全不可追踪——它只是在浏览器层面限制了数据的持久化存储。

隐身模式下不会保存的数据:

数据类型是否保存
浏览历史不保存
Cookie不保存(会话期间在内存中存在)
缓存文件不保存
下载记录保存(文件落地到磁盘)
书签不保存
表单数据不保存
保存的密码不保存

隐身模式下仍然会留下的痕迹:

痕迹类型保存位置
DNS 查询操作系统 DNS 缓存
网络连接操作系统网络日志、路由器日志
文件落地磁盘文件系统
进程创建操作系统进程日志
注册表TypedURLs、UserAssist

2. DNS 缓存痕迹

即使使用隐身模式访问网站,DNS 查询仍然会在操作系统层面留下痕迹。

Windows DNS 缓存查看

ipconfig /displaydns

DNS 缓存中会记录所有最近解析的域名,包括隐身模式下访问的域名。缓存条目包含域名、解析结果和剩余生存时间。

Linux DNS 缓存查看

systemd-resolve --statistics

3. 网络连接痕迹

隐身模式下的网络连接会在以下位置留下痕迹:

  • 操作系统网络状态netstatss 等工具可以查看当前网络连接
  • Windows 事件日志:DNS 客户端事件日志(Microsoft-Windows-DNS-Client/Operational)
  • 防火墙日志:Windows 防火墙和第三方防火墙的连接日志
  • 路由器日志:家庭或企业路由器的访问日志

4. 操作系统层面痕迹

Windows 注册表痕迹

  • NTUSER.DAT\Software\Microsoft\Internet Explorer\TypedURLs:用户手动输入的 URL(IE/Edge)
  • NTUSER.DAT\Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs:最近打开的文档
  • NTUSER.DAT\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU:运行对话框历史

文件系统痕迹

  • 下载的文件会落地到磁盘,文件时间戳(创建、修改、访问时间)记录了访问时间
  • 临时文件目录中可能残留隐身模式访问的资源

5. 扩展在隐身模式下的行为

Chrome 的扩展默认在隐身模式下禁用。用户可以选择允许特定扩展在隐身模式下运行。如果扩展被允许在隐身模式下运行,其行为不会因为隐身模式而受到限制。

检查扩展是否在隐身模式下运行:

SELECT json_extract(value, '$.incognito_content_scripts') AS incognito_content,
       json_extract(value, '$.incognito') AS incognito_mode
FROM preferences;

6. 隐身模式取证方法

  1. DNS 缓存提取:使用 ipconfig /displaydns 或第三方工具提取 DNS 缓存
  2. 内存取证:在浏览器运行时采集内存,从内存中提取历史记录和 Cookie
  3. 网络流量捕获:使用 Wireshark 等工具捕获隐身模式下的网络流量
  4. 浏览器内部数据:使用 chrome://history/#incognito 查看隐身模式标签页的当前历史(仅在浏览器运行时可见)
  5. JavaScript 控制台:在隐身模式标签页中打开开发者工具,可以通过 performance.getEntries() 查看当前会话的资源加载记录

0x09 浏览器同步数据取证

1. Chrome 同步机制

Chrome 同步将浏览器数据(历史记录、Cookie、密码、书签、扩展等)上传到 Google 的服务器。同步数据包括:

同步数据类型说明
CHROME_HISTORY浏览历史
CHROME_BOOKMARKS书签
CHROME_PASSWORDS保存的密码
CHROME_COOKIESCookie
CHROME_EXTENSIONS已安装的扩展
CHROME_PREFERENCES浏览器设置
CHROME_AUTOFILL自动填充数据

同步数据在取证中的价值:

  • 可以从一台设备上获取另一台设备的浏览历史
  • 可以获取已经被本地清除的浏览器数据
  • 可以验证用户在不同设备上的行为一致性

本地同步状态检查

检查 Chrome 的同步配置:

Default/Preferences → sync → datatype_configs
SELECT json_extract(Preferences, '$.sync') AS sync_config
FROM preferences;

2. Firefox 同步机制

Firefox Sync 使用端到端加密,数据在客户端加密后上传到 Mozilla 服务器。这意味着:

  • 除非获得用户的 Firefox 账号密码,否则无法解密同步数据
  • Mozilla 服务器上存储的是加密后的数据
  • 取证人员无法直接从 Mozilla 服务器获取同步数据

Firefox 同步的加密机制:

  • 使用用户名和密码派生加密密钥
  • 使用 AES-256-CBC 加密数据
  • 使用 HMAC-SHA256 验证数据完整性

3. Edge 同步机制

Edge 的同步机制与 Chrome 类似,数据存储在 Microsoft 账号的云端。同步数据类型与 Chrome 基本一致,额外包含:

  • Edge 的收藏夹数据
  • Edge 的标签页同步
  • Edge 的密码健康报告

4. 同步数据在取证中的价值

同步数据在以下场景中具有独特的取证价值:

  • 多设备场景:嫌疑人使用多台设备时,同步数据可以提供跨设备的行为视图
  • 数据恢复:本地数据被清除后,同步数据可能仍然存在于服务器端
  • 行为验证:通过对比不同设备的同步数据,可以验证用户是否使用了多台设备
  • 时间线扩展:同步数据可能包含本地数据库中已过期的早期历史记录

5. 从 Google 服务器提取同步数据

在法律授权的情况下,可以通过以下方式获取 Google 服务器上的同步数据:

  1. Google Takeout:Google 提供的用户数据导出服务,可以导出浏览器历史、书签、密码等
  2. 法律传票:通过法律程序向 Google 发出传票,要求提供特定账号的同步数据
  3. Google Transparency Report:查询 Google 收到的用户数据请求

Google Takeout 可以导出的数据范围:

数据类型说明
Chrome History浏览历史
Chrome Bookmarks书签
Chrome Passwords保存的密码(加密)
Chrome Settings浏览器设置
Chrome Autofill自动填充数据
Chrome Extensions扩展数据

0x10 浏览器自动填充与密码分析

1. Chrome/Edge 自动填充数据

Chrome 和 Edge 的自动填充数据存储在 Web Data 数据库中:

自动填充表单数据查询

SELECT
    name,
    value,
    date_created,
    date_last_used,
    usage_count,
    datetime((date_created / 1000000) - 11644473600, 'unixepoch', 'localtime') AS created,
    datetime((date_last_used / 1000000) - 11644473600, 'unixepoch', 'localtime') AS last_used
FROM autofill
ORDER BY usage_count DESC
LIMIT 50;

自动填充配置数据查询

SELECT
    '姓名' AS data_type,
    first_name || ' ' || middle_name || ' ' || last_name AS value
FROM autofill_profile_names
UNION ALL
SELECT
    '地址' AS data_type,
    street_address || ', ' || city || ', ' || state || ' ' || zip_code AS value
FROM autofill_profile_addresses
UNION ALL
SELECT
    '电话' AS data_type,
    number AS value
FROM autofill_profile_phones;

2. Firefox 自动填充数据

Firefox 的表单历史存储在 formhistory.sqlite 中:

SELECT
    fieldname,
    value,
    timesUsed,
    datetime(firstUsed / 1000000, 'unixepoch', 'localtime') AS first_used,
    datetime(lastUsed / 1000000, 'unixepoch', 'localtime') AS last_used
FROM moz_formhistory
ORDER BY timesUsed DESC
LIMIT 50;

3. 保存密码的加密存储

Chrome 保存的密码存储在 Login Data 数据库的 logins 表中:

SELECT
    origin_url,
    username_value,
    CASE
        WHEN password_value IS NOT NULL THEN 'ENCRYPTED'
        ELSE 'EMPTY'
    END AS password_status,
    date_created,
    date_last_used,
    datetime((date_created / 1000000) - 11644473600, 'unixepoch', 'localtime') AS created,
    datetime((date_last_used / 1000000) - 11644473600, 'unixepoch', 'localtime') AS last_used,
    times_used,
    blacklisted_by_user
FROM logins
WHERE blacklisted_by_user = 0
ORDER BY times_used DESC
LIMIT 50;

Firefox 保存的密码存储在 logins.json 文件中:

import json

def parse_firefox_logins(logins_path):
    with open(logins_path, 'r', encoding='utf-8') as f:
        data = json.load(f)
    logins = data.get('logins', [])
    results = []
    for login in logins:
        results.append({
            'hostname': login.get('hostname', ''),
            'httpRealm': login.get('httpRealm', ''),
            'formSubmitURL': login.get('formSubmitURL', ''),
            'usernameEncrypted': bool(login.get('encryptedUsername', '')),
            'passwordEncrypted': bool(login.get('encryptedPassword', '')),
            'timeCreated': login.get('timeCreated', 0),
            'timeLastUsed': login.get('timeLastUsed', 0),
            'timesUsed': login.get('timesUsed', 0)
        })
    return results

4. 自动填充数据在画像中的价值

自动填充数据是构建用户画像的高价值数据源。通过分析自动填充数据,可以获取以下信息:

数据类型画像价值
姓名用户真实姓名
地址用户的居住地址或工作地址
电话号码用户的联系方式
电子邮件用户的邮箱地址
信用卡信息用户的财务信息
表单输入历史用户的搜索和输入模式
保存的密码用户访问的系统和账号

通过分析 usage_countdate_last_used 字段,可以识别用户最常访问的系统和最近的活动模式。


0x11 证据强度分层

浏览器取证中的证据可以按照可信度和证明力分为三个层级。

1. 确认恶意(Confirmation Level)

能够直接确认恶意行为或攻击意图的浏览器证据:

证据类型说明确认条件
下载恶意文件从已知恶意 URL 下载了恶意软件URL 匹配威胁情报 + 文件落地 + 执行痕迹
访问 C2 基础设施访问了已知的 C2 服务器URL 匹配 C2 域名/IP + 非正常业务关联
窃取凭据Cookie/Session 被发送到非授权域名网络流量佐证 + Cookie 域名异常
安装恶意扩展安装了具有恶意行为的浏览器扩展扩展行为分析 + 权限分析 + 网络通信分析
账号异常活动在异常时间或位置登录系统登录时间 + Cookie 会话 + 与已知设备对比

2. 高度可疑(High Suspicion Level)

不能直接确认恶意行为,但强烈暗示可疑意图的浏览器证据:

证据类型说明可疑条件
隐身模式高频使用大量使用隐身模式浏览特定网站浏览量异常 + 与正常业务无关
访问暗网市场访问了 .onion 或暗网相关网站域名匹配暗网市场 + 非技术研究背景
下载黑客工具从已知来源下载了渗透测试工具与用户职责不匹配 + 非授权使用
频繁清除浏览数据频繁清除历史记录和 Cookie清除频率异常 + 与正常行为模式不符
访问恶意重定向多次被重定向到恶意站点重定向链包含恶意域名

3. 需要关注(Attention Level)

需要进一步分析才能判断是否与安全事件相关的浏览器证据:

证据类型说明关注条件
异常的下载模式在非工作时间下载大量文件时间异常 + 文件类型异常
访问竞争对手网站访问了竞争对手的网站与工作无关 + 高频访问
频繁的搜索记录搜索了敏感关键词关键词与安全事件相关
自动填充数据变化自动填充数据频繁更新可能表明使用了多台设备
扩展安装安装了新的浏览器扩展扩展权限异常 + 非公司标准扩展

0x12 公开案例中的浏览器取证

案例一:勒索软件受害者 — 浏览器历史还原攻击链

事件背景

2024 年,某企业遭遇 LockBit 3.0 勒索软件攻击。攻击者通过钓鱼邮件投递恶意附件,最终加密了企业核心数据。在应急响应中,浏览器历史记录成为还原攻击链的关键证据。

浏览器取证发现

  1. 钓鱼页面访问:浏览器历史记录显示员工在攻击前 3 天访问了一个伪装为公司 VPN 登录页面的钓鱼站点
  2. 恶意文件下载:下载记录显示员工从该钓鱼站点下载了一个 ZIP 文件,文件保存到 Downloads 目录
  3. 文件被执行:文件系统时间戳显示 ZIP 文件在下载后 2 分钟内被解压,其中的 .iso 文件被双击打开
  4. 后续访问:浏览器历史中出现多个加密货币交易所的访问记录,表明攻击者可能使用受害者的浏览器访问了赎金支付页面

证据链

钓鱼邮件 → 浏览器访问钓鱼站 → 下载 ZIP → 解压 ISO → 执行恶意代码 → 勒索加密

案例二:内部威胁 — 浏览器下载记录追踪

事件背景

2025 年,某科技公司发现其源代码被泄露到 GitHub 上。安全团队对涉嫌泄露的员工进行取证分析,浏览器下载记录成为关键证据。

浏览器取证发现

  1. 大量代码下载:下载记录显示员工在过去一个月内从内部 GitLab 下载了大量代码仓库
  2. 异常下载时间:下载活动集中在深夜和周末
  3. 文件路径分析:下载文件的路径指向外部 USB 设备的挂载点
  4. Cookie 分析:Cookie 记录显示员工在下载后访问了 GitHub 的上传页面

证据链

GitLab 代码下载 → 下载到 USB 路径 → 上传到 GitHub → 源代码泄露

事件背景

2025 年,某金融机构遭受针对性钓鱼攻击。攻击者通过窃取员工的会话 Cookie,冒充员工访问内部系统,窃取了客户数据。

浏览器取证发现

  1. 异常 Cookie 来源:Cookie 数据库中出现了从未知 IP 地址设置的会话 Cookie
  2. 异常 URL 访问:浏览器历史显示了从外部 IP 访问内部系统的记录
  3. 登录时间异常:Cookie 的创建时间与员工的正常工作时间不一致
  4. URL 参数异常:部分 URL 包含异常的查询参数,可能是攻击者注入的

证据链

钓鱼页面 → 窃取员工凭据 → 伪造会话 Cookie → 从外部 IP 冒充访问 → 窃取客户数据

0x13 浏览器取证检测自动化与狩猎

1. Chrome 数据库解析脚本

import sqlite3
import os
import json
import csv
from datetime import datetime

CHROME_EPOCH = 11644473600

def chrome_timestamp_to_readable(ts):
    if ts == 0:
        return 'N/A'
    try:
        epoch = (ts / 1000000) - CHROME_EPOCH
        return datetime.utcfromtimestamp(epoch).strftime('%Y-%m-%d %H:%M:%S')
    except (ValueError, OSError):
        return 'INVALID'

def extract_chrome_history(profile_path):
    history_db = os.path.join(profile_path, 'History')
    if not os.path.exists(history_db):
        return []
    conn = sqlite3.connect(history_db)
    cursor = conn.cursor()
    cursor.execute('''
        SELECT u.url, u.title, u.visit_count, u.typed_count,
               u.last_visit_time, v.visit_time, v.transition
        FROM urls u
        LEFT JOIN visits v ON u.id = v.url
        ORDER BY v.visit_time DESC
    ''')
    results = []
    for row in cursor.fetchall():
        url, title, visit_count, typed_count, last_visit, visit_time, transition = row
        results.append({
            'url': url,
            'title': title,
            'visit_count': visit_count,
            'typed_count': typed_count,
            'last_visit': chrome_timestamp_to_readable(last_visit or 0),
            'visit_time': chrome_timestamp_to_readable(visit_time or 0),
            'transition': transition
        })
    conn.close()
    return results

def extract_chrome_cookies(profile_path):
    cookies_db = os.path.join(profile_path, 'Cookies')
    if not os.path.exists(cookies_db):
        return []
    conn = sqlite3.connect(cookies_db)
    cursor = conn.cursor()
    cursor.execute('''
        SELECT host_key, name, path,
               datetime((expires_utc / 1000000) - 11644473600, 'unixepoch', 'localtime'),
               is_secure, is_httponly,
               datetime((creation_utc / 1000000) - 11644473600, 'unixepoch', 'localtime')
        FROM cookies
        ORDER BY creation_utc DESC
    ''')
    results = []
    for row in cursor.fetchall():
        host, name, path, expiry, secure, httponly, created = row
        results.append({
            'host': host,
            'name': name,
            'path': path,
            'expiry': expiry,
            'secure': bool(secure),
            'httponly': bool(httponly),
            'created': created
        })
    conn.close()
    return results

def extract_chrome_downloads(profile_path):
    history_db = os.path.join(profile_path, 'History')
    if not os.path.exists(history_db):
        return []
    conn = sqlite3.connect(history_db)
    cursor = conn.cursor()
    cursor.execute('''
        SELECT d.id, d.target_path, d.target_bytes, d.received_bytes,
               d.state, d.danger_type, d.interrupt_reason, d.opened,
               datetime((d.start_time / 1000000) - 11644473600, 'unixepoch', 'localtime'),
               datetime((d.finished_time / 1000000) - 11644473600, 'unixepoch', 'localtime'),
               duc.chain
        FROM downloads d
        LEFT JOIN (
            SELECT download_id, GROUP_CONCAT(url, ' -> ') AS chain
            FROM downloads_url_chains
            GROUP BY download_id
        ) duc ON d.id = duc.download_id
        ORDER BY d.start_time DESC
    ''')
    results = []
    for row in cursor.fetchall():
        did, path, target_bytes, recv_bytes, state, danger, interrupt, opened, start, end, chain = row
        results.append({
            'id': did,
            'target_path': path,
            'target_bytes': target_bytes,
            'received_bytes': recv_bytes,
            'state': state,
            'danger_type': danger,
            'interrupt_reason': interrupt,
            'opened': opened,
            'start_time': start,
            'end_time': end,
            'url_chain': chain
        })
    conn.close()
    return results

def export_to_csv(data, output_path):
    if not data:
        return
    keys = data[0].keys()
    with open(output_path, 'w', newline='', encoding='utf-8') as f:
        writer = csv.DictWriter(f, fieldnames=keys)
        writer.writeheader()
        writer.writerows(data)

def analyze_chrome_profile(profile_path, output_dir):
    os.makedirs(output_dir, exist_ok=True)
    history = extract_chrome_history(profile_path)
    export_to_csv(history, os.path.join(output_dir, 'chrome_history.csv'))
    cookies = extract_chrome_cookies(profile_path)
    export_to_csv(cookies, os.path.join(output_dir, 'chrome_cookies.csv'))
    downloads = extract_chrome_downloads(profile_path)
    export_to_csv(downloads, os.path.join(output_dir, 'chrome_downloads.csv'))
    summary = {
        'profile': profile_path,
        'history_entries': len(history),
        'cookie_entries': len(cookies),
        'download_entries': len(downloads)
    }
    with open(os.path.join(output_dir, 'summary.json'), 'w', encoding='utf-8') as f:
        json.dump(summary, f, indent=2, ensure_ascii=False)
    return summary

2. Firefox 数据库解析脚本

import sqlite3
import os
import json
import csv
from datetime import datetime

def firefox_timestamp_to_readable(ts):
    if ts == 0:
        return 'N/A'
    try:
        return datetime.utcfromtimestamp(ts / 1000000).strftime('%Y-%m-%d %H:%M:%S')
    except (ValueError, OSError):
        return 'INVALID'

def extract_firefox_history(profile_path):
    places_db = os.path.join(profile_path, 'places.sqlite')
    if not os.path.exists(places_db):
        return []
    conn = sqlite3.connect(places_db)
    cursor = conn.cursor()
    cursor.execute('''
        SELECT p.url, p.title, p.visit_count, p.last_visit_date,
               hv.visit_type,
               datetime(hv.visit_date / 1000000, 'unixepoch', 'localtime')
        FROM moz_places p
        LEFT JOIN moz_historyvisits hv ON p.id = hv.place_id
        WHERE p.visit_count > 0
        ORDER BY hv.visit_date DESC
    ''')
    results = []
    for row in cursor.fetchall():
        url, title, visit_count, last_visit, visit_type, visit_time = row
        results.append({
            'url': url,
            'title': title,
            'visit_count': visit_count,
            'last_visit': firefox_timestamp_to_readable(last_visit or 0),
            'visit_type': visit_type,
            'visit_time': visit_time
        })
    conn.close()
    return results

def extract_firefox_cookies(profile_path):
    cookies_db = os.path.join(profile_path, 'cookies.sqlite')
    if not os.path.exists(cookies_db):
        return []
    conn = sqlite3.connect(cookies_db)
    cursor = conn.cursor()
    cursor.execute('''
        SELECT host, name, value, path, expiry, isSecure, isHttpOnly,
               datetime(expiry, 'unixepoch', 'localtime'),
               datetime(lastAccessed / 1000000, 'unixepoch', 'localtime')
        FROM moz_cookies
        ORDER BY lastAccessed DESC
    ''')
    results = []
    for row in cursor.fetchall():
        host, name, value, path, expiry, secure, httponly, expiry_time, last_access = row
        results.append({
            'host': host,
            'name': name,
            'has_value': bool(value),
            'path': path,
            'expiry': expiry_time,
            'secure': bool(secure),
            'httponly': bool(httponly),
            'last_access': last_access
        })
    conn.close()
    return results

def extract_firefox_form_history(profile_path):
    form_db = os.path.join(profile_path, 'formhistory.sqlite')
    if not os.path.exists(form_db):
        return []
    conn = sqlite3.connect(form_db)
    cursor = conn.cursor()
    cursor.execute('''
        SELECT fieldname, value, timesUsed,
               datetime(firstUsed / 1000000, 'unixepoch', 'localtime'),
               datetime(lastUsed / 1000000, 'unixepoch', 'localtime')
        FROM moz_formhistory
        ORDER BY timesUsed DESC
    ''')
    results = []
    for row in cursor.fetchall():
        fieldname, value, times_used, first_used, last_used = row
        results.append({
            'fieldname': fieldname,
            'value': value,
            'times_used': times_used,
            'first_used': first_used,
            'last_used': last_used
        })
    conn.close()
    return results

def export_to_csv(data, output_path):
    if not data:
        return
    keys = data[0].keys()
    with open(output_path, 'w', newline='', encoding='utf-8') as f:
        writer = csv.DictWriter(f, fieldnames=keys)
        writer.writeheader()
        writer.writerows(data)

def analyze_firefox_profile(profile_path, output_dir):
    os.makedirs(output_dir, exist_ok=True)
    history = extract_firefox_history(profile_path)
    export_to_csv(history, os.path.join(output_dir, 'firefox_history.csv'))
    cookies = extract_firefox_cookies(profile_path)
    export_to_csv(cookies, os.path.join(output_dir, 'firefox_cookies.csv'))
    forms = extract_firefox_form_history(profile_path)
    export_to_csv(forms, os.path.join(output_dir, 'firefox_forms.csv'))
    summary = {
        'profile': profile_path,
        'history_entries': len(history),
        'cookie_entries': len(cookies),
        'form_entries': len(forms)
    }
    with open(os.path.join(output_dir, 'summary.json'), 'w', encoding='utf-8') as f:
        json.dump(summary, f, indent=2, ensure_ascii=False)
    return summary

3. 批量浏览器取证脚本

#!/bin/bash

OUTPUT_DIR="$HOME/browser_forensics_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$OUTPUT_DIR"

echo "[+] Browser Forensics Collection Script"
echo "[+] Output directory: $OUTPUT_DIR"

collect_chrome_profiles() {
    local base_dirs=(
        "$HOME/Library/Application Support/Google/Chrome"
        "$HOME/.config/google-chrome"
        "$HOME/AppData/Local/Google/Chrome/User Data"
    )
    for dir in "${base_dirs[@]}"; do
        if [ -d "$dir" ]; then
            echo "[+] Found Chrome at: $dir"
            find "$dir" -maxdepth 1 -name "Default" -o -name "Profile *" | while read profile; do
                profile_name=$(basename "$profile")
                echo "[+] Processing Chrome profile: $profile_name"
                mkdir -p "$OUTPUT_DIR/chrome/$profile_name"
                for db in History "Cookies" "Login Data" "Web Data"; do
                    if [ -f "$profile/$db" ]; then
                        cp "$profile/$db" "$OUTPUT_DIR/chrome/$profile_name/" 2>/dev/null
                        echo "    Copied: $db"
                    fi
                done
                if [ -f "$profile/Preferences" ]; then
                    cp "$profile/Preferences" "$OUTPUT_DIR/chrome/$profile_name/" 2>/dev/null
                    echo "    Copied: Preferences"
                fi
                if [ -f "$profile/Local State" ]; then
                    cp "$dir/Local State" "$OUTPUT_DIR/chrome/$profile_name/" 2>/dev/null
                    echo "    Copied: Local State"
                fi
            done
        fi
    done
}

collect_firefox_profiles() {
    local base_dirs=(
        "$HOME/Library/Application Support/Firefox"
        "$HOME/.mozilla/firefox"
        "$HOME/AppData/Roaming/Mozilla/Firefox"
    )
    for dir in "${base_dirs[@]}"; do
        if [ -d "$dir" ]; then
            echo "[+] Found Firefox at: $dir"
            find "$dir" -maxdepth 1 -type d -name "*.default*" | while read profile; do
                profile_name=$(basename "$profile")
                echo "[+] Processing Firefox profile: $profile_name"
                mkdir -p "$OUTPUT_DIR/firefox/$profile_name"
                for db in places.sqlite cookies.sqlite formhistory.sqlite; do
                    if [ -f "$profile/$db" ]; then
                        cp "$profile/$db" "$OUTPUT_DIR/firefox/$profile_name/" 2>/dev/null
                        echo "    Copied: $db"
                    fi
                done
                if [ -f "$profile/logins.json" ]; then
                    cp "$profile/logins.json" "$OUTPUT_DIR/firefox/$profile_name/" 2>/dev/null
                    echo "    Copied: logins.json"
                fi
                if [ -f "$profile/key4.db" ]; then
                    cp "$profile/key4.db" "$OUTPUT_DIR/firefox/$profile_name/" 2>/dev/null
                    echo "    Copied: key4.db"
                fi
            done
        fi
    done
}

collect_edge_profiles() {
    local base_dirs=(
        "$HOME/Library/Application Support/Microsoft Edge"
        "$HOME/.config/microsoft-edge"
        "$HOME/AppData/Local/Microsoft/Edge/User Data"
    )
    for dir in "${base_dirs[@]}"; do
        if [ -d "$dir" ]; then
            echo "[+] Found Edge at: $dir"
            find "$dir" -maxdepth 1 -name "Default" -o -name "Profile *" | while read profile; do
                profile_name=$(basename "$profile")
                echo "[+] Processing Edge profile: $profile_name"
                mkdir -p "$OUTPUT_DIR/edge/$profile_name"
                for db in History "Cookies" "Login Data" "Web Data"; do
                    if [ -f "$profile/$db" ]; then
                        cp "$profile/$db" "$OUTPUT_DIR/edge/$profile_name/" 2>/dev/null
                        echo "    Copied: $db"
                    fi
                done
            done
        fi
    done
}

echo "[+] Collecting Chrome profiles..."
collect_chrome_profiles

echo "[+] Collecting Firefox profiles..."
collect_firefox_profiles

echo "[+] Collecting Edge profiles..."
collect_edge_profiles

echo "[+] Generating file hashes..."
find "$OUTPUT_DIR" -type f -exec sha256sum {} \; > "$OUTPUT_DIR/file_hashes.txt" 2>/dev/null

echo "[+] Collection complete. Output: $OUTPUT_DIR"
echo "[+] Total files collected: $(find "$OUTPUT_DIR" -type f | wc -l)"

4. Sigma 检测规则

title: Chrome History Database Access
id: b7c8d9e0-2001-0001-abcd-ef1234567801
status: experimental
description: 检测通过非浏览器进程访问 Chrome History 数据库的可疑行为
author: Security Team
date: 2026/07/01
references:
  - https://attack.mitre.org/techniques/T1005/
  - https://attack.mitre.org/techniques/T1213/
logsource:
  category: file_access
  product: windows
detection:
  selection_history:
    TargetFilename|endswith: '\History'
    TargetFilename|contains: '\Google\Chrome\User Data\'
  selection_login:
    TargetFilename|endswith: '\Login Data'
    TargetFilename|contains: '\Google\Chrome\User Data\'
  selection_cookie:
    TargetFilename|endswith: '\Cookies'
    TargetFilename|contains: '\Google\Chrome\User Data\'
  selection_firefox_places:
    TargetFilename|endswith: '\places.sqlite'
    TargetFilename|contains: '\Mozilla\Firefox\Profiles\'
  selection_firefox_cookies:
    TargetFilename|endswith: '\cookies.sqlite'
    TargetFilename|contains: '\Mozilla\Firefox\Profiles\'
  condition: selection_history or selection_login or selection_cookie or selection_firefox_places or selection_firefox_cookies
level: medium
tags:
  - attack.discovery
  - attack.t1005
  - attack.t1213
title: Browser History Cleared - Suspicious Timing
id: b7c8d9e0-2002-0001-abcd-ef1234567802
status: experimental
description: 检测在安全事件时间窗口内清除浏览器历史记录的可疑行为
author: Security Team
date: 2026/07/01
references:
  - https://attack.mitre.org/techniques/T1070/
logsource:
  category: process_creation
  product: windows
detection:
  selection_chrome_clear:
    Image|endswith: '\chrome.exe'
    CommandLine|contains:
      - '--clear-browser-data'
      - 'ClearBrowsingData'
  selection_edge_clear:
    Image|endswith: '\msedge.exe'
    CommandLine|contains:
      - '--clear-browser-data'
      - 'ClearBrowsingData'
  selection_firefox_clear:
    Image|endswith: '\firefox.exe'
    CommandLine|contains:
      - '-ClearPrivateData'
      - '-PlacesShutdownCleaner'
  selection_cmd_del_history:
    Image|endswith: '\cmd.exe'
    CommandLine|contains|all:
      - 'del'
      - 'History'
    CommandLine|contains:
      - '\Google\Chrome\User Data\'
      - '\Mozilla\Firefox\Profiles\'
      - '\Microsoft\Edge\User Data\'
  selection_powershell_remove_history:
    Image|endswith: '\powershell.exe' OR Image|endswith: '\pwsh.exe'
    CommandLine|contains:
      - 'Remove-Item'
    CommandLine|contains:
      - 'History'
      - 'places.sqlite'
  condition: selection_chrome_clear or selection_edge_clear or selection_firefox_clear or selection_cmd_del_history or selection_powershell_remove_history
level: high
tags:
  - attack.defense_evasion
  - attack.t1070
title: Browser Credential Database Accessed by Non-Browser Process
id: b7c8d9e0-2003-0001-abcd-ef1234567803
status: experimental
description: 检测非浏览器进程读取浏览器密码数据库的可疑行为
author: Security Team
date: 2026/07/01
references:
  - https://attack.mitre.org/techniques/T1555/
  - https://attack.mitre.org/techniques/T1003/
logsource:
  category: process_access
  product: windows
detection:
  selection:
    TargetImage|endswith: '\chrome.exe' OR
    TargetImage|endswith: '\msedge.exe' OR
    TargetImage|endswith: '\firefox.exe'
  filter_browser:
    SourceImage|endswith: '\chrome.exe' OR
    SourceImage|endswith: '\msedge.exe' OR
    SourceImage|endswith: '\firefox.exe' OR
    SourceImage|endswith: '\GoogleUpdate.exe' OR
    SourceImage|endswith: '\MicrosoftEdgeUpdate.exe' OR
    SourceImage|endswith: '\crashpad_handler.exe'
  condition: selection and not filter_browser
level: high
tags:
  - attack.credential_access
  - attack.t1555
  - attack.t1003
title: Suspicious Download from Known Malicious Domain
id: b7c8d9e0-2004-0001-abcd-ef1234567804
status: experimental
description: 检测从已知恶意域名下载文件的浏览器行为
author: Security Team
date: 2026/07/01
references:
  - https://attack.mitre.org/techniques/T1189/
  - https://attack.mitre.org/techniques/T1204/
logsource:
  category: network_connection
  product: windows
detection:
  selection_chrome:
    Image|endswith: '\chrome.exe'
    DestinationHostname|endswith:
      - '.xyz'
      - '.top'
      - '.club'
      - '.buzz'
      - '.work'
      - '.click'
      - '.link'
  selection_edge:
    Image|endswith: '\msedge.exe'
    DestinationHostname|endswith:
      - '.xyz'
      - '.top'
      - '.club'
      - '.buzz'
      - '.work'
      - '.click'
      - '.link'
  condition: selection_chrome or selection_edge
level: medium
tags:
  - attack.initial_access
  - attack.t1189
title: Browser Extension Loaded from Non-Standard Path
id: b7c8d9e0-2005-0001-abcd-ef1234567805
status: experimental
description: 检测从非标准路径加载浏览器扩展的可疑行为
author: Security Team
date: 2026/07/01
references:
  - https://attack.mitre.org/techniques/T1176/
logsource:
  category: file_create
  product: windows
detection:
  selection_chrome_ext:
    TargetFilename|contains: '\Google\Chrome\User Data\Default\Extensions\'
    TargetFilename|endswith: '\manifest.json'
  selection_edge_ext:
    TargetFilename|contains: '\Microsoft\Edge\User Data\Default\Extensions\'
    TargetFilename|endswith: '\manifest.json'
  selection_firefox_ext:
    TargetFilename|contains: '\Mozilla\Firefox\Profiles\'
    TargetFilename|endswith: '.xpi'
  selection_temp_ext:
    TargetFilename|contains:
      - '\Temp\'
      - '\AppData\Local\Temp\'
    TargetFilename|contains: 'extension'
    TargetFilename|endswith:
      - '.crx'
      - '.xpi'
      - '.manifest.json'
  condition: (selection_chrome_ext or selection_edge_ext or selection_firefox_ext) and selection_temp_ext
level: high
tags:
  - attack.persistence
  - attack.t1176

0x14 参考资料

  1. NIST. “Guide to Integrating Forensic Techniques into Incident Response.” SP 800-86, 2006. https://csrc.nist.gov/publications/detail/sp/800-86/final
  2. SANS Institute. “Browser Forensics: Investigating Web Activity.” DFIR Cheat Sheet, 2024. https://www.sans.org/white-papers/browser-forensics/
  3. Volkamer Research Group. “Browser Forensics: Investigating Web Browsers.” 2023. https://browserforensics.com/
  4. Ozdoganoglu, H. and Tinx, T. “Browser Forensics.” Proceedings of the Digital Forensic Research Workshop (DFRWS), 2019.
  5. Chromium Project. “SQLite, and Chrome’s Use of It.” Chromium Documentation, 2025. https://www.chromium.org/developers/design-documents/
  6. Mozilla Developer Network. “Firefox Developer Tools: Network Monitor.” MDN Web Docs, 2025. https://developer.mozilla.org/en-US/docs/Tools/Network_Monitor
  7. NirSoft. “ChromeCacheView - Chrome Cache Viewer.” 2025. https://www.nirsoft.net/utils/chrome_cache_view.html
  8. Autopsy. “Browser Artifacts Module.” The Sleuth Kit, 2025. https://www.autopsy.com/
  9. Bergmann, T. “Forensic Analysis of Browser Artifacts.” Journal of Digital Forensics, Security and Law, Vol. 14, 2024.
  10. European Network and Information Security Agency (ENISA). “Digital Forensics Tools and Techniques.” 2023. https://www.enisa.europa.eu/
  11. Volatility Foundation. “Volatility 3 Memory Forensics Framework.” 2025. https://www.volatilityfoundation.org/
  12. MITRE ATT&CK. “T1176 - Browser Extensions.” Enterprise ATT&CK, 2025. https://attack.mitre.org/techniques/T1176/