performing-sqlite-database-forensics
对 SQLite 数据库执行取证分析,从空闲列表(Freelist)和 WAL 文件中恢复已删除记录,解码编码时间戳,并从浏览器历史、即时通讯应用和移动设备数据库中提取证据。
Best use case
performing-sqlite-database-forensics is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
对 SQLite 数据库执行取证分析,从空闲列表(Freelist)和 WAL 文件中恢复已删除记录,解码编码时间戳,并从浏览器历史、即时通讯应用和移动设备数据库中提取证据。
Teams using performing-sqlite-database-forensics should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/performing-sqlite-database-forensics/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How performing-sqlite-database-forensics Compares
| Feature / Agent | performing-sqlite-database-forensics | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
对 SQLite 数据库执行取证分析,从空闲列表(Freelist)和 WAL 文件中恢复已删除记录,解码编码时间戳,并从浏览器历史、即时通讯应用和移动设备数据库中提取证据。
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# 执行 SQLite 数据库取证
## 概述
SQLite 是全球部署最广泛的数据库引擎,几乎每个移动应用、Web 浏览器和许多桌面应用都使用它存储用户数据。在数字取证(Digital Forensics)中,SQLite 数据库是包含浏览器历史、消息记录、通话日志、GPS 位置、应用偏好设置和缓存内容的关键证据来源。取证分析超越了简单的 SQL 查询,需要检查内部 B 树(B-tree)页面结构、包含已删除记录的空闲列表(Freelist)页面、保存事务历史的写前日志(WAL,Write-Ahead Log)文件,以及数据库页面中删除后仍可能保留可恢复数据的未分配空间。
## 前置条件
- DB Browser for SQLite(sqlitebrowser)
- SQLite 命令行工具(sqlite3)
- Python 3.8+ 及 sqlite3 模块
- Belkasoft Evidence Center 或 Axiom(商业版)
- 十六进制编辑器(HxD、010 Editor)用于手动页面检查
- 理解 B 树数据结构
## SQLite 内部结构
### 数据库头部(前 100 字节)
| 偏移量 | 大小 | 描述 |
|--------|------|------|
| 0 | 16 | 魔数字符串:"SQLite format 3\000" |
| 16 | 2 | 页面大小(512-65536 字节) |
| 18 | 1 | 文件格式写入版本 |
| 19 | 1 | 文件格式读取版本 |
| 24 | 4 | 文件变更计数器 |
| 28 | 4 | 数据库页面数 |
| 32 | 4 | 第一个空闲列表主干页面编号 |
| 36 | 4 | 空闲列表总页面数 |
| 52 | 4 | 文本编码(1=UTF-8,2=UTF-16le,3=UTF-16be) |
| 96 | 4 | 版本有效性编号 |
### 页面类型
| 类型 | ID | 描述 |
|------|----|------|
| B 树内部节点 | 0x05 | 内部表节点 |
| B 树叶节点 | 0x0D | 包含实际记录的表叶页面 |
| 索引内部节点 | 0x02 | 内部索引节点 |
| 索引叶节点 | 0x0A | 索引叶页面 |
| 空闲列表主干 | - | 跟踪已释放页面 |
| 空闲列表叶 | - | 包含可恢复数据的已释放页面 |
| 溢出页 | - | 大型记录的延续 |
## 已删除记录恢复
### 方法 1:空闲列表页面分析
删除记录时,SQLite 可能将其页面放入空闲列表而非立即覆盖。
```python
import struct
import sqlite3
import os
def analyze_freelist(db_path: str) -> dict:
"""分析 SQLite 空闲列表,识别包含已删除数据的页面。"""
with open(db_path, "rb") as f:
# 读取头部
header = f.read(100)
page_size = struct.unpack(">H", header[16:18])[0]
if page_size == 1:
page_size = 65536
first_freelist_page = struct.unpack(">I", header[32:36])[0]
total_freelist_pages = struct.unpack(">I", header[36:40])[0]
freelist_info = {
"page_size": page_size,
"first_freelist_page": first_freelist_page,
"total_freelist_pages": total_freelist_pages,
"trunk_pages": [],
"leaf_pages": []
}
if first_freelist_page == 0:
return freelist_info
# 遍历空闲列表主干链
trunk_page = first_freelist_page
while trunk_page != 0:
offset = (trunk_page - 1) * page_size
f.seek(offset)
page_data = f.read(page_size)
next_trunk = struct.unpack(">I", page_data[0:4])[0]
leaf_count = struct.unpack(">I", page_data[4:8])[0]
leaves = []
for i in range(leaf_count):
leaf_page = struct.unpack(">I", page_data[8 + i * 4:12 + i * 4])[0]
leaves.append(leaf_page)
freelist_info["trunk_pages"].append({
"page_number": trunk_page,
"next_trunk": next_trunk,
"leaf_count": leaf_count,
"leaf_pages": leaves
})
freelist_info["leaf_pages"].extend(leaves)
trunk_page = next_trunk
return freelist_info
def extract_freelist_content(db_path: str, output_dir: str):
"""提取空闲列表页面的原始内容以供分析。"""
info = analyze_freelist(db_path)
os.makedirs(output_dir, exist_ok=True)
with open(db_path, "rb") as f:
page_size = info["page_size"]
for page_num in info["leaf_pages"]:
offset = (page_num - 1) * page_size
f.seek(offset)
page_data = f.read(page_size)
output_file = os.path.join(output_dir, f"freelist_page_{page_num}.bin")
with open(output_file, "wb") as out:
out.write(page_data)
return len(info["leaf_pages"])
```
### 方法 2:WAL(写前日志)分析
WAL 文件包含尚未检查点写回主数据库的待处理事务。
```python
def parse_wal_header(wal_path: str) -> dict:
"""解析 SQLite WAL 文件头部和帧清单。"""
with open(wal_path, "rb") as f:
header = f.read(32)
magic = struct.unpack(">I", header[0:4])[0]
file_format = struct.unpack(">I", header[4:8])[0]
page_size = struct.unpack(">I", header[8:12])[0]
checkpoint_seq = struct.unpack(">I", header[12:16])[0]
salt1 = struct.unpack(">I", header[16:20])[0]
salt2 = struct.unpack(">I", header[20:24])[0]
wal_info = {
"magic": hex(magic),
"format": file_format,
"page_size": page_size,
"checkpoint_sequence": checkpoint_seq,
"frames": []
}
# 解析帧(每帧 24 字节头部 + page_size 数据)
frame_offset = 32
frame_num = 0
file_size = os.path.getsize(wal_path)
while frame_offset + 24 + page_size <= file_size:
f.seek(frame_offset)
frame_header = f.read(24)
page_number = struct.unpack(">I", frame_header[0:4])[0]
db_size_after = struct.unpack(">I", frame_header[4:8])[0]
wal_info["frames"].append({
"frame_number": frame_num,
"page_number": page_number,
"db_size_pages": db_size_after,
"offset": frame_offset
})
frame_offset += 24 + page_size
frame_num += 1
return wal_info
```
### 方法 3:页面内未分配空间
活跃 B 树页面内已删除的单元在单元指针数组和单元内容区域之间的未分配区域留有数据。
```python
def analyze_unallocated_space(db_path: str, page_number: int) -> dict:
"""分析特定 B 树页面内的未分配空间。"""
with open(db_path, "rb") as f:
header = f.read(100)
page_size = struct.unpack(">H", header[16:18])[0]
if page_size == 1:
page_size = 65536
offset = (page_number - 1) * page_size
f.seek(offset)
page_data = f.read(page_size)
# 解析页面头部(根据类型为 8 或 12 字节)
page_type = page_data[0]
first_freeblock = struct.unpack(">H", page_data[1:3])[0]
cell_count = struct.unpack(">H", page_data[3:5])[0]
cell_content_offset = struct.unpack(">H", page_data[5:7])[0]
if cell_content_offset == 0:
cell_content_offset = 65536
header_size = 12 if page_type in (0x02, 0x05) else 8
cell_pointer_end = header_size + cell_count * 2
unallocated_start = cell_pointer_end
unallocated_end = cell_content_offset
unallocated_size = unallocated_end - unallocated_start
return {
"page_number": page_number,
"page_type": hex(page_type),
"cell_count": cell_count,
"unallocated_start": unallocated_start,
"unallocated_end": unallocated_end,
"unallocated_size": unallocated_size,
"unallocated_data": page_data[unallocated_start:unallocated_end].hex()
}
```
## 常见取证数据库
| 应用 | 数据库文件 | 关键表 |
|------|-----------|--------|
| Chrome | History | urls, visits, downloads, keyword_search_terms |
| Firefox | places.sqlite | moz_places, moz_historyvisits |
| Safari | History.db | history_items, history_visits |
| WhatsApp | msgstore.db | messages, chat_list |
| Signal | signal.sqlite | sms, mms |
| iMessage | sms.db | message, handle, chat |
| Android SMS | mmssms.db | sms, mms, threads |
| Skype | main.db | Messages, Conversations |
## 时间戳解码
```python
from datetime import datetime, timedelta
def decode_chrome_timestamp(chrome_ts: int) -> datetime:
"""将 Chrome/WebKit 时间戳转换为 datetime(自 1601-01-01 的微秒数)。"""
epoch_delta = 11644473600
return datetime.utcfromtimestamp((chrome_ts / 1000000) - epoch_delta)
def decode_unix_timestamp(unix_ts: int) -> datetime:
"""将 Unix 时间戳转换为 datetime。"""
return datetime.utcfromtimestamp(unix_ts)
def decode_mac_absolute_time(mac_ts: float) -> datetime:
"""将 Mac 绝对时间(自 2001-01-01 的秒数)转换为 datetime。"""
mac_epoch = datetime(2001, 1, 1)
return mac_epoch + timedelta(seconds=mac_ts)
def decode_mozilla_timestamp(moz_ts: int) -> datetime:
"""将 Mozilla PRTime(自 Unix 纪元的微秒数)转换为 datetime。"""
return datetime.utcfromtimestamp(moz_ts / 1000000)
```
## 参考资料
- SQLite 文件格式:https://www.sqlite.org/fileformat2.html
- Belkasoft SQLite 分析:https://belkasoft.com/sqlite-analysis
- Spyder Forensics SQLite 培训:https://www.spyderforensics.com/sqlite-forensic-fundamentals-2025/
- 受损 SQLite 数据库的取证分析:https://www.forensicfocus.com/articles/forensic-analysis-of-damaged-sqlite-databases/Related Skills
performing-yara-rule-development-for-detection
通过识别可执行文件中的唯一字节模式、字符串和行为指标,开发精准的 YARA 恶意软件检测规则,同时将误报率降至最低。
performing-wireless-security-assessment-with-kismet
使用 Kismet 通过被动射频监控进行无线网络安全评估,检测流氓接入点(Rogue AP)、隐藏 SSID、弱加密和未授权客户端。
performing-wireless-network-penetration-test
执行无线网络渗透测试,通过捕获握手包、破解 WPA2/WPA3 密钥、检测流氓接入点以及使用 Aircrack-ng 和相关工具测试无线网络分段,评估 WiFi 安全性。
performing-windows-artifact-analysis-with-eric-zimmerman-tools
使用 Eric Zimmerman 的开源 EZ Tools 套件(包括 KAPE、MFTECmd、PECmd、LECmd、JLECmd 和 Timeline Explorer)执行全面的 Windows 取证制品分析,解析注册表 hive、预取文件、事件日志和文件系统元数据。
performing-wifi-password-cracking-with-aircrack
在授权无线安全评估中捕获 WPA/WPA2 握手包,并使用 aircrack-ng、hashcat 和字典攻击进行离线密码破解, 以评估密码短语强度和无线网络安全状况。
performing-web-cache-poisoning-attack
在授权安全测试期间,通过未纳入缓存键的头部和参数毒化缓存响应,利用 Web 缓存机制向其他用户投递恶意内容。
performing-web-cache-deception-attack
通过利用 CDN 缓存层与源服务器之间的路径规范化差异,执行 Web 缓存欺骗攻击,从而缓存并获取敏感的已认证内容。
performing-web-application-vulnerability-triage
使用 OWASP 风险评级方法论对 DAST/SAST 扫描器的 Web 应用程序漏洞发现进行分类,区分真阳性和假阳性,并确定修复优先级。
performing-web-application-scanning-with-nikto
Nikto 是一款开源 Web 服务器和 Web 应用程序扫描器,可针对超过 7,000 个潜在危险文件/程序进行测试,检查超过 1,250 个服务器的过期版本,并识别超过 270 个服务器的版本特定问题。
performing-web-application-penetration-test
遵循 OWASP Web 安全测试指南(WSTG)方法论,对 Web 应用程序执行系统化安全测试,识别认证、授权、 输入验证、会话管理和业务逻辑中的漏洞。测试人员以 Burp Suite 作为主要拦截代理,结合手动测试技术 发现自动化扫描器遗漏的缺陷。适用于 Web 应用渗透测试、OWASP 测试、应用安全评估或 Web 漏洞测试等请求场景。
performing-web-application-firewall-bypass
使用编码技术、HTTP 方法操控、参数污染和载荷混淆绕过 Web 应用防火墙保护,将 SQL 注入、XSS 及其他攻击载荷穿透 WAF 检测规则。
performing-vulnerability-scanning-with-nessus
使用 Tenable Nessus 执行认证和未认证漏洞扫描,识别网络基础设施、服务器和应用程序中的已知漏洞、 错误配置、默认凭据和缺失补丁。扫描器将发现与 CVE 数据库和 CVSS 评分关联,生成优先级修复指导。 适用于漏洞扫描、Nessus 评估、补丁合规检查或自动化漏洞检测等请求场景。