dfindexeddb 20240225__py3-none-any.whl → 20240229__py3-none-any.whl
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/cli.py +147 -0
- dfindexeddb/indexeddb/chromium.py +54 -0
- dfindexeddb/version.py +1 -1
- {dfindexeddb-20240225.dist-info → dfindexeddb-20240229.dist-info}/METADATA +54 -14
- {dfindexeddb-20240225.dist-info → dfindexeddb-20240229.dist-info}/RECORD +10 -8
- dfindexeddb-20240229.dist-info/entry_points.txt +2 -0
- {dfindexeddb-20240225.dist-info → dfindexeddb-20240229.dist-info}/AUTHORS +0 -0
- {dfindexeddb-20240225.dist-info → dfindexeddb-20240229.dist-info}/LICENSE +0 -0
- {dfindexeddb-20240225.dist-info → dfindexeddb-20240229.dist-info}/WHEEL +0 -0
- {dfindexeddb-20240225.dist-info → dfindexeddb-20240229.dist-info}/top_level.txt +0 -0
dfindexeddb/cli.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
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
|
+
"""A CLI tool for dfindexeddb."""
|
|
16
|
+
import argparse
|
|
17
|
+
import dataclasses
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
import json
|
|
20
|
+
import pathlib
|
|
21
|
+
import sys
|
|
22
|
+
|
|
23
|
+
from dfindexeddb.leveldb import log
|
|
24
|
+
from dfindexeddb.leveldb import ldb
|
|
25
|
+
from dfindexeddb.indexeddb import chromium
|
|
26
|
+
from dfindexeddb.indexeddb import v8
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Encoder(json.JSONEncoder):
|
|
30
|
+
"""A JSON encoder class for dfindexeddb fields."""
|
|
31
|
+
def default(self, o):
|
|
32
|
+
if isinstance(o, bytes):
|
|
33
|
+
return o.decode(encoding='ascii', errors='backslashreplace')
|
|
34
|
+
if isinstance(o, datetime):
|
|
35
|
+
return o.isoformat()
|
|
36
|
+
if isinstance(o, v8.Undefined):
|
|
37
|
+
return "<undefined>"
|
|
38
|
+
if isinstance(o, v8.Null):
|
|
39
|
+
return "<null>"
|
|
40
|
+
if isinstance(o, set):
|
|
41
|
+
return list(o)
|
|
42
|
+
if isinstance(o, v8.RegExp):
|
|
43
|
+
return str(o)
|
|
44
|
+
return json.JSONEncoder.default(self, o)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _Output(structure, to_json=False):
|
|
48
|
+
"""Helper method to output parsed structure to stdout."""
|
|
49
|
+
if to_json:
|
|
50
|
+
structure_dict = dataclasses.asdict(structure)
|
|
51
|
+
print(json.dumps(structure_dict, indent=2, cls=Encoder))
|
|
52
|
+
else:
|
|
53
|
+
print(structure)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def IndexeddbCommand(args):
|
|
57
|
+
"""The CLI for processing a log/ldb file as indexeddb."""
|
|
58
|
+
if args.source.name.endswith('.log'):
|
|
59
|
+
records = list(
|
|
60
|
+
log.LogFileReader(args.source).GetKeyValueRecords())
|
|
61
|
+
elif args.source.name.endswith('.ldb'):
|
|
62
|
+
records = list(
|
|
63
|
+
ldb.LdbFileReader(args.source).GetKeyValueRecords())
|
|
64
|
+
else:
|
|
65
|
+
print('Unsupported file type.', file=sys.stderr)
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
for record in records:
|
|
69
|
+
record = chromium.IndexedDBRecord.FromLevelDBRecord(record)
|
|
70
|
+
_Output(record, to_json=args.json)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def LdbCommand(args):
|
|
74
|
+
"""The CLI for processing ldb files."""
|
|
75
|
+
ldb_file = ldb.LdbFileReader(args.source)
|
|
76
|
+
|
|
77
|
+
if args.structure_type == 'blocks':
|
|
78
|
+
# Prints block information.
|
|
79
|
+
for block in ldb_file.GetBlocks():
|
|
80
|
+
_Output(block, to_json=args.json)
|
|
81
|
+
|
|
82
|
+
elif args.structure_type == 'records':
|
|
83
|
+
# Prints key value record information.
|
|
84
|
+
for record in ldb_file.GetKeyValueRecords():
|
|
85
|
+
_Output(record, to_json=args.json)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def LogCommand(args):
|
|
89
|
+
"""The CLI for processing log files."""
|
|
90
|
+
log_file = log.LogFileReader(args.source)
|
|
91
|
+
|
|
92
|
+
if args.structure_type == 'blocks':
|
|
93
|
+
# Prints block information.
|
|
94
|
+
for block in log_file.GetBlocks():
|
|
95
|
+
_Output(block, to_json=args.json)
|
|
96
|
+
|
|
97
|
+
elif args.structure_type == 'physical_records':
|
|
98
|
+
# Prints log file physical record information.
|
|
99
|
+
for log_file_record in log_file.GetPhysicalRecords():
|
|
100
|
+
_Output(log_file_record, to_json=args.json)
|
|
101
|
+
|
|
102
|
+
elif args.structure_type == 'write_batches':
|
|
103
|
+
# Prints log file batch information.
|
|
104
|
+
for batch in log_file.GetWriteBatches():
|
|
105
|
+
_Output(batch, to_json=args.json)
|
|
106
|
+
|
|
107
|
+
elif args.structure_type in ('parsed_internal_key', 'records'):
|
|
108
|
+
# Prints key value record information.
|
|
109
|
+
for record in log_file.GetKeyValueRecords():
|
|
110
|
+
_Output(record, to_json=args.json)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def App():
|
|
114
|
+
"""The CLI app entrypoint."""
|
|
115
|
+
parser = argparse.ArgumentParser(
|
|
116
|
+
prog='dfindexeddb',
|
|
117
|
+
description='A cli tool for the dfindexeddb package')
|
|
118
|
+
|
|
119
|
+
parser.add_argument(
|
|
120
|
+
'-s', '--source', required=True, type=pathlib.Path,
|
|
121
|
+
help='The source leveldb file')
|
|
122
|
+
parser.add_argument('--json', action='store_true', help='Output as JSON')
|
|
123
|
+
subparsers = parser.add_subparsers(required=True)
|
|
124
|
+
|
|
125
|
+
parser_log = subparsers.add_parser('log')
|
|
126
|
+
parser_log.add_argument(
|
|
127
|
+
'structure_type', choices=[
|
|
128
|
+
'blocks',
|
|
129
|
+
'physical_records',
|
|
130
|
+
'write_batches',
|
|
131
|
+
'parsed_internal_key',
|
|
132
|
+
'records'])
|
|
133
|
+
parser_log.set_defaults(func=LogCommand)
|
|
134
|
+
|
|
135
|
+
parser_log = subparsers.add_parser('ldb')
|
|
136
|
+
parser_log.add_argument(
|
|
137
|
+
'structure_type', choices=[
|
|
138
|
+
'blocks',
|
|
139
|
+
'records'])
|
|
140
|
+
parser_log.set_defaults(func=LdbCommand)
|
|
141
|
+
|
|
142
|
+
parser_log = subparsers.add_parser('indexeddb')
|
|
143
|
+
parser_log.set_defaults(func=IndexeddbCommand)
|
|
144
|
+
|
|
145
|
+
args = parser.parse_args()
|
|
146
|
+
|
|
147
|
+
args.func(args)
|
|
@@ -17,11 +17,16 @@ from __future__ import annotations
|
|
|
17
17
|
from dataclasses import dataclass, field
|
|
18
18
|
from datetime import datetime
|
|
19
19
|
import io
|
|
20
|
+
import sys
|
|
21
|
+
import traceback
|
|
20
22
|
from typing import Any, BinaryIO, Optional, Tuple, Type, TypeVar, Union
|
|
21
23
|
|
|
22
24
|
from dfindexeddb import errors
|
|
23
25
|
from dfindexeddb import utils
|
|
26
|
+
from dfindexeddb.indexeddb import blink
|
|
24
27
|
from dfindexeddb.indexeddb import definitions
|
|
28
|
+
from dfindexeddb.leveldb import ldb
|
|
29
|
+
from dfindexeddb.leveldb import log
|
|
25
30
|
|
|
26
31
|
|
|
27
32
|
T = TypeVar('T')
|
|
@@ -1281,3 +1286,52 @@ class ObjectStore:
|
|
|
1281
1286
|
id: int
|
|
1282
1287
|
name: str
|
|
1283
1288
|
records: list = field(default_factory=list, repr=False)
|
|
1289
|
+
|
|
1290
|
+
|
|
1291
|
+
@dataclass
|
|
1292
|
+
class IndexedDBRecord:
|
|
1293
|
+
"""An IndexedDB Record.
|
|
1294
|
+
|
|
1295
|
+
Attributes:
|
|
1296
|
+
offset: the offset of the record.
|
|
1297
|
+
key: the key of the record.
|
|
1298
|
+
value: the value of the record.
|
|
1299
|
+
sequence_number: if available, the sequence number of the record.
|
|
1300
|
+
type: the type of the record.
|
|
1301
|
+
"""
|
|
1302
|
+
offset: int
|
|
1303
|
+
key: Any
|
|
1304
|
+
value: Any
|
|
1305
|
+
sequence_number: int
|
|
1306
|
+
type: int
|
|
1307
|
+
|
|
1308
|
+
@classmethod
|
|
1309
|
+
def FromLevelDBRecord(
|
|
1310
|
+
cls, record: Union[ldb.LdbKeyValueRecord, log.ParsedInternalKey]
|
|
1311
|
+
) -> IndexedDBRecord:
|
|
1312
|
+
"""Returns an IndexedDBRecord from a ParsedInternalKey."""
|
|
1313
|
+
idb_key = IndexedDbKey.FromBytes(
|
|
1314
|
+
record.key, base_offset=record.offset)
|
|
1315
|
+
|
|
1316
|
+
idb_value = idb_key.ParseValue(record.value)
|
|
1317
|
+
if isinstance(idb_key, ObjectStoreDataKey):
|
|
1318
|
+
|
|
1319
|
+
# The ObjectStoreDataKey value should decode as a 2-tuple comprising
|
|
1320
|
+
# a version integer and a SSV as a raw byte string
|
|
1321
|
+
if (isinstance(idb_value, tuple) and len(idb_value) == 2 and
|
|
1322
|
+
isinstance(idb_value[1], bytes)):
|
|
1323
|
+
|
|
1324
|
+
try:
|
|
1325
|
+
blink_value = blink.V8ScriptValueDecoder.FromBytes(idb_value[1])
|
|
1326
|
+
idb_value = idb_value[0], blink_value
|
|
1327
|
+
except (errors.ParserError, errors.DecoderError) as err:
|
|
1328
|
+
print(f'Error parsing blink value: {err}', file=sys.stderr)
|
|
1329
|
+
print(f'Traceback: {traceback.format_exc()}', file=sys.stderr)
|
|
1330
|
+
|
|
1331
|
+
return cls(
|
|
1332
|
+
offset=record.offset,
|
|
1333
|
+
key=idb_key,
|
|
1334
|
+
value=idb_value,
|
|
1335
|
+
sequence_number=record.sequence_number if hasattr(
|
|
1336
|
+
record, 'sequence_number') else None,
|
|
1337
|
+
type=record.type)
|
dfindexeddb/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dfindexeddb
|
|
3
|
-
Version:
|
|
3
|
+
Version: 20240229
|
|
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>
|
|
@@ -236,6 +236,12 @@ include:
|
|
|
236
236
|
* emails and contact information from an e-mail application,
|
|
237
237
|
* images and metadata from a photo gallery application
|
|
238
238
|
|
|
239
|
+
## Installation
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
$ pip install dfindexeddb
|
|
243
|
+
```
|
|
244
|
+
|
|
239
245
|
## Installation from source
|
|
240
246
|
|
|
241
247
|
### Linux
|
|
@@ -256,23 +262,57 @@ include:
|
|
|
256
262
|
$ pip install .
|
|
257
263
|
```
|
|
258
264
|
|
|
259
|
-
##
|
|
265
|
+
## Usage
|
|
266
|
+
|
|
267
|
+
A CLI tool is available after installation:
|
|
260
268
|
|
|
261
|
-
|
|
262
|
-
|
|
269
|
+
```
|
|
270
|
+
$ dfindexeddb -h
|
|
271
|
+
usage: dfindexeddb [-h] -s SOURCE [--json] {log,ldb,indexeddb} ...
|
|
263
272
|
|
|
264
|
-
|
|
265
|
-
them to standard output.
|
|
266
|
-
- Optionally, you can also install the `leveldb` python package if you
|
|
267
|
-
would prefer to use a native leveldb library instead of the leveldb parser in
|
|
268
|
-
this repository.
|
|
269
|
-
* `tools/ldb_dump.py` - parses structures from a LevelDB .ldb file and prints
|
|
270
|
-
them to standard output.
|
|
271
|
-
* `tools/log_dump.py` - parses structures from a LevelDB .log file and prints
|
|
272
|
-
them to standard output.
|
|
273
|
+
A cli tool for the dfindexeddb package
|
|
273
274
|
|
|
275
|
+
positional arguments:
|
|
276
|
+
{log,ldb,indexeddb}
|
|
277
|
+
|
|
278
|
+
options:
|
|
279
|
+
-s SOURCE, --source SOURCE
|
|
280
|
+
The source leveldb file
|
|
281
|
+
--json Output as JSON
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
To parse a LevelDB .log file:
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
$ dfindexeddb -s <SOURCE> log -h
|
|
288
|
+
usage: dfindexeddb log [-h] {blocks,physical_records,write_batches,parsed_internal_key,records}
|
|
274
289
|
|
|
290
|
+
positional arguments:
|
|
291
|
+
{blocks,physical_records,write_batches,parsed_internal_key,records}
|
|
275
292
|
|
|
293
|
+
options:
|
|
294
|
+
-h, --help show this help message and exit
|
|
276
295
|
```
|
|
277
|
-
|
|
296
|
+
|
|
297
|
+
To parse a LevelDB .ldb file:
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
$ dfindexeddb -s <SOURCE> ldb -h
|
|
301
|
+
usage: dfindexeddb ldb [-h] {blocks,records}
|
|
302
|
+
|
|
303
|
+
positional arguments:
|
|
304
|
+
{blocks,records}
|
|
305
|
+
|
|
306
|
+
options:
|
|
307
|
+
-h, --help show this help message and exit
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
To parse a LevelDB .ldb or .log file as IndexedDB:
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
$ dfindexeddb -s <SOURCE> indexeddb -h
|
|
314
|
+
usage: dfindexeddb indexeddb [-h]
|
|
315
|
+
|
|
316
|
+
options:
|
|
317
|
+
-h, --help show this help message and exit
|
|
278
318
|
```
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
dfindexeddb/__init__.py,sha256=KPYL9__l8od6_OyDfGRTgaJ6iy_fqIgZ-dS2S-e3Rac,599
|
|
2
|
+
dfindexeddb/cli.py,sha256=LD2-BwmXC3qFcJwgP09QDFxU3HOGtsg0Kbtyx-hAqzA,4525
|
|
2
3
|
dfindexeddb/errors.py,sha256=PNpwyf_lrPc4TE77oAakX3mu5D_YcP3f80wq8Y1LkvY,749
|
|
3
4
|
dfindexeddb/utils.py,sha256=g9iiGRX4DB1wFBSBHa6b9lg7JzAdE0SN0DrdB2aS_Co,10091
|
|
4
|
-
dfindexeddb/version.py,sha256=
|
|
5
|
+
dfindexeddb/version.py,sha256=XwHKYiT0CeLWo90AaJfOYHD1mEEgIlUUSB6ot_rU8wc,750
|
|
5
6
|
dfindexeddb/indexeddb/__init__.py,sha256=kExXSVBCTKCD5BZJkdMfUMqGksH-DMJxP2_lI0gq-BE,575
|
|
6
7
|
dfindexeddb/indexeddb/blink.py,sha256=MblpYfv-ByG7n_fjYKu2EUhpfVJdUveoW4oSAg5T4tY,3534
|
|
7
|
-
dfindexeddb/indexeddb/chromium.py,sha256=
|
|
8
|
+
dfindexeddb/indexeddb/chromium.py,sha256=Anw6QIU7PrsxpUW7qxrUXRb5vBRcxozhv3mHov7Ti8k,43984
|
|
8
9
|
dfindexeddb/indexeddb/definitions.py,sha256=yline3y3gmZx6s-dwjpPDNs5HO4zT6KZqPWQfEsHDoM,7413
|
|
9
10
|
dfindexeddb/indexeddb/v8.py,sha256=ldqpc9T1kG7BOdjnHjQ5hNO9OCXZ3_Zd6vRSpC-NrEA,21893
|
|
10
11
|
dfindexeddb/leveldb/__init__.py,sha256=KPYL9__l8od6_OyDfGRTgaJ6iy_fqIgZ-dS2S-e3Rac,599
|
|
11
12
|
dfindexeddb/leveldb/ldb.py,sha256=uShhXjQe4Sz3dn54IXbGxRtE6D8RNpu1NDy5Zb0P9LA,7927
|
|
12
13
|
dfindexeddb/leveldb/log.py,sha256=cyMfjDz5a6gfGb5NonxC1Y72OmHYBWzYK8UMVzP_umw,8532
|
|
13
|
-
dfindexeddb-
|
|
14
|
-
dfindexeddb-
|
|
15
|
-
dfindexeddb-
|
|
16
|
-
dfindexeddb-
|
|
17
|
-
dfindexeddb-
|
|
18
|
-
dfindexeddb-
|
|
14
|
+
dfindexeddb-20240229.dist-info/AUTHORS,sha256=QbvjbAom57fpEkekkCVFUj0B9KUMGraR510aUMBC-PE,286
|
|
15
|
+
dfindexeddb-20240229.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
16
|
+
dfindexeddb-20240229.dist-info/METADATA,sha256=ILzTLaRO96ALuHL8d72tt3a3shliEGzGHZC54n5wPpc,15933
|
|
17
|
+
dfindexeddb-20240229.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
18
|
+
dfindexeddb-20240229.dist-info/entry_points.txt,sha256=UsfPLLhTiVAAtZ8Rq3ZR7JNFGMuHqJy-tugGWonQWtc,52
|
|
19
|
+
dfindexeddb-20240229.dist-info/top_level.txt,sha256=X9OTaub1c8S_JJ7g-f8JdkhhdiZ4x1j4eni1hdUCwE4,12
|
|
20
|
+
dfindexeddb-20240229.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|