cclgroupltd/ccl_chromium_reader
GitHub: cclgroupltd/ccl_chromium_reader
该库用 Python 重新实现了 Chrome/Chromium 浏览器多种底层数据存储的读取技术,为数字取证和 Web 应用数据研究提供编程化访问接口。
Stars: 239 | Forks: 49
# ccl_chromium_reader
此代码库包含了一组(部分为不完整的)Python 重新实现,用于处理 Chrome/Chromium/类 Chrome 应用程序在各种数据存储中保存数据时所使用的技术。这些库提供了对这些数据存储的编程访问接口,并侧重于数字取证(例如,对于大多数痕迹,会提供数据的偏移量或 ID,以便于定位和手动检查)。
支持的技术包括:
* Snappy 解压
* LevelDB
* Protobuf
* Pickles
* V8 对象反序列化
* Blink 对象反序列化
* IndexedDB
* Web Storage(Local Storage 和 Session Storage)
* Cache(包括 Block File 和 Simple 格式)
* SNSS Session 文件(部分支持)
* FileSystem API
* Notifications API(Platform Notifications)
* Downloads(来自 shared_proto_db)
* History
此外,还包含一些实用的脚本,例如:
* `ccl_chromium_cache.py` - 将 cache 库作为命令行工具使用,可导出缓存以及所有的 HTTP 头信息。
* `ccl_chrome_audit.py` - 这是一个工具,可用于扫描代码库中支持的及其他几种数据存储,以查找与特定 host 相关的记录——主要设计用于研究 Web 应用程序存储的数据。
## Python 版本
此库中的代码是在 Python 3.10 环境下编写和测试的。它*应该*兼容 3.9 版本,但使用了一些早期版本中尚未引入的语言特性。
库中的某些部分可能支持向前兼容几个更早的版本,但如果你报告了关于 3.10 之前任何版本的 Bug,我们的第一个问题将会是:你能升级到 3.10 吗?
## 关于依赖项的说明
此代码库包含一个 pip 格式的 `requirements.txt` 文件。除了 `Brotli` 之外,其中列出的依赖项仅在运行 `ccl_chrome_audit.py` 脚本,或将 `ccl_chromium_cache` 模块作为脚本导出缓存时才需要;其他库仅使用本代码库中的其他脚本和 Python 标准库即可正常工作。
## 文档
目前库中的文档还不够详尽,但我们最近已经开展了一些工作,添加了更多的用法说明字符串,并填补了 type-hints 中的一些空白。我们欢迎大家提交 pull request 来协助完善文档。
## ccl_chrome_audit
此脚本基于 host 名称的片段(正则表达式),对 Chrom(e|ium) 配置文件文件夹中的多个数据存储进行审计。它旨在通过快速突显与该域名相关的数据存放在何处,来辅助对 Web 应用程序的研究(同样适用于 Electron 应用程序等)。
### 注意事项
目前,该脚本主要设计用于 Windows 系统,并且在填充数据的原始 host 上运行(这是因为 Cookie 解密是通过 DPAPI 实现的)。
### 用法
```
ccl_chrome_audit.py [cache folder (for mobile)]
```
### 当前支持的数据源
* Bookmarks
* History
* Downloads(来自 History)
* Downloads(来自 shared_proto_db)
* Favicons
* Cache
* Cookies
* Local Storage
* Session Storage
* IndexedDb
* File System API
* Platform Notifications
* Logins
* Sessions (SNSS)
## ChromiumProfileFolder
`ChromiumProfileFolder` 类旨在作为一个便捷的入口点,以使用该包中大部分实用的功能。它会按需加载数据,因此相比于直接使用各个独立模块,使用此对象的“启动成本”几乎为零,但其优势在于内置了更好的搜索和过滤功能,并且提供了一个更简便的接口来整合来自这些不同来源的数据。
在此版本中,`ChromiumProfileFolder` 支持以下数据存储:
* History
* Cache
* IndexedDB
* Local Storage
* Session Storage
要使用此对象,只需将配置文件夹的路径传递给构造函数即可(该对象支持上下文管理器接口):
```
import pathlib
from ccl_chromium_reader import ChromiumProfileFolder
profile_path = pathlib.Path("profile path goes here")
with ChromiumProfileFolder(profile_path) as profile:
... # do things with the profile
```
`ChromiumProfileFolder` 对象中大多数用于检索数据的方法都可以通过 `KeySearch` 接口进行搜索/过滤,其实质为以下之一:
* 一个 `str`,在这种情况下,搜索将尝试精确匹配该值
* 一个 `str` 集合(例如 `list` 或 `tuple`),在这种情况下,搜索将尝试精确匹配其中包含的任意一个值
* 一个 `re.pattern`,在这种情况下,搜索将尝试在搜索内容的任意位置进行匹配(类似于 `re.search`)
* 一个接受 `str` 作为参数并返回 `bool`(指示是否匹配)的函数。
```
import re
import pathlib
from ccl_chromium_reader import ChromiumProfileFolder
profile_path = pathlib.Path("profile path goes here")
with ChromiumProfileFolder(profile_path) as profile:
# Match one of two possible hosts exactly, then a regular expression for the key
for ls_rec in profile.iter_local_storage(
storage_key=["http://not-a-real-url1.com", "http://not-a-real-url2.com"],
script_key=re.compile(r"message\d{1,3}?-text")):
print(ls_rec.value)
# Match all urls which end with "&read=1"
for hist_rec in profile.iterate_history_records(url=lambda x: x.endswith("&read=1")):
print(hist_rec.title, hist_rec.url)
```
## IndexedDB
`ccl_chromium_indexeddb.py` 库用于处理 Chrome 等浏览器中的 IndexedDB 数据。
### 博客
请在此处阅读相关主题的博客:https://www.cclsolutionsgroup.com/post/indexeddb-on-chromium
### 注意事项
在文档方面还有很多工作要做,但这些模块应该完全可以用于从 IndexedDB 中提取数据,不过需要注意以下几点:
#### LevelDB 已删除的数据
LevelDB 模块会无差别地输出记录的现有版本和已删除/旧版本;通过一些处理是可以区分它们的,但目前这并没有真正集成到现有的模块中。所以目前在某种程度上,你是“免费”获取了已删除的数据……无论你是否需要。
#### Blink 数据类型
我相当确信所有可能的 V8 对象类型都已被考虑在内(当然,如果有人能指出遗漏之处并将其修复,我会非常高兴!),但宿主 Blink 对象很可能还不全;因此,如果你在 ccl_blink_value_deserializer 内部遇到错误,并且能为我提供一些测试数据,我将不胜感激!
#### 循环引用
V8 源码中指出,在序列化过程中可能会出现递归引用,我们目前还没有对此进行处理,因此如果 Python 抛出 `RecursionError`,很可能就是这个原因。我们计划采用与 ccl_bplist 类似的方法,即对集合类型进行子类化,并对各项进行即时(Just In Time)解析,但这尚未实现。
## 使用模块
访问记录有两种方法:一种是使用一组包装器对象的、更具 Python 风格的 API;另一种是不会掩盖底层工作原理的原始 API。在大多数情况下,使用原始 API 可能并没有太大优势,因此除非你有强烈的理由,否则建议使用包装器对象。
### 包装器 API
```
import sys
from ccl_chromium_reader import ccl_chromium_indexeddb
# 假设命令行参数是指向 .leveldb 和 .blob 文件夹的路径
leveldb_folder_path = sys.argv[1]
blob_folder_path = sys.argv[2]
# 打开 indexedDB:
wrapper = ccl_chromium_indexeddb.WrappedIndexDB(leveldb_folder_path, blob_folder_path)
# 你可以使用 `wrapper.database_ids` 检查存在的数据库
# 可以通过多种方式从 wrapper 访问数据库:
db = wrapper[2] # accessing database using id number
db = wrapper["MyTestDatabase"] # accessing database using name (only valid for single origin indexedDB instances)
db = wrapper["MyTestDatabase", "file__0@1"] # accessing the database using name and origin
# 注意:在大多数情况下,使用 name 和 origin 可能是首选方式
# wrapper 对象还支持使用 `in` 检查数据库
# 你可以使用 `db.object_store_names` 检查 object store 名称
# 可以通过多种方式从数据库访问 object store:
obj_store = db[1] # accessing object store using id number
obj_store = db["store"] # accessing object store using name
# 然后可以通过在 for 循环中迭代 object store 来访问记录
for record in obj_store.iterate_records():
print(record.user_key)
print(record.value)
# if this record contained a FileInfo object somewhere linking
# to data stored in the blob dir, we could access that data like
# so (assume the "file" key in the record value is our FileInfo):
with record.get_blob_stream(record.value["file"]) as f:
file_data = f.read()
# 默认情况下,解码记录时的任何错误都会抛出异常
# 这在 for 循环中迭代记录时可能会很麻烦,因此
# 通过向 errors_to_stdout 参数传入 True 和/或向 bad_deserialization_data_handler 传入一个
# 错误处理函数,你可以
# 执行日志记录而不是崩溃:
for record in obj_store.iterate_records(
errors_to_stdout=True,
bad_deserializer_data_handler= lambda k,v: print(f"error: {k}, {v}")):
print(record.user_key)
print(record.value)
```
### 原始访问 API
```
import sys
from ccl_chromium_reader import ccl_chromium_indexeddb
# 假设命令行参数是指向 .leveldb 和 .blob 文件夹的路径
leveldb_folder_path = sys.argv[1]
blob_folder_path = sys.argv[2]
# 打开数据库:
db = ccl_chromium_indexeddb.IndexedDb(leveldb_folder_path, blob_folder_path)
# 可能有多个数据库,因此我们需要遍历它们(注意:
# DatabaseID 对象包含额外的元数据,它们不仅仅是整数):
for db_id_meta in db.global_metadata.db_ids:
# and within each database, there will be multiple object stores so we
# will need to know the maximum object store number (this process will be
# cleaned up in future releases):
max_objstore_id = db.get_database_metadata(
db_id_meta.dbid_no,
ccl_chromium_indexeddb.DatabaseMetadataType.MaximumObjectStoreId)
# if the above returns None, then there are no stores in this db
if max_objstore_id is None:
continue
# there may be multiple object stores, so again, we iterate through them
# this time based on the id number. Object stores start at id 1 and the
# max_objstore_id is inclusive:
for obj_store_id in range(1, max_objstore_id + 1):
# now we can ask the indexeddb wrapper for all records for this db
# and object store:
for record in db.iterate_records(db_id_meta.dbid_no, obj_store_id):
print(f"key: {record.user_key}")
print(f"key: {record.value}")
# if this record contained a FileInfo object somewhere linking
# to data stored in the blob dir, we could access that data like
# so (assume the "file" key in the record value is our FileInfo):
with record.get_blob_stream(record.value["file"]) as f:
file_data = f.read()
```
## Local Storage
`ccl_chromium_localstorage` 包含了从 Chromium/Chrome 配置文件文件夹中读取 Local Storage 数据的功能。
### 博客
请在此处阅读相关主题的博客:https://www.cclsolutionsgroup.com/post/chromium-session-storage-and-local-storage
### 使用模块
以下示例展示了如何按 host 分组遍历所有记录:
```
import sys
import pathlib
from ccl_chromium_reader import ccl_chromium_localstorage
level_db_in_dir = pathlib.Path(sys.argv[1])
# 创建用于访问数据的 LocalStoreDb 对象
with ccl_chromium_localstorage.LocalStoreDb(level_db_in_dir) as local_storage:
for storage_key in local_storage.iter_storage_keys():
print(f"Getting records for {storage_key}")
for record in local_storage.iter_records_for_storage_key(storage_key):
# we can attempt to associate this record with a batch, which may
# provide an approximate timestamp (withing 5-60 seconds) for this
# record.
batch = local_storage.find_batch(record.leveldb_seq_number)
timestamp = batch.timestamp if batch else None
print(record.leveldb_seq_number, record.script_key, record.value, sep="\t")
```
## Session Storage
`ccl_chromium_sessionstorage` 包含了从 Chromium/Chrome 配置文件文件夹中读取 Session Storage 数据的功能。
### 博客
请在此处阅读相关主题的博客:https://www.cclsolutionsgroup.com/post/chromium-session-storage-and-local-storage
### 使用模块
以下示例展示了如何按 host 分组遍历所有记录:
```
import sys
import pathlib
from ccl_chromium_reader import ccl_chromium_sessionstorage
level_db_in_dir = pathlib.Path(sys.argv[1])
# 创建用于访问数据的 SessionStoreDb 对象
with ccl_chromium_sessionstorage.SessionStoreDb(level_db_in_dir) as session_storage:
for host in session_storage.iter_hosts():
print(f"Getting records for {host}")
for record in session_storage.iter_records_for_host(host):
print(record.leveldb_sequence_number, record.key, record.value)
```
## Cache
`ccl_chromium_cache` 包含了读取 Chromium 缓存数据(包括 block file 和 simple cache 格式)的功能。它可用于以编程方式访问缓存数据和元数据(包括 HTTP 头信息)。
### CLI
将该模块作为脚本执行,允许你导出缓存(支持任意格式)并将所有元数据整理成 csv 文件。
```
USAGE: ccl_chromium_cache.py
```
### 使用模块
模块中的 `main()` 函数(提供了 CLI 功能)展示了检测缓存类型、从缓存中读取数据和元数据的完整流程。
标签:Chromium, HTTP工具, LevelDB, Python, 云资产清单, 代码示例, 数字取证, 数据分析, 无后门, 浏览器, 自动化脚本, 逆向工具, 逆向工程