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, 云资产清单, 代码示例, 数字取证, 数据分析, 无后门, 浏览器, 自动化脚本, 逆向工具, 逆向工程