dfindexeddb 20240501__tar.gz → 20241031__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dfindexeddb-20240501/dfindexeddb.egg-info → dfindexeddb-20241031}/PKG-INFO +42 -15
- {dfindexeddb-20240501 → dfindexeddb-20241031}/README.md +38 -14
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/chromium/blink.py +5 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/chromium/record.py +11 -6
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/chromium/v8.py +30 -10
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/cli.py +52 -9
- dfindexeddb-20241031/dfindexeddb/indexeddb/firefox/definitions.py +143 -0
- dfindexeddb-20241031/dfindexeddb/indexeddb/firefox/gecko.py +600 -0
- dfindexeddb-20241031/dfindexeddb/indexeddb/firefox/record.py +180 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/safari/webkit.py +52 -20
- dfindexeddb-20241031/dfindexeddb/indexeddb/types.py +71 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/leveldb/cli.py +69 -6
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/leveldb/descriptor.py +2 -1
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/leveldb/log.py +9 -3
- dfindexeddb-20241031/dfindexeddb/leveldb/plugins/__init__.py +17 -0
- dfindexeddb-20241031/dfindexeddb/leveldb/plugins/chrome_notifications.py +135 -0
- dfindexeddb-20241031/dfindexeddb/leveldb/plugins/interface.py +36 -0
- dfindexeddb-20241031/dfindexeddb/leveldb/plugins/manager.py +75 -0
- dfindexeddb-20241031/dfindexeddb/leveldb/plugins/notification_database_data_pb2.py +38 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/leveldb/record.py +33 -1
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/utils.py +35 -1
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/version.py +1 -1
- {dfindexeddb-20240501 → dfindexeddb-20241031/dfindexeddb.egg-info}/PKG-INFO +42 -15
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb.egg-info/SOURCES.txt +10 -1
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb.egg-info/requires.txt +4 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/pyproject.toml +8 -4
- {dfindexeddb-20240501 → dfindexeddb-20241031}/AUTHORS +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/LICENSE +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/__init__.py +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/errors.py +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/__init__.py +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/chromium/__init__.py +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/chromium/definitions.py +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/firefox/__init__.py +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/safari/__init__.py +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/safari/definitions.py +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/safari/record.py +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/utils.py +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/leveldb/__init__.py +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/leveldb/definitions.py +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/leveldb/ldb.py +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/leveldb/utils.py +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb.egg-info/dependency_links.txt +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb.egg-info/entry_points.txt +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb.egg-info/top_level.txt +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/setup.cfg +0 -0
- {dfindexeddb-20240501 → dfindexeddb-20241031}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dfindexeddb
|
|
3
|
-
Version:
|
|
3
|
+
Version: 20241031
|
|
4
4
|
Summary: dfindexeddb is an experimental Python tool for performing digital forensic analysis of IndexedDB and leveldb files.
|
|
5
5
|
Author-email: Syd Pleno <sydp@google.com>
|
|
6
6
|
Maintainer-email: dfIndexeddb Developers <dfindexeddb-dev@googlegroups.com>
|
|
@@ -219,6 +219,9 @@ License-File: LICENSE
|
|
|
219
219
|
License-File: AUTHORS
|
|
220
220
|
Requires-Dist: python-snappy==0.6.1
|
|
221
221
|
Requires-Dist: zstd==1.5.5.1
|
|
222
|
+
Provides-Extra: plugins
|
|
223
|
+
Requires-Dist: protobuf; extra == "plugins"
|
|
224
|
+
Requires-Dist: dfdatetime; extra == "plugins"
|
|
222
225
|
|
|
223
226
|
# dfIndexeddb
|
|
224
227
|
|
|
@@ -227,8 +230,7 @@ analysis of IndexedDB and LevelDB files.
|
|
|
227
230
|
|
|
228
231
|
It parses LevelDB, IndexedDB and JavaScript structures from these files without
|
|
229
232
|
requiring native libraries. (Note: only a subset of IndexedDB key types and
|
|
230
|
-
JavaScript types for Safari and Chromium-based browsers are currently supported.
|
|
231
|
-
Firefox is under development).
|
|
233
|
+
JavaScript types for Firefox, Safari and Chromium-based browsers are currently supported).
|
|
232
234
|
|
|
233
235
|
The content of IndexedDB files is dependent on what a web application stores
|
|
234
236
|
locally/offline using the web browser's
|
|
@@ -255,6 +257,12 @@ include:
|
|
|
255
257
|
$ pip install dfindexeddb
|
|
256
258
|
```
|
|
257
259
|
|
|
260
|
+
To also install the dependencies for leveldb/indexeddb plugins, run
|
|
261
|
+
```
|
|
262
|
+
$ pip install 'dfindexeddb[plugins]'
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
|
|
258
266
|
## Installation from source
|
|
259
267
|
|
|
260
268
|
1. [Linux] Install the snappy compression development package
|
|
@@ -273,6 +281,11 @@ include:
|
|
|
273
281
|
$ pip install .
|
|
274
282
|
```
|
|
275
283
|
|
|
284
|
+
To also install the dependencies for leveldb/indexeddb plugins, run
|
|
285
|
+
```
|
|
286
|
+
$ pip install '.[plugins]'
|
|
287
|
+
```
|
|
288
|
+
|
|
276
289
|
## Usage
|
|
277
290
|
|
|
278
291
|
Two CLI tools for parsing IndexedDB/LevelDB files are available after
|
|
@@ -299,6 +312,13 @@ options:
|
|
|
299
312
|
|
|
300
313
|
#### Examples:
|
|
301
314
|
|
|
315
|
+
To parse IndexedDB records from an sqlite file for Firefox and output the
|
|
316
|
+
results as JSON, use the following command:
|
|
317
|
+
|
|
318
|
+
```
|
|
319
|
+
dfindexeddb db -s SOURCE --format firefox -o json
|
|
320
|
+
```
|
|
321
|
+
|
|
302
322
|
To parse IndexedDB records from an sqlite file for Safari and output the
|
|
303
323
|
results as JSON-L, use the following command:
|
|
304
324
|
|
|
@@ -359,7 +379,15 @@ options:
|
|
|
359
379
|
To parse records from a LevelDB folder, use the following command:
|
|
360
380
|
|
|
361
381
|
```
|
|
362
|
-
|
|
382
|
+
dfleveldb db -s SOURCE
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
To parse records from a LevelDB folder, and use the sequence number to
|
|
386
|
+
determine recovered records and output as JSON, use the
|
|
387
|
+
following command:
|
|
388
|
+
|
|
389
|
+
```
|
|
390
|
+
dfleveldb db -s SOURCE --use_sequence_number
|
|
363
391
|
```
|
|
364
392
|
|
|
365
393
|
To parse blocks / physical records/ write batches / internal key records from a
|
|
@@ -383,15 +411,14 @@ following command:
|
|
|
383
411
|
|
|
384
412
|
```
|
|
385
413
|
$ dfleveldb descriptor -s SOURCE [-o {json,jsonl,repr}] [-t {blocks,physical_records,versionedit} | -v]
|
|
386
|
-
|
|
387
|
-
options:
|
|
388
|
-
-h, --help show this help message and exit
|
|
389
|
-
-s SOURCE, --source SOURCE
|
|
390
|
-
The source leveldb file
|
|
391
|
-
-o {json,jsonl,repr}, --output {json,jsonl,repr}
|
|
392
|
-
Output format. Default is json
|
|
393
|
-
-t {blocks,physical_records,versionedit}, --structure_type {blocks,physical_records,versionedit}
|
|
394
|
-
Parses the specified structure. Default is versionedit.
|
|
395
|
-
-v, --version_history
|
|
396
|
-
Parses the leveldb version history.
|
|
397
414
|
```
|
|
415
|
+
|
|
416
|
+
#### Plugins
|
|
417
|
+
|
|
418
|
+
To apply a plugin parser for a leveldb file/folder, add the
|
|
419
|
+
`--plugin [Plugin Name]` argument. Currently, there is support for the
|
|
420
|
+
following artifacts:
|
|
421
|
+
|
|
422
|
+
| Plugin Name | Artifact Name |
|
|
423
|
+
| -------- | ------- |
|
|
424
|
+
| `ChromeNotificationRecord` | Chrome/Chromium Notifications |
|
|
@@ -5,8 +5,7 @@ analysis of IndexedDB and LevelDB files.
|
|
|
5
5
|
|
|
6
6
|
It parses LevelDB, IndexedDB and JavaScript structures from these files without
|
|
7
7
|
requiring native libraries. (Note: only a subset of IndexedDB key types and
|
|
8
|
-
JavaScript types for Safari and Chromium-based browsers are currently supported.
|
|
9
|
-
Firefox is under development).
|
|
8
|
+
JavaScript types for Firefox, Safari and Chromium-based browsers are currently supported).
|
|
10
9
|
|
|
11
10
|
The content of IndexedDB files is dependent on what a web application stores
|
|
12
11
|
locally/offline using the web browser's
|
|
@@ -33,6 +32,12 @@ include:
|
|
|
33
32
|
$ pip install dfindexeddb
|
|
34
33
|
```
|
|
35
34
|
|
|
35
|
+
To also install the dependencies for leveldb/indexeddb plugins, run
|
|
36
|
+
```
|
|
37
|
+
$ pip install 'dfindexeddb[plugins]'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
|
|
36
41
|
## Installation from source
|
|
37
42
|
|
|
38
43
|
1. [Linux] Install the snappy compression development package
|
|
@@ -51,6 +56,11 @@ include:
|
|
|
51
56
|
$ pip install .
|
|
52
57
|
```
|
|
53
58
|
|
|
59
|
+
To also install the dependencies for leveldb/indexeddb plugins, run
|
|
60
|
+
```
|
|
61
|
+
$ pip install '.[plugins]'
|
|
62
|
+
```
|
|
63
|
+
|
|
54
64
|
## Usage
|
|
55
65
|
|
|
56
66
|
Two CLI tools for parsing IndexedDB/LevelDB files are available after
|
|
@@ -77,6 +87,13 @@ options:
|
|
|
77
87
|
|
|
78
88
|
#### Examples:
|
|
79
89
|
|
|
90
|
+
To parse IndexedDB records from an sqlite file for Firefox and output the
|
|
91
|
+
results as JSON, use the following command:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
dfindexeddb db -s SOURCE --format firefox -o json
|
|
95
|
+
```
|
|
96
|
+
|
|
80
97
|
To parse IndexedDB records from an sqlite file for Safari and output the
|
|
81
98
|
results as JSON-L, use the following command:
|
|
82
99
|
|
|
@@ -137,7 +154,15 @@ options:
|
|
|
137
154
|
To parse records from a LevelDB folder, use the following command:
|
|
138
155
|
|
|
139
156
|
```
|
|
140
|
-
|
|
157
|
+
dfleveldb db -s SOURCE
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
To parse records from a LevelDB folder, and use the sequence number to
|
|
161
|
+
determine recovered records and output as JSON, use the
|
|
162
|
+
following command:
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
dfleveldb db -s SOURCE --use_sequence_number
|
|
141
166
|
```
|
|
142
167
|
|
|
143
168
|
To parse blocks / physical records/ write batches / internal key records from a
|
|
@@ -161,15 +186,14 @@ following command:
|
|
|
161
186
|
|
|
162
187
|
```
|
|
163
188
|
$ dfleveldb descriptor -s SOURCE [-o {json,jsonl,repr}] [-t {blocks,physical_records,versionedit} | -v]
|
|
164
|
-
|
|
165
|
-
options:
|
|
166
|
-
-h, --help show this help message and exit
|
|
167
|
-
-s SOURCE, --source SOURCE
|
|
168
|
-
The source leveldb file
|
|
169
|
-
-o {json,jsonl,repr}, --output {json,jsonl,repr}
|
|
170
|
-
Output format. Default is json
|
|
171
|
-
-t {blocks,physical_records,versionedit}, --structure_type {blocks,physical_records,versionedit}
|
|
172
|
-
Parses the specified structure. Default is versionedit.
|
|
173
|
-
-v, --version_history
|
|
174
|
-
Parses the leveldb version history.
|
|
175
189
|
```
|
|
190
|
+
|
|
191
|
+
#### Plugins
|
|
192
|
+
|
|
193
|
+
To apply a plugin parser for a leveldb file/folder, add the
|
|
194
|
+
`--plugin [Plugin Name]` argument. Currently, there is support for the
|
|
195
|
+
following artifacts:
|
|
196
|
+
|
|
197
|
+
| Plugin Name | Artifact Name |
|
|
198
|
+
| -------- | ------- |
|
|
199
|
+
| `ChromeNotificationRecord` | Chrome/Chromium Notifications |
|
|
@@ -780,6 +780,9 @@ class V8ScriptValueDecoder:
|
|
|
780
780
|
|
|
781
781
|
Returns:
|
|
782
782
|
A parsed CryptoKey.
|
|
783
|
+
|
|
784
|
+
Raises:
|
|
785
|
+
ParserError: if there is an unexpected CryptoKeySubTag.
|
|
783
786
|
"""
|
|
784
787
|
_, raw_key_byte = self.deserializer.decoder.DecodeUint8()
|
|
785
788
|
key_byte = definitions.CryptoKeySubTag(raw_key_byte)
|
|
@@ -795,6 +798,8 @@ class V8ScriptValueDecoder:
|
|
|
795
798
|
key_type, algorithm_parameters = self._ReadED25519Key()
|
|
796
799
|
elif key_byte == definitions.CryptoKeySubTag.NO_PARAMS_KEY:
|
|
797
800
|
key_type, algorithm_parameters = self.ReadNoParamsKey()
|
|
801
|
+
else:
|
|
802
|
+
raise errors.ParserError('Unexpected CryptoKeySubTag')
|
|
798
803
|
|
|
799
804
|
_, raw_usages = self.deserializer.decoder.DecodeUint32Varint()
|
|
800
805
|
usages = definitions.CryptoKeyUsage(raw_usages)
|
|
@@ -1275,14 +1275,17 @@ class ExternalObjectEntry(utils.FromDecoderMixin):
|
|
|
1275
1275
|
filename = None
|
|
1276
1276
|
last_modified = None
|
|
1277
1277
|
token = None
|
|
1278
|
-
|
|
1279
|
-
|
|
1278
|
+
else:
|
|
1279
|
+
if (object_type ==
|
|
1280
|
+
definitions.ExternalObjectType.FILE_SYSTEM_ACCESS_HANDLE):
|
|
1281
|
+
_, token = decoder.DecodeBlobWithLength()
|
|
1282
|
+
else:
|
|
1283
|
+
token = None
|
|
1280
1284
|
blob_number = None
|
|
1281
1285
|
mime_type = None
|
|
1282
1286
|
size = None
|
|
1283
1287
|
filename = None
|
|
1284
1288
|
last_modified = None
|
|
1285
|
-
_, token = decoder.DecodeBlobWithLength()
|
|
1286
1289
|
|
|
1287
1290
|
return cls(offset=base_offset + offset, object_type=object_type,
|
|
1288
1291
|
blob_number=blob_number, mime_type=mime_type, size=size,
|
|
@@ -1417,20 +1420,22 @@ class FolderReader:
|
|
|
1417
1420
|
|
|
1418
1421
|
def GetRecords(
|
|
1419
1422
|
self,
|
|
1420
|
-
use_manifest: bool = False
|
|
1423
|
+
use_manifest: bool = False,
|
|
1424
|
+
use_sequence_number: bool = False
|
|
1421
1425
|
) -> Generator[IndexedDBRecord, None, None]:
|
|
1422
1426
|
"""Yield LevelDBRecords.
|
|
1423
1427
|
|
|
1424
1428
|
Args:
|
|
1425
1429
|
use_manifest: True to use the current manifest in the folder as a means to
|
|
1426
1430
|
find the active file set.
|
|
1427
|
-
|
|
1431
|
+
use_sequence_number: True to use the sequence number to determine the
|
|
1428
1432
|
Yields:
|
|
1429
1433
|
IndexedDBRecord.
|
|
1430
1434
|
"""
|
|
1431
1435
|
leveldb_folder_reader = record.FolderReader(self.foldername)
|
|
1432
1436
|
for leveldb_record in leveldb_folder_reader.GetRecords(
|
|
1433
|
-
use_manifest=use_manifest
|
|
1437
|
+
use_manifest=use_manifest,
|
|
1438
|
+
use_sequence_number=use_sequence_number):
|
|
1434
1439
|
try:
|
|
1435
1440
|
yield IndexedDBRecord.FromLevelDBRecord(
|
|
1436
1441
|
leveldb_record)
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
# See the License for the specific language governing permissions and
|
|
14
14
|
# limitations under the License.
|
|
15
15
|
"""Parsers for v8 javascript serialized objects."""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
16
18
|
from dataclasses import dataclass
|
|
17
19
|
from datetime import datetime
|
|
18
20
|
import io
|
|
@@ -25,8 +27,8 @@ from dfindexeddb.indexeddb.chromium import definitions
|
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
@dataclass
|
|
28
|
-
class
|
|
29
|
-
"""A parsed Javascript
|
|
30
|
+
class ArrayBufferView:
|
|
31
|
+
"""A parsed Javascript ArrayBufferView."""
|
|
30
32
|
buffer: bytes
|
|
31
33
|
tag: definitions.V8ArrayBufferViewTag
|
|
32
34
|
offset: int
|
|
@@ -34,15 +36,23 @@ class BufferArrayView:
|
|
|
34
36
|
flags: int
|
|
35
37
|
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
@dataclass
|
|
40
|
+
class JSArray:
|
|
38
41
|
"""A parsed Javascript array.
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
properties
|
|
43
|
+
A Javascript array behaves like a Python list but allows assigning arbitrary
|
|
44
|
+
properties. The array is stored in the attribute __array__.
|
|
42
45
|
"""
|
|
46
|
+
def __init__(self):
|
|
47
|
+
self.__array__ = []
|
|
48
|
+
|
|
49
|
+
def Append(self, element: Any):
|
|
50
|
+
"""Appends a new element to the array."""
|
|
51
|
+
self.__array__.append(element)
|
|
43
52
|
|
|
44
53
|
def __repr__(self):
|
|
45
|
-
array_entries = ", ".join(
|
|
54
|
+
array_entries = ", ".join(
|
|
55
|
+
[str(entry) for entry in list(self.__array__)])
|
|
46
56
|
properties = ", ".join(
|
|
47
57
|
f'{key}: {value}' for key, value in self.properties.items())
|
|
48
58
|
return f'[{array_entries}, {properties}]'
|
|
@@ -52,6 +62,11 @@ class JSArray(list):
|
|
|
52
62
|
"""Returns the object properties."""
|
|
53
63
|
return self.__dict__
|
|
54
64
|
|
|
65
|
+
def __eq__(self, other: JSArray):
|
|
66
|
+
return (
|
|
67
|
+
self.__array__ == other.__array__
|
|
68
|
+
and self.properties == other.properties)
|
|
69
|
+
|
|
55
70
|
def __contains__(self, item):
|
|
56
71
|
return item in self.__dict__
|
|
57
72
|
|
|
@@ -194,6 +209,7 @@ class ValueDeserializer:
|
|
|
194
209
|
if tag and tag == definitions.V8SerializationTag.ARRAY_BUFFER_VIEW:
|
|
195
210
|
self._ConsumeTag(tag)
|
|
196
211
|
result = self._ReadJSArrayBufferView(result)
|
|
212
|
+
self.objects[self._GetNextId()] = result
|
|
197
213
|
return result
|
|
198
214
|
|
|
199
215
|
def _ReadObjectInternal(self) -> Tuple[definitions.V8SerializationTag, Any]:
|
|
@@ -385,7 +401,10 @@ class ValueDeserializer:
|
|
|
385
401
|
while self._PeekTag() != end_tag:
|
|
386
402
|
key = self._ReadObject()
|
|
387
403
|
value = self._ReadObject()
|
|
388
|
-
js_object
|
|
404
|
+
if isinstance(js_object, dict):
|
|
405
|
+
js_object[key] = value
|
|
406
|
+
else:
|
|
407
|
+
js_object.properties[key] = value
|
|
389
408
|
num_properties += 1
|
|
390
409
|
self._ConsumeTag(end_tag)
|
|
391
410
|
return num_properties
|
|
@@ -407,7 +426,7 @@ class ValueDeserializer:
|
|
|
407
426
|
js_array = JSArray()
|
|
408
427
|
_, length = self.decoder.DecodeUint32Varint()
|
|
409
428
|
for _ in range(length):
|
|
410
|
-
js_array.
|
|
429
|
+
js_array.Append(Undefined())
|
|
411
430
|
|
|
412
431
|
num_properties = self._ReadJSObjectProperties(
|
|
413
432
|
js_array.__dict__, definitions.V8SerializationTag.END_SPARSE_JS_ARRAY)
|
|
@@ -440,7 +459,7 @@ class ValueDeserializer:
|
|
|
440
459
|
|
|
441
460
|
if self.version < 11 and isinstance(array_object, Undefined):
|
|
442
461
|
continue
|
|
443
|
-
js_array.
|
|
462
|
+
js_array.Append(array_object)
|
|
444
463
|
|
|
445
464
|
num_properties = self._ReadJSObjectProperties(
|
|
446
465
|
js_array.__dict__, definitions.V8SerializationTag.END_DENSE_JS_ARRAY)
|
|
@@ -571,6 +590,7 @@ class ValueDeserializer:
|
|
|
571
590
|
if is_resizable:
|
|
572
591
|
_, max_byte_length = self.decoder.DecodeUint32Varint()
|
|
573
592
|
if byte_length > max_byte_length:
|
|
593
|
+
self.objects[next_id] = array_buffer
|
|
574
594
|
return array_buffer
|
|
575
595
|
if byte_length:
|
|
576
596
|
_, array_buffer = self.decoder.ReadBytes(byte_length)
|
|
@@ -589,7 +609,7 @@ class ValueDeserializer:
|
|
|
589
609
|
else:
|
|
590
610
|
flags = 0
|
|
591
611
|
|
|
592
|
-
return
|
|
612
|
+
return ArrayBufferView(
|
|
593
613
|
buffer=buffer,
|
|
594
614
|
tag=definitions.V8ArrayBufferViewTag(tag[0]),
|
|
595
615
|
offset=byte_offset,
|
|
@@ -20,10 +20,13 @@ from datetime import datetime
|
|
|
20
20
|
import json
|
|
21
21
|
import pathlib
|
|
22
22
|
|
|
23
|
+
from dfindexeddb import utils
|
|
23
24
|
from dfindexeddb import version
|
|
24
25
|
from dfindexeddb.indexeddb.chromium import blink
|
|
25
26
|
from dfindexeddb.indexeddb.chromium import record as chromium_record
|
|
26
27
|
from dfindexeddb.indexeddb.chromium import v8
|
|
28
|
+
from dfindexeddb.indexeddb.firefox import gecko
|
|
29
|
+
from dfindexeddb.indexeddb.firefox import record as firefox_record
|
|
27
30
|
from dfindexeddb.indexeddb.safari import record as safari_record
|
|
28
31
|
|
|
29
32
|
|
|
@@ -36,9 +39,9 @@ class Encoder(json.JSONEncoder):
|
|
|
36
39
|
"""A JSON encoder class for dfindexeddb fields."""
|
|
37
40
|
def default(self, o):
|
|
38
41
|
if dataclasses.is_dataclass(o):
|
|
39
|
-
o_dict =
|
|
42
|
+
o_dict = utils.asdict(o)
|
|
40
43
|
return o_dict
|
|
41
|
-
if isinstance(o, bytes):
|
|
44
|
+
if isinstance(o, (bytes, bytearray)):
|
|
42
45
|
out = []
|
|
43
46
|
for x in o:
|
|
44
47
|
if chr(x) not in _VALID_PRINTABLE_CHARACTERS:
|
|
@@ -50,6 +53,8 @@ class Encoder(json.JSONEncoder):
|
|
|
50
53
|
return o.isoformat()
|
|
51
54
|
if isinstance(o, v8.Undefined):
|
|
52
55
|
return "<undefined>"
|
|
56
|
+
if isinstance(o, v8.JSArray):
|
|
57
|
+
return o.__dict__
|
|
53
58
|
if isinstance(o, v8.Null):
|
|
54
59
|
return "<null>"
|
|
55
60
|
if isinstance(o, set):
|
|
@@ -72,18 +77,31 @@ def _Output(structure, output):
|
|
|
72
77
|
|
|
73
78
|
|
|
74
79
|
def BlinkCommand(args):
|
|
75
|
-
"""The CLI for processing a file as a blink value."""
|
|
80
|
+
"""The CLI for processing a file as a blink-encoded value."""
|
|
76
81
|
with open(args.source, 'rb') as fd:
|
|
77
82
|
buffer = fd.read()
|
|
78
83
|
blink_value = blink.V8ScriptValueDecoder.FromBytes(buffer)
|
|
79
84
|
_Output(blink_value, output=args.output)
|
|
80
85
|
|
|
81
86
|
|
|
87
|
+
def GeckoCommand(args):
|
|
88
|
+
"""The CLI for processing a file as a gecko-encoded value."""
|
|
89
|
+
with open(args.source, 'rb') as fd:
|
|
90
|
+
buffer = fd.read()
|
|
91
|
+
blink_value = gecko.JSStructuredCloneDecoder.FromBytes(buffer)
|
|
92
|
+
_Output(blink_value, output=args.output)
|
|
93
|
+
|
|
94
|
+
|
|
82
95
|
def DbCommand(args):
|
|
83
96
|
"""The CLI for processing a directory as IndexedDB."""
|
|
84
97
|
if args.format in ('chrome', 'chromium'):
|
|
85
98
|
for db_record in chromium_record.FolderReader(
|
|
86
|
-
args.source).GetRecords(
|
|
99
|
+
args.source).GetRecords(
|
|
100
|
+
use_manifest=args.use_manifest,
|
|
101
|
+
use_sequence_number=args.use_sequence_number):
|
|
102
|
+
_Output(db_record, output=args.output)
|
|
103
|
+
elif args.format == 'firefox':
|
|
104
|
+
for db_record in firefox_record.FileReader(args.source).Records():
|
|
87
105
|
_Output(db_record, output=args.output)
|
|
88
106
|
elif args.format == 'safari':
|
|
89
107
|
for db_record in safari_record.FileReader(args.source).Records():
|
|
@@ -130,6 +148,24 @@ def App():
|
|
|
130
148
|
help='Output format. Default is json')
|
|
131
149
|
parser_blink.set_defaults(func=BlinkCommand)
|
|
132
150
|
|
|
151
|
+
parser_gecko = subparsers.add_parser(
|
|
152
|
+
'gecko', help='Parse a file as a gecko-encoded value.')
|
|
153
|
+
parser_gecko.add_argument(
|
|
154
|
+
'-s', '--source',
|
|
155
|
+
required=True,
|
|
156
|
+
type=pathlib.Path,
|
|
157
|
+
help='The source file.')
|
|
158
|
+
parser_gecko.add_argument(
|
|
159
|
+
'-o',
|
|
160
|
+
'--output',
|
|
161
|
+
choices=[
|
|
162
|
+
'json',
|
|
163
|
+
'jsonl',
|
|
164
|
+
'repr'],
|
|
165
|
+
default='json',
|
|
166
|
+
help='Output format. Default is json')
|
|
167
|
+
parser_gecko.set_defaults(func=GeckoCommand)
|
|
168
|
+
|
|
133
169
|
parser_db = subparsers.add_parser(
|
|
134
170
|
'db', help='Parse a directory as IndexedDB.')
|
|
135
171
|
parser_db.add_argument(
|
|
@@ -139,15 +175,22 @@ def App():
|
|
|
139
175
|
help=(
|
|
140
176
|
'The source IndexedDB folder (for chrome/chromium) '
|
|
141
177
|
'or file (for safari).'))
|
|
178
|
+
recover_group = parser_db.add_mutually_exclusive_group()
|
|
179
|
+
recover_group.add_argument(
|
|
180
|
+
'--use_manifest',
|
|
181
|
+
action='store_true',
|
|
182
|
+
help='Use manifest file to determine active/deleted records.')
|
|
183
|
+
recover_group.add_argument(
|
|
184
|
+
'--use_sequence_number',
|
|
185
|
+
action='store_true',
|
|
186
|
+
help=(
|
|
187
|
+
'Use sequence number and file offset to determine active/deleted '
|
|
188
|
+
'records.'))
|
|
142
189
|
parser_db.add_argument(
|
|
143
190
|
'--format',
|
|
144
191
|
required=True,
|
|
145
|
-
choices=['chromium', 'chrome', 'safari'],
|
|
192
|
+
choices=['chromium', 'chrome', 'firefox', 'safari'],
|
|
146
193
|
help='The type of IndexedDB to parse.')
|
|
147
|
-
parser_db.add_argument(
|
|
148
|
-
'--use_manifest',
|
|
149
|
-
action='store_true',
|
|
150
|
-
help='Use manifest file to determine active/deleted records.')
|
|
151
194
|
parser_db.add_argument(
|
|
152
195
|
'-o',
|
|
153
196
|
'--output',
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright 2024 Google LLC
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
"""Definitions for Firefox IndexedDB."""
|
|
16
|
+
from enum import IntEnum
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class IndexedDBKeyType(IntEnum):
|
|
20
|
+
"""IndexedDB Key Types."""
|
|
21
|
+
TERMINATOR = 0
|
|
22
|
+
FLOAT = 0x10
|
|
23
|
+
DATE = 0x20
|
|
24
|
+
STRING = 0x30
|
|
25
|
+
BINARY = 0x40
|
|
26
|
+
ARRAY = 0x50
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
MAX_ARRAY_COLLAPSE = 3
|
|
30
|
+
MAX_RECURSION_DEPTH = 64
|
|
31
|
+
MAX_LENGTH = (1 << 30) - 2
|
|
32
|
+
ONE_BYTE_LIMIT = 0x7E
|
|
33
|
+
TWO_BYTE_LIMIT = 0x3FFF + 0x7F
|
|
34
|
+
ONE_BYTE_ADJUST = 1
|
|
35
|
+
TWO_BYTE_ADJUST = -0x7F
|
|
36
|
+
THREE_BYTE_SHIFT = 6
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class StructuredDataType(IntEnum):
|
|
40
|
+
"""Structured Data Types."""
|
|
41
|
+
FLOAT_MAX = 0xFFF00000
|
|
42
|
+
HEADER = 0xFFF10000
|
|
43
|
+
NULL = 0xFFFF0000
|
|
44
|
+
UNDEFINED = 0xFFFF0001
|
|
45
|
+
BOOLEAN = 0xFFFF0002
|
|
46
|
+
INT32 = 0xFFFF0003
|
|
47
|
+
STRING = 0xFFFF0004
|
|
48
|
+
DATE_OBJECT = 0xFFFF0005
|
|
49
|
+
REGEXP_OBJECT = 0xFFFF0006
|
|
50
|
+
ARRAY_OBJECT = 0xFFFF0007
|
|
51
|
+
OBJECT_OBJECT = 0xFFFF0008
|
|
52
|
+
ARRAY_BUFFER_OBJECT_V2 = 0xFFFF0009
|
|
53
|
+
BOOLEAN_OBJECT = 0xFFFF000A
|
|
54
|
+
STRING_OBJECT = 0xFFFF000B
|
|
55
|
+
NUMBER_OBJECT = 0xFFFF000C
|
|
56
|
+
BACK_REFERENCE_OBJECT = 0xFFFF000D
|
|
57
|
+
DO_NOT_USE_1 = 0xFFFF000E
|
|
58
|
+
DO_NOT_USE_2 = 0xFFFF000F
|
|
59
|
+
TYPED_ARRAY_OBJECT_V2 = 0xFFFF0010
|
|
60
|
+
MAP_OBJECT = 0xFFFF0011
|
|
61
|
+
SET_OBJECT = 0xFFFF0012
|
|
62
|
+
END_OF_KEYS = 0xFFFF0013
|
|
63
|
+
DO_NOT_USE_3 = 0xFFFF0014
|
|
64
|
+
DATA_VIEW_OBJECT_V2 = 0xFFFF0015
|
|
65
|
+
SAVED_FRAME_OBJECT = 0xFFFF0016
|
|
66
|
+
JSPRINCIPALS = 0xFFFF0017
|
|
67
|
+
NULL_JSPRINCIPALS = 0xFFFF0018
|
|
68
|
+
RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM = 0xFFFF0019
|
|
69
|
+
RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM = 0xFFFF001A
|
|
70
|
+
SHARED_ARRAY_BUFFER_OBJECT = 0xFFFF001B
|
|
71
|
+
SHARED_WASM_MEMORY_OBJECT = 0xFFFF001C
|
|
72
|
+
BIGINT = 0xFFFF001D
|
|
73
|
+
BIGINT_OBJECT = 0xFFFF001E
|
|
74
|
+
ARRAY_BUFFER_OBJECT = 0xFFFF001F
|
|
75
|
+
TYPED_ARRAY_OBJECT = 0xFFFF0020
|
|
76
|
+
DATA_VIEW_OBJECT = 0xFFFF0021
|
|
77
|
+
ERROR_OBJECT = 0xFFFF0022
|
|
78
|
+
RESIZABLE_ARRAY_BUFFER_OBJECT = 0xFFFF0023
|
|
79
|
+
GROWABLE_SHARED_ARRAY_BUFFER_OBJECT = 0xFFFF0024
|
|
80
|
+
TYPED_ARRAY_V1_INT8 = 0xFFFF0100
|
|
81
|
+
TYPED_ARRAY_V1_UINT8 = 0xFFFF0101
|
|
82
|
+
TYPED_ARRAY_V1_INT16 = 0xFFFF0102
|
|
83
|
+
TYPED_ARRAY_V1_UINT16 = 0xFFFF0103
|
|
84
|
+
TYPED_ARRAY_V1_INT32 = 0xFFFF0104
|
|
85
|
+
TYPED_ARRAY_V1_UINT32 = 0xFFFF0105
|
|
86
|
+
TYPED_ARRAY_V1_FLOAT32 = 0xFFFF0106
|
|
87
|
+
TYPED_ARRAY_V1_FLOAT64 = 0xFFFF0107
|
|
88
|
+
TYPED_ARRAY_V1_UINT8_CLAMPED = 0xFFFF0108
|
|
89
|
+
TRANSFER_MAP_HEADER = 0xFFFF0200
|
|
90
|
+
TRANSFER_MAP_PENDING_ENTRY = 0xFFFF0201
|
|
91
|
+
TRANSFER_MAP_ARRAY_BUFFER = 0xFFFF0202
|
|
92
|
+
TRANSFER_MAP_STORED_ARRAY_BUFFER = 0xFFFF0203
|
|
93
|
+
TRANSFER_MAP_END_OF_BUILTIN_TYPES = 0xFFFF0204
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class StructuredCloneTags(IntEnum):
|
|
97
|
+
"""Structured Clone Tags."""
|
|
98
|
+
BLOB = 0xFFFF8001
|
|
99
|
+
FILE_WITHOUT_LASTMODIFIEDDATE = 0xFFFF8002
|
|
100
|
+
FILELIST = 0xFFFF8003
|
|
101
|
+
MUTABLEFILE = 0xFFFF8004
|
|
102
|
+
FILE = 0xFFFF8005
|
|
103
|
+
WASM_MODULE = 0xFFFF8006
|
|
104
|
+
IMAGEDATA = 0xFFFF8007
|
|
105
|
+
DOMPOINT = 0xFFFF8008
|
|
106
|
+
DOMPOINTREADONLY = 0xFFFF8009
|
|
107
|
+
CRYPTOKEY = 0xFFFF800A
|
|
108
|
+
NULL_PRINCIPAL = 0xFFFF800B
|
|
109
|
+
SYSTEM_PRINCIPAL = 0xFFFF800C
|
|
110
|
+
CONTENT_PRINCIPAL = 0xFFFF800D
|
|
111
|
+
DOMQUAD = 0xFFFF800E
|
|
112
|
+
RTCCERTIFICATE = 0xFFFF800F
|
|
113
|
+
DOMRECT = 0xFFFF8010
|
|
114
|
+
DOMRECTREADONLY = 0xFFFF8011
|
|
115
|
+
EXPANDED_PRINCIPAL = 0xFFFF8012
|
|
116
|
+
DOMMATRIX = 0xFFFF8013
|
|
117
|
+
URLSEARCHPARAMS = 0xFFFF8014
|
|
118
|
+
DOMMATRIXREADONLY = 0xFFFF8015
|
|
119
|
+
DOMEXCEPTION = 0xFFFF80016
|
|
120
|
+
EMPTY_SLOT_9 = 0xFFFF8017
|
|
121
|
+
STRUCTUREDCLONETESTER = 0xFFFF8018
|
|
122
|
+
FILESYSTEMHANDLE = 0xFFFF8019
|
|
123
|
+
FILESYSTEMFILEHANDLE = 0xFFFF801A
|
|
124
|
+
FILESYSTEMDIRECTORYHANDLE = 0xFFFF801B
|
|
125
|
+
IMAGEBITMAP = 0xFFFF801C
|
|
126
|
+
MAP_MESSAGEPORT = 0xFFFF801D
|
|
127
|
+
FORMDATA = 0xFFFF801E
|
|
128
|
+
CANVAS = 0xFFFF801F # This tag is for OffscreenCanvas.
|
|
129
|
+
DIRECTORY = 0xFFFF8020
|
|
130
|
+
INPUTSTREAM = 0xFFFF8021
|
|
131
|
+
STRUCTURED_CLONE_HOLDER = 0xFFFF8022
|
|
132
|
+
BROWSING_CONTEXT = 0xFFFF8023
|
|
133
|
+
CLONED_ERROR_OBJECT = 0xFFFF8024
|
|
134
|
+
READABLESTREAM = 0xFFFF8025
|
|
135
|
+
WRITABLESTREAM = 0xFFFF8026
|
|
136
|
+
TRANSFORMSTREAM = 0xFFFF8027
|
|
137
|
+
VIDEOFRAME = 0xFFFF8028
|
|
138
|
+
ENCODEDVIDEOCHUNK = 0xFFFF8029
|
|
139
|
+
AUDIODATA = 0xFFFF8030
|
|
140
|
+
ENCODEDAUDIOCHUNK = 0xFFFF8031
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
FRAME_HEADER = b'\xff\x06\x00\x00sNaPpY'
|