浏览器取证深度分析 浏览器取证深度分析 浏览器是现代数字取证中信息密度最高的用户行为数据源之一。每一次 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 系统
浏览器 默认存储路径 Chrome C:\Users\<username>\AppData\Local\Google\Chrome\User Data\Default\Edge C:\Users\<username>\AppData\Local\Microsoft\Edge\User Data\Default\Firefox C:\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 Memory WAL 的共享内存索引
取证要点: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 字段的取值含义:
值 含义 取证意义 0 TYPED 用户手动输入 URL 1 LINK 通过链接点击 2 BOOKMARK 通过书签访问 3 SUBFRAME 框架内导航 4 REDIRECT 重定向 5 RELOAD 页面刷新 6 KEYWORD 关键词搜索 7 GENERATE 生成的 URL(如 JavaScript)
5. 数据库锁定和并发处理 浏览器在运行时会使用 SQLite 的 WAL 模式来处理并发读写。取证时需要注意以下问题:
数据库锁定
当浏览器正在运行时,SQLite 数据库文件可能处于锁定状态。取证工具尝试直接读取可能失败或获取到不一致的数据。建议的处理方式:
首先检查浏览器进程是否正在运行 如果可以,优雅地关闭浏览器后再采集 如果无法关闭浏览器,使用 SQLite 的 WAL 模式只读连接 使用 .timeout 设置等待时间 并发读取的 WAL 模式
WAL 模式下,数据库的读取操作不会阻塞写入操作,写入操作也不会阻塞读取操作。这使得取证工具可以在浏览器运行时读取数据库,但可能无法获取到最新的 WAL 中的数据。
0x03 历史记录分析 1. Chrome/Edge 历史记录 Chrome 和 Edge 的历史记录存储在 History 数据库的 urls 和 visits 表中。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_historyvisits 的 visit_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_mode 和 PRAGMA integrity_check:
PRAGMA journal_mode;
PRAGMA integrity_check;
PRAGMA page_count;
PRAGMA freelist_count; 如果 freelist_count 的值异常高,可能表明数据库中的大量记录被删除。
0x04 Cookie 分析 1. Chrome/Edge Cookie 结构 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
2. Firefox Cookie 结构 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 ;3. Cookie 加密机制 Chrome Cookie 加密
Chrome 对 Cookie 的加密方式因操作系统而异:
操作系统 加密方式 说明 Windows DPAPI 使用 os_crypt.encrypted_key 中的密钥 macOS Keychain 存储在 Chrome Safe Storage 中 Linux PBKDF2 密钥来自 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 ' %e xample.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 4. Session Cookie vs Persistent Cookie 特征 Session Cookie Persistent Cookie 过期时间 浏览器关闭后删除 设定的过期时间 存储位置 内存中 磁盘上的 SQLite 数据库 取证价值 只能从运行中的浏览器内存提取 可以从磁盘数据库中提取 生命周期 会话级别 数天到数年
取证要点:Session Cookie 虽然在浏览器关闭后从内存中消失,但如果浏览器在运行时崩溃或被强制终止,Session Cookie 可能被写入磁盘的 Session Storage 中。
5. 第三方 Cookie 分析 第三方 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 ;6. 恶意 Cookie 检测 恶意 Cookie 可能用于持久化恶意代码、窃取会话信息或进行 C2 通信。检测恶意 Cookie 的特征包括:
异常的 Cookie 值长度 :超长的 Cookie 值可能包含编码后的恶意数据Base64 编码的 Cookie 值 :检查 Cookie 值是否为有效的 Base64 编码与已知恶意域名关联的 Cookie :通过威胁情报匹配 Cookie 的域名异常的 Cookie 属性组合 :如设置了 HttpOnly 但没有 Secure 的 CookieSELECT
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 :缓存资源的完整 URLHTTP 头部 :Content-Type、Last-Modified、Cache-Control 等缓存时间 :资源被缓存的时间文件大小 :缓存资源的大小过期时间 :缓存资源的过期时间2. Firefox 缓存结构 Firefox 的缓存存储在 cache2 目录下:
路径 说明 cache2/entries/缓存条目元数据 cache2/data/缓存数据文件
Firefox 的缓存条目使用类似于 MHTML 的格式存储,每个条目包含完整的 HTTP 响应(状态行、头部、正文)。
3. 缓存文件恢复 缓存文件即使在浏览器清除缓存后,也可能通过磁盘恢复工具提取。原因是浏览器清除缓存时通常只是删除索引文件,而不一定立即覆盖数据块。
Chrome 缓存恢复
Chrome 的缓存使用 disk_cache 库,其内部结构是一个 key-value 存储系统。恢复步骤:
提取 Cache_Data 目录下的所有文件 使用 chromecacheview(NirSoft)或 bulk_extractor 解析缓存 识别缓存中的 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. 已删除网页缓存恢复 当浏览器清除缓存后,数据块可能仍然存在于磁盘上。取证恢复的方法包括:
使用磁盘镜像工具 :先对磁盘进行完整镜像,然后在镜像上进行文件恢复分析 SQLite WAL 文件 :WAL 文件可能包含缓存操作的记录检查浏览器的 Session Storage :Session Storage 中可能包含当前会话中访问的页面数据使用取证工具 :Autopsy、FTK Imager 等工具可以从磁盘镜像中恢复已删除的缓存文件0x06 下载记录分析 1. Chrome/Edge 下载记录 Chrome 和 Edge 的下载记录存储在 History 数据库的 downloads 和 downloads_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 的下载记录不直接存储文件哈希,但可以通过以下方式获取:
从下载记录中获取文件落地路径 使用取证工具计算落地文件的 MD5/SHA256 哈希 将哈希与威胁情报平台(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 中是否包含 XMLHttpRequest 或 fetch 调用外部 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-internals(chrome://net-internals/)可以查看扩展的网络活动日志。
0x08 隐身模式痕迹分析 1. 隐身模式的数据保留范围 隐身模式(Incognito Mode / Private Browsing)的设计目标是在浏览器关闭后不保留浏览历史、Cookie、缓存等数据。但隐身模式并非完全不可追踪——它只是在浏览器层面限制了数据的持久化存储。
隐身模式下不会保存 的数据:
数据类型 是否保存 浏览历史 不保存 Cookie 不保存(会话期间在内存中存在) 缓存文件 不保存 下载记录 保存(文件落地到磁盘) 书签 不保存 表单数据 不保存 保存的密码 不保存
隐身模式下仍然会留下 的痕迹:
痕迹类型 保存位置 DNS 查询 操作系统 DNS 缓存 网络连接 操作系统网络日志、路由器日志 文件落地 磁盘文件系统 进程创建 操作系统进程日志 注册表 TypedURLs、UserAssist
2. DNS 缓存痕迹 即使使用隐身模式访问网站,DNS 查询仍然会在操作系统层面留下痕迹。
Windows DNS 缓存查看
DNS 缓存中会记录所有最近解析的域名,包括隐身模式下访问的域名。缓存条目包含域名、解析结果和剩余生存时间。
Linux DNS 缓存查看
systemd-resolve --statistics 3. 网络连接痕迹 隐身模式下的网络连接会在以下位置留下痕迹:
操作系统网络状态 :netstat、ss 等工具可以查看当前网络连接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. 隐身模式取证方法 DNS 缓存提取 :使用 ipconfig /displaydns 或第三方工具提取 DNS 缓存内存取证 :在浏览器运行时采集内存,从内存中提取历史记录和 Cookie网络流量捕获 :使用 Wireshark 等工具捕获隐身模式下的网络流量浏览器内部数据 :使用 chrome://history/#incognito 查看隐身模式标签页的当前历史(仅在浏览器运行时可见)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_configsSELECT 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 服务器上的同步数据:
Google Takeout :Google 提供的用户数据导出服务,可以导出浏览器历史、书签、密码等法律传票 :通过法律程序向 Google 发出传票,要求提供特定账号的同步数据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_count 和 date_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 勒索软件攻击。攻击者通过钓鱼邮件投递恶意附件,最终加密了企业核心数据。在应急响应中,浏览器历史记录成为还原攻击链的关键证据。
浏览器取证发现
钓鱼页面访问 :浏览器历史记录显示员工在攻击前 3 天访问了一个伪装为公司 VPN 登录页面的钓鱼站点恶意文件下载 :下载记录显示员工从该钓鱼站点下载了一个 ZIP 文件,文件保存到 Downloads 目录文件被执行 :文件系统时间戳显示 ZIP 文件在下载后 2 分钟内被解压,其中的 .iso 文件被双击打开后续访问 :浏览器历史中出现多个加密货币交易所的访问记录,表明攻击者可能使用受害者的浏览器访问了赎金支付页面证据链
钓鱼邮件 → 浏览器访问钓鱼站 → 下载 ZIP → 解压 ISO → 执行恶意代码 → 勒索加密案例二:内部威胁 — 浏览器下载记录追踪 事件背景
2025 年,某科技公司发现其源代码被泄露到 GitHub 上。安全团队对涉嫌泄露的员工进行取证分析,浏览器下载记录成为关键证据。
浏览器取证发现
大量代码下载 :下载记录显示员工在过去一个月内从内部 GitLab 下载了大量代码仓库异常下载时间 :下载活动集中在深夜和周末文件路径分析 :下载文件的路径指向外部 USB 设备的挂载点Cookie 分析 :Cookie 记录显示员工在下载后访问了 GitHub 的上传页面证据链
GitLab 代码下载 → 下载到 USB 路径 → 上传到 GitHub → 源代码泄露案例三:钓鱼攻击 — Cookie 和 URL 分析 事件背景
2025 年,某金融机构遭受针对性钓鱼攻击。攻击者通过窃取员工的会话 Cookie,冒充员工访问内部系统,窃取了客户数据。
浏览器取证发现
异常 Cookie 来源 :Cookie 数据库中出现了从未知 IP 地址设置的会话 Cookie异常 URL 访问 :浏览器历史显示了从外部 IP 访问内部系统的记录登录时间异常 :Cookie 的创建时间与员工的正常工作时间不一致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 参考资料 NIST. “Guide to Integrating Forensic Techniques into Incident Response.” SP 800-86, 2006. https://csrc.nist.gov/publications/detail/sp/800-86/final SANS Institute. “Browser Forensics: Investigating Web Activity.” DFIR Cheat Sheet, 2024. https://www.sans.org/white-papers/browser-forensics/ Volkamer Research Group. “Browser Forensics: Investigating Web Browsers.” 2023. https://browserforensics.com/ Ozdoganoglu, H. and Tinx, T. “Browser Forensics.” Proceedings of the Digital Forensic Research Workshop (DFRWS), 2019. Chromium Project. “SQLite, and Chrome’s Use of It.” Chromium Documentation, 2025. https://www.chromium.org/developers/design-documents/ Mozilla Developer Network. “Firefox Developer Tools: Network Monitor.” MDN Web Docs, 2025. https://developer.mozilla.org/en-US/docs/Tools/Network_Monitor NirSoft. “ChromeCacheView - Chrome Cache Viewer.” 2025. https://www.nirsoft.net/utils/chrome_cache_view.html Autopsy. “Browser Artifacts Module.” The Sleuth Kit, 2025. https://www.autopsy.com/ Bergmann, T. “Forensic Analysis of Browser Artifacts.” Journal of Digital Forensics, Security and Law, Vol. 14, 2024. European Network and Information Security Agency (ENISA). “Digital Forensics Tools and Techniques.” 2023. https://www.enisa.europa.eu/ Volatility Foundation. “Volatility 3 Memory Forensics Framework.” 2025. https://www.volatilityfoundation.org/ MITRE ATT&CK. “T1176 - Browser Extensions.” Enterprise ATT&CK, 2025. https://attack.mitre.org/techniques/T1176/