linear-mcp-fast 0.1.0__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.
- ccl_chromium_reader/__init__.py +2 -0
- ccl_chromium_reader/ccl_chromium_cache.py +1335 -0
- ccl_chromium_reader/ccl_chromium_filesystem.py +302 -0
- ccl_chromium_reader/ccl_chromium_history.py +357 -0
- ccl_chromium_reader/ccl_chromium_indexeddb.py +1060 -0
- ccl_chromium_reader/ccl_chromium_localstorage.py +454 -0
- ccl_chromium_reader/ccl_chromium_notifications.py +268 -0
- ccl_chromium_reader/ccl_chromium_profile_folder.py +568 -0
- ccl_chromium_reader/ccl_chromium_sessionstorage.py +368 -0
- ccl_chromium_reader/ccl_chromium_snss2.py +332 -0
- ccl_chromium_reader/ccl_shared_proto_db_downloads.py +189 -0
- ccl_chromium_reader/common.py +19 -0
- ccl_chromium_reader/download_common.py +78 -0
- ccl_chromium_reader/profile_folder_protocols.py +276 -0
- ccl_chromium_reader/serialization_formats/__init__.py +0 -0
- ccl_chromium_reader/serialization_formats/ccl_blink_value_deserializer.py +401 -0
- ccl_chromium_reader/serialization_formats/ccl_easy_chromium_pickle.py +133 -0
- ccl_chromium_reader/serialization_formats/ccl_protobuff.py +276 -0
- ccl_chromium_reader/serialization_formats/ccl_v8_value_deserializer.py +627 -0
- ccl_chromium_reader/storage_formats/__init__.py +0 -0
- ccl_chromium_reader/storage_formats/ccl_leveldb.py +582 -0
- ccl_simplesnappy/__init__.py +1 -0
- ccl_simplesnappy/ccl_simplesnappy.py +306 -0
- linear_mcp_fast/__init__.py +8 -0
- linear_mcp_fast/__main__.py +6 -0
- linear_mcp_fast/reader.py +433 -0
- linear_mcp_fast/server.py +367 -0
- linear_mcp_fast/store_detector.py +117 -0
- linear_mcp_fast-0.1.0.dist-info/METADATA +160 -0
- linear_mcp_fast-0.1.0.dist-info/RECORD +39 -0
- linear_mcp_fast-0.1.0.dist-info/WHEEL +5 -0
- linear_mcp_fast-0.1.0.dist-info/entry_points.txt +2 -0
- linear_mcp_fast-0.1.0.dist-info/top_level.txt +4 -0
- tools_and_utilities/Chromium_dump_local_storage.py +111 -0
- tools_and_utilities/Chromium_dump_session_storage.py +92 -0
- tools_and_utilities/benchmark.py +35 -0
- tools_and_utilities/ccl_chrome_audit.py +651 -0
- tools_and_utilities/dump_indexeddb_details.py +59 -0
- tools_and_utilities/dump_leveldb.py +53 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2020, CCL Forensics
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
5
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
6
|
+
the Software without restriction, including without limitation the rights to
|
|
7
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
8
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
9
|
+
so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import sys
|
|
24
|
+
import enum
|
|
25
|
+
import typing
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
|
|
28
|
+
from . import ccl_v8_value_deserializer
|
|
29
|
+
|
|
30
|
+
# See: https://chromium.googlesource.com/chromium/src/third_party/+/master/blink/renderer/bindings/core/v8/serialization
|
|
31
|
+
# https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules.cc
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# WebCoreStrings are read as (length:uint32_t, string:UTF8[length]).
|
|
35
|
+
# RawStrings are read as (length:uint32_t, string:UTF8[length]).
|
|
36
|
+
# RawUCharStrings are read as
|
|
37
|
+
# (length:uint32_t, string:UChar[length/sizeof(UChar)]).
|
|
38
|
+
# RawFiles are read as
|
|
39
|
+
# (path:WebCoreString, url:WebCoreStrng, type:WebCoreString).
|
|
40
|
+
# There is a reference table that maps object references (uint32_t) to
|
|
41
|
+
# v8::Values.
|
|
42
|
+
# Tokens marked with (ref) are inserted into the reference table and given the
|
|
43
|
+
# next object reference ID after decoding.
|
|
44
|
+
# All tags except InvalidTag, PaddingTag, ReferenceCountTag, VersionTag,
|
|
45
|
+
# GenerateFreshObjectTag and GenerateFreshArrayTag push their results to the
|
|
46
|
+
# deserialization stack.
|
|
47
|
+
# There is also an 'open' stack that is used to resolve circular references.
|
|
48
|
+
# Objects or arrays may contain self-references. Before we begin to deserialize
|
|
49
|
+
# the contents of these values, they are first given object reference IDs (by
|
|
50
|
+
# GenerateFreshObjectTag/GenerateFreshArrayTag); these reference IDs are then
|
|
51
|
+
# used with ObjectReferenceTag to tie the recursive knot.
|
|
52
|
+
|
|
53
|
+
__version__ = "0.3"
|
|
54
|
+
__description__ = "Partial reimplementation of the Blink Javascript Object Serialization"
|
|
55
|
+
__contact__ = "Alex Caithness"
|
|
56
|
+
|
|
57
|
+
__DEBUG = False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def log(msg, debug_only=True):
|
|
61
|
+
if __DEBUG or not debug_only:
|
|
62
|
+
caller_name = sys._getframe(1).f_code.co_name
|
|
63
|
+
caller_line = sys._getframe(1).f_code.co_firstlineno
|
|
64
|
+
print(f"{caller_name} ({caller_line}):\t{msg}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class BlobIndexType(enum.Enum):
|
|
68
|
+
Blob = 0
|
|
69
|
+
File = 1
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class BlobIndex:
|
|
74
|
+
index_type: BlobIndexType
|
|
75
|
+
index_id: int
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass(frozen=True)
|
|
79
|
+
class NativeFileHandle:
|
|
80
|
+
is_dir: bool
|
|
81
|
+
name: str
|
|
82
|
+
token_index: int
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(frozen=True)
|
|
86
|
+
class CryptoKey:
|
|
87
|
+
sub_type: "V8CryptoKeySubType"
|
|
88
|
+
algorithm_type: typing.Optional["V8CryptoKeyAlgorithm"]
|
|
89
|
+
hash_type: typing.Optional["V8CryptoKeyAlgorithm"]
|
|
90
|
+
asymmetric_key_type: typing.Optional["V8AsymmetricCryptoKeyType"]
|
|
91
|
+
byte_length: typing.Optional[int]
|
|
92
|
+
public_exponent: typing.Optional[bytes]
|
|
93
|
+
named_curve_type: typing.Optional["V8CryptoNamedCurve"]
|
|
94
|
+
key_usage: "V8CryptoKeyUsage"
|
|
95
|
+
key_data: bytes
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Constants:
|
|
99
|
+
tag_kMessagePortTag = b"M" # index:int -> MessagePort. Fills the result with
|
|
100
|
+
# transferred MessagePort.
|
|
101
|
+
tag_kMojoHandleTag = b"h" # index:int -> MojoHandle. Fills the result with
|
|
102
|
+
# transferred MojoHandle.
|
|
103
|
+
tag_kBlobTag = b"b" # uuid:WebCoreString, type:WebCoreString, size:uint64_t ->
|
|
104
|
+
# Blob (ref)
|
|
105
|
+
tag_kBlobIndexTag = b"i" # index:int32_t -> Blob (ref)
|
|
106
|
+
tag_kFileTag = b"f" # file:RawFile -> File (ref)
|
|
107
|
+
tag_kFileIndexTag = b"e" # index:int32_t -> File (ref)
|
|
108
|
+
tag_kDOMFileSystemTag = b"d" # type : int32_t, name:WebCoreString,
|
|
109
|
+
# uuid:WebCoreString -> FileSystem (ref)
|
|
110
|
+
tag_kNativeFileSystemFileHandleTag = b"n" # name:WebCoreString, index:uint32_t
|
|
111
|
+
# -> NativeFileSystemFileHandle (ref)
|
|
112
|
+
tag_kNativeFileSystemDirectoryHandleTag = b"N" # name:WebCoreString, index:uint32_t ->
|
|
113
|
+
# NativeFileSystemDirectoryHandle (ref)
|
|
114
|
+
tag_kFileListTag = b"l" # length:uint32_t, files:RawFile[length] -> FileList (ref)
|
|
115
|
+
tag_kFileListIndexTag = b"L" # length:uint32_t, files:int32_t[length] -> FileList (ref)
|
|
116
|
+
tag_kImageDataTag = b"#" # tags terminated by ImageSerializationTag::kEnd (see
|
|
117
|
+
# SerializedColorParams.h), width:uint32_t,
|
|
118
|
+
# height:uint32_t, pixelDataLength:uint64_t,
|
|
119
|
+
# data:byte[pixelDataLength]
|
|
120
|
+
# -> ImageData (ref)
|
|
121
|
+
tag_kImageBitmapTag = b"g" # tags terminated by ImageSerializationTag::kEnd (see
|
|
122
|
+
# SerializedColorParams.h), width:uint32_t,
|
|
123
|
+
# height:uint32_t, pixelDataLength:uint32_t,
|
|
124
|
+
# data:byte[pixelDataLength]
|
|
125
|
+
# -> ImageBitmap (ref)
|
|
126
|
+
tag_kImageBitmapTransferTag = b"G" # index:uint32_t -> ImageBitmap. For ImageBitmap transfer
|
|
127
|
+
tag_kOffscreenCanvasTransferTag = b"H" # index, width, height, id,
|
|
128
|
+
# filter_quality::uint32_t ->
|
|
129
|
+
# OffscreenCanvas. For OffscreenCanvas
|
|
130
|
+
# transfer
|
|
131
|
+
tag_kReadableStreamTransferTag = b"r" # index:uint32_t
|
|
132
|
+
tag_kTransformStreamTransferTag = b"m" # index:uint32_t
|
|
133
|
+
tag_kWritableStreamTransferTag = b"w" # index:uint32_t
|
|
134
|
+
tag_kDOMPointTag = b"Q" # x:Double, y:Double, z:Double, w:Double
|
|
135
|
+
tag_kDOMPointReadOnlyTag = b"W" # x:Double, y:Double, z:Double, w:Double
|
|
136
|
+
tag_kDOMRectTag = b"E" # x:Double, y:Double, width:Double, height:Double
|
|
137
|
+
tag_kDOMRectReadOnlyTag = b"R" # x:Double, y:Double, width:Double, height:Double
|
|
138
|
+
tag_kDOMQuadTag = b"T" # p1:Double, p2:Double, p3:Double, p4:Double
|
|
139
|
+
tag_kDOMMatrixTag = b"Y" # m11..m44: 16 Double
|
|
140
|
+
tag_kDOMMatrixReadOnlyTag = b"U" # m11..m44: 16 Double
|
|
141
|
+
tag_kDOMMatrix2DTag = b"I" # a..f: 6 Double
|
|
142
|
+
tag_kDOMMatrix2DReadOnlyTag = b"O" # a..f: 6 Double
|
|
143
|
+
tag_kCryptoKeyTag = b"K" # subtag:byte, props, usages:uint32_t,
|
|
144
|
+
# keyDataLength:uint32_t, keyData:byte[keyDataLength]
|
|
145
|
+
# If subtag=AesKeyTag:
|
|
146
|
+
# props = keyLengthBytes:uint32_t, algorithmId:uint32_t
|
|
147
|
+
# If subtag=HmacKeyTag:
|
|
148
|
+
# props = keyLengthBytes:uint32_t, hashId:uint32_t
|
|
149
|
+
# If subtag=RsaHashedKeyTag:
|
|
150
|
+
# props = algorithmId:uint32_t, type:uint32_t,
|
|
151
|
+
# modulusLengthBits:uint32_t,
|
|
152
|
+
# publicExponentLength:uint32_t,
|
|
153
|
+
# publicExponent:byte[publicExponentLength],
|
|
154
|
+
# hashId:uint32_t
|
|
155
|
+
# If subtag=EcKeyTag:
|
|
156
|
+
# props = algorithmId:uint32_t, type:uint32_t,
|
|
157
|
+
# namedCurve:uint32_t
|
|
158
|
+
tag_kRTCCertificateTag = b"k" # length:uint32_t, pemPrivateKey:WebCoreString,
|
|
159
|
+
# pemCertificate:WebCoreString
|
|
160
|
+
tag_kRTCEncodedAudioFrameTag = b"A" # uint32_t -> transferred audio frame ID
|
|
161
|
+
tag_kRTCEncodedVideoFrameTag = b"V" # uint32_t -> transferred video frame ID
|
|
162
|
+
tag_kVideoFrameTag = b"v" # uint32_t -> transferred video frame ID
|
|
163
|
+
|
|
164
|
+
# The following tags were used by the Shape Detection API implementation
|
|
165
|
+
# between M71 and M81. During these milestones, the API was always behind
|
|
166
|
+
# a flag. Usage was removed in https:#crrev.com/c/2040378.
|
|
167
|
+
tag_kDeprecatedDetectedBarcodeTag = b"B"
|
|
168
|
+
tag_kDeprecatedDetectedFaceTag = b"F"
|
|
169
|
+
tag_kDeprecatedDetectedTextTag = b"t"
|
|
170
|
+
|
|
171
|
+
tag_kDOMExceptionTag = b"x" # name:String,message:String,stack:String
|
|
172
|
+
tag_kVersionTag = b"\xff" # version:uint32_t -> Uses this as the file version.
|
|
173
|
+
tag_kTrailerOffsetTag = b"\xfe" # offset:uint64_t (fixed width, network order) from buffer, start size:uint32_t (fixed width, network order)
|
|
174
|
+
tag_kTrailerRequiresInterfacesTag = b"\xA0"
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class V8CryptoKeySubType(enum.IntEnum):
|
|
178
|
+
"""
|
|
179
|
+
See: third_party/blink/renderer/bindings/modules/v8/serialization/web_crypto_sub_tags.h
|
|
180
|
+
Used by the kCryptoKeyTag type
|
|
181
|
+
"""
|
|
182
|
+
AesKey = 1
|
|
183
|
+
HmacKey = 2
|
|
184
|
+
# ID 3 was used by RsaKeyTag, while still behind experimental flag.
|
|
185
|
+
RsaHashedKey = 4
|
|
186
|
+
EcKey = 5
|
|
187
|
+
NoParamsKey = 6
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class V8CryptoKeyAlgorithm(enum.IntEnum):
|
|
191
|
+
"""
|
|
192
|
+
See: third_party/blink/renderer/bindings/modules/v8/serialization/web_crypto_sub_tags.h
|
|
193
|
+
Used by the kCryptoKeyTag type
|
|
194
|
+
"""
|
|
195
|
+
AesCbcTag = 1
|
|
196
|
+
HmacTag = 2
|
|
197
|
+
RsaSsaPkcs1v1_5Tag = 3
|
|
198
|
+
# ID 4 was used by RsaEs, while still behind experimental flag.
|
|
199
|
+
Sha1Tag = 5
|
|
200
|
+
Sha256Tag = 6
|
|
201
|
+
Sha384Tag = 7
|
|
202
|
+
Sha512Tag = 8
|
|
203
|
+
AesGcmTag = 9
|
|
204
|
+
RsaOaepTag = 10
|
|
205
|
+
AesCtrTag = 11
|
|
206
|
+
AesKwTag = 12
|
|
207
|
+
RsaPssTag = 13
|
|
208
|
+
EcdsaTag = 14
|
|
209
|
+
EcdhTag = 15
|
|
210
|
+
HkdfTag = 16
|
|
211
|
+
Pbkdf2Tag = 17
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class V8AsymmetricCryptoKeyType(enum.IntEnum):
|
|
215
|
+
Public = 1
|
|
216
|
+
Private = 2
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class V8CryptoNamedCurve(enum.IntEnum):
|
|
220
|
+
"""
|
|
221
|
+
See: third_party/blink/renderer/bindings/modules/v8/serialization/web_crypto_sub_tags.h
|
|
222
|
+
Used by the kCryptoKeyTag type
|
|
223
|
+
"""
|
|
224
|
+
P256 = 1
|
|
225
|
+
P384 = 2
|
|
226
|
+
P521 = 3
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class V8CryptoKeyUsage(enum.IntFlag):
|
|
230
|
+
"""
|
|
231
|
+
See: third_party/blink/renderer/bindings/modules/v8/serialization/web_crypto_sub_tags.h
|
|
232
|
+
Used by the kCryptoKeyTag type
|
|
233
|
+
"""
|
|
234
|
+
kExtractableUsage = 1 << 0
|
|
235
|
+
kEncryptUsage = 1 << 1
|
|
236
|
+
kDecryptUsage = 1 << 2
|
|
237
|
+
kSignUsage = 1 << 3
|
|
238
|
+
kVerifyUsage = 1 << 4
|
|
239
|
+
kDeriveKeyUsage = 1 << 5
|
|
240
|
+
kWrapKeyUsage = 1 << 6
|
|
241
|
+
kUnwrapKeyUsage = 1 << 7
|
|
242
|
+
kDeriveBitsUsage = 1 << 8
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class BlinkV8Deserializer:
|
|
246
|
+
def _read_varint(self, stream) -> int:
|
|
247
|
+
return ccl_v8_value_deserializer.read_le_varint(stream)[0]
|
|
248
|
+
|
|
249
|
+
def _read_varint32(self, stream) -> int:
|
|
250
|
+
return ccl_v8_value_deserializer.read_le_varint(stream, is_32bit=True)[0]
|
|
251
|
+
|
|
252
|
+
def _read_utf8_string(self, stream: typing.BinaryIO) -> str:
|
|
253
|
+
length = self._read_varint32(stream)
|
|
254
|
+
raw_string = stream.read(length)
|
|
255
|
+
if len(raw_string) != length:
|
|
256
|
+
raise ValueError("Could not read all of the utf-8 data")
|
|
257
|
+
return raw_string.decode("utf-8")
|
|
258
|
+
|
|
259
|
+
# def _read_uint32(self, stream: typing.BinaryIO) -> int:
|
|
260
|
+
# raw = stream.read(4)
|
|
261
|
+
# if len(raw) < 4:
|
|
262
|
+
# raise ValueError("Could not read enough data when reading int32")
|
|
263
|
+
# return struct.unpack("<I", raw)[0]
|
|
264
|
+
|
|
265
|
+
def _read_file_index(self, stream: typing.BinaryIO) -> BlobIndex:
|
|
266
|
+
return BlobIndex(BlobIndexType.File, self._read_varint(stream))
|
|
267
|
+
|
|
268
|
+
def _read_blob_index(self, stream: typing.BinaryIO) -> BlobIndex:
|
|
269
|
+
return BlobIndex(BlobIndexType.Blob, self._read_varint(stream))
|
|
270
|
+
|
|
271
|
+
def _read_file_list_index(self, stream: typing.BinaryIO) -> typing.Iterable[BlobIndex]:
|
|
272
|
+
length = self._read_varint(stream)
|
|
273
|
+
result = [self._read_file_index(stream) for _ in range(length)]
|
|
274
|
+
return result
|
|
275
|
+
|
|
276
|
+
def _read_native_file_handle(self, is_dir: bool, stream: typing.BinaryIO) -> NativeFileHandle:
|
|
277
|
+
return NativeFileHandle(is_dir, self._read_utf8_string(stream), self._read_varint(stream))
|
|
278
|
+
|
|
279
|
+
def _read_crypto_key(self, stream: typing.BinaryIO):
|
|
280
|
+
sub_type = V8CryptoKeySubType(stream.read(1)[0])
|
|
281
|
+
|
|
282
|
+
if sub_type == V8CryptoKeySubType.AesKey:
|
|
283
|
+
algorithm_id = V8CryptoKeyAlgorithm(self._read_varint32(stream))
|
|
284
|
+
byte_length = self._read_varint32(stream)
|
|
285
|
+
params = {
|
|
286
|
+
"algorithm_type": algorithm_id,
|
|
287
|
+
"byte_length": byte_length,
|
|
288
|
+
"hash_type": None,
|
|
289
|
+
"named_curve_type": None,
|
|
290
|
+
"asymmetric_key_type": None,
|
|
291
|
+
"public_exponent": None
|
|
292
|
+
}
|
|
293
|
+
elif sub_type == V8CryptoKeySubType.HmacKey:
|
|
294
|
+
byte_length = self._read_varint32(stream)
|
|
295
|
+
hash_id = V8CryptoKeyAlgorithm(self._read_varint32(stream))
|
|
296
|
+
params = {
|
|
297
|
+
"byte_length": byte_length,
|
|
298
|
+
"hash_type": hash_id,
|
|
299
|
+
"algorithm_type": None,
|
|
300
|
+
"named_curve_type": None,
|
|
301
|
+
"asymmetric_key_type": None,
|
|
302
|
+
"public_exponent": None
|
|
303
|
+
}
|
|
304
|
+
elif sub_type == V8CryptoKeySubType.RsaHashedKey:
|
|
305
|
+
algorithm_id = V8CryptoKeyAlgorithm(self._read_varint32(stream))
|
|
306
|
+
asymmetric_key_type = V8AsymmetricCryptoKeyType(stream.read(1)[0])
|
|
307
|
+
length_bytes = self._read_varint32(stream)
|
|
308
|
+
public_exponent_length = self._read_varint32(stream)
|
|
309
|
+
public_exponent = stream.read(public_exponent_length)
|
|
310
|
+
if len(public_exponent) != public_exponent_length:
|
|
311
|
+
raise ValueError(f"Could not read all of public exponent data")
|
|
312
|
+
hash_id = V8CryptoKeyAlgorithm(self._read_varint32(stream))
|
|
313
|
+
params = {
|
|
314
|
+
"algorithm_type": algorithm_id,
|
|
315
|
+
"asymmetric_key_type": asymmetric_key_type,
|
|
316
|
+
"byte_length": length_bytes,
|
|
317
|
+
"public_exponent": public_exponent,
|
|
318
|
+
"hash_type": hash_id,
|
|
319
|
+
"named_curve_type": None
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
elif sub_type == V8CryptoKeySubType.EcKey:
|
|
323
|
+
algorithm_id = V8CryptoKeyAlgorithm(self._read_varint32(stream))
|
|
324
|
+
asymmetric_key_type = V8AsymmetricCryptoKeyType(stream.read(1)[0])
|
|
325
|
+
named_curve = V8CryptoNamedCurve(self._read_varint32(stream))
|
|
326
|
+
params = {
|
|
327
|
+
"algorithm_type": algorithm_id,
|
|
328
|
+
"asymmetric_key_type": asymmetric_key_type,
|
|
329
|
+
"named_curve_type": named_curve,
|
|
330
|
+
"hash_type": None,
|
|
331
|
+
"byte_length": None,
|
|
332
|
+
"public_exponent": None
|
|
333
|
+
}
|
|
334
|
+
elif sub_type == V8CryptoKeySubType.NoParamsKey:
|
|
335
|
+
algorithm_id = V8CryptoKeyAlgorithm(self._read_varint32(stream))
|
|
336
|
+
params = {
|
|
337
|
+
"algorithm_type": algorithm_id,
|
|
338
|
+
"hash_type": None,
|
|
339
|
+
"asymmetric_key_type": None,
|
|
340
|
+
"byte_length": None,
|
|
341
|
+
"named_curve_type": None,
|
|
342
|
+
"public_exponent": None
|
|
343
|
+
}
|
|
344
|
+
else:
|
|
345
|
+
raise ValueError(f"Unknown V8CryptoKeySubType {sub_type}")
|
|
346
|
+
|
|
347
|
+
params["key_usage"] = V8CryptoKeyUsage(self._read_varint32(stream))
|
|
348
|
+
key_length = self._read_varint32(stream)
|
|
349
|
+
key_data = stream.read(key_length)
|
|
350
|
+
if len(key_data) < key_length:
|
|
351
|
+
raise ValueError("Could not read all key data")
|
|
352
|
+
|
|
353
|
+
params["key_data"] = key_data
|
|
354
|
+
return CryptoKey(sub_type, **params)
|
|
355
|
+
|
|
356
|
+
def _not_implemented(self, stream):
|
|
357
|
+
raise NotImplementedError()
|
|
358
|
+
|
|
359
|
+
def read(self, stream: typing.BinaryIO) -> typing.Any:
|
|
360
|
+
tag = stream.read(1)
|
|
361
|
+
|
|
362
|
+
func = {
|
|
363
|
+
Constants.tag_kMessagePortTag: lambda x: self._not_implemented(x),
|
|
364
|
+
Constants.tag_kMojoHandleTag: lambda x: self._not_implemented(x),
|
|
365
|
+
Constants.tag_kBlobTag: lambda x: self._not_implemented(x),
|
|
366
|
+
Constants.tag_kBlobIndexTag: lambda x: self._read_blob_index(x),
|
|
367
|
+
Constants.tag_kFileTag: lambda x: self._not_implemented(x),
|
|
368
|
+
Constants.tag_kFileIndexTag: lambda x: self._read_file_index(x),
|
|
369
|
+
Constants.tag_kDOMFileSystemTag: lambda x: self._not_implemented(x),
|
|
370
|
+
Constants.tag_kNativeFileSystemFileHandleTag: lambda x: self._read_native_file_handle(False, x),
|
|
371
|
+
Constants.tag_kNativeFileSystemDirectoryHandleTag: lambda x: self._read_native_file_handle(True, x),
|
|
372
|
+
Constants.tag_kFileListTag: lambda x: self._not_implemented(x),
|
|
373
|
+
Constants.tag_kFileListIndexTag: lambda x: self._read_file_list_index(x),
|
|
374
|
+
Constants.tag_kImageDataTag: lambda x: self._not_implemented(x),
|
|
375
|
+
Constants.tag_kImageBitmapTag: lambda x: self._not_implemented(x),
|
|
376
|
+
Constants.tag_kImageBitmapTransferTag: lambda x: self._not_implemented(x),
|
|
377
|
+
Constants.tag_kOffscreenCanvasTransferTag: lambda x: self._not_implemented(x),
|
|
378
|
+
Constants.tag_kReadableStreamTransferTag: lambda x: self._not_implemented(x),
|
|
379
|
+
Constants.tag_kTransformStreamTransferTag: lambda x: self._not_implemented(x),
|
|
380
|
+
Constants.tag_kWritableStreamTransferTag: lambda x: self._not_implemented(x),
|
|
381
|
+
Constants.tag_kDOMPointTag: lambda x: self._not_implemented(x),
|
|
382
|
+
Constants.tag_kDOMPointReadOnlyTag: lambda x: self._not_implemented(x),
|
|
383
|
+
Constants.tag_kDOMRectTag: lambda x: self._not_implemented(x),
|
|
384
|
+
Constants.tag_kDOMRectReadOnlyTag: lambda x: self._not_implemented(x),
|
|
385
|
+
Constants.tag_kDOMQuadTag: lambda x: self._not_implemented(x),
|
|
386
|
+
Constants.tag_kDOMMatrixTag: lambda x: self._not_implemented(x),
|
|
387
|
+
Constants.tag_kDOMMatrixReadOnlyTag: lambda x: self._not_implemented(x),
|
|
388
|
+
Constants.tag_kDOMMatrix2DTag: lambda x: self._not_implemented(x),
|
|
389
|
+
Constants.tag_kDOMMatrix2DReadOnlyTag: lambda x: self._not_implemented(x),
|
|
390
|
+
Constants.tag_kCryptoKeyTag: lambda x: self._read_crypto_key(x),
|
|
391
|
+
Constants.tag_kRTCCertificateTag: lambda x: self._not_implemented(x),
|
|
392
|
+
Constants.tag_kRTCEncodedAudioFrameTag: lambda x: self._not_implemented(x),
|
|
393
|
+
Constants.tag_kRTCEncodedVideoFrameTag: lambda x: self._not_implemented(x),
|
|
394
|
+
Constants.tag_kVideoFrameTag: lambda x: self._not_implemented(x),
|
|
395
|
+
Constants.tag_kDOMExceptionTag: lambda x: self._not_implemented(x)
|
|
396
|
+
}.get(tag)
|
|
397
|
+
|
|
398
|
+
if func is None:
|
|
399
|
+
raise ValueError(f"Unknown tag: {tag}")
|
|
400
|
+
|
|
401
|
+
return func(stream)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2022, CCL Forensics
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
5
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
6
|
+
the Software without restriction, including without limitation the rights to
|
|
7
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
8
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
9
|
+
so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import io
|
|
24
|
+
import datetime
|
|
25
|
+
import struct
|
|
26
|
+
import os
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
__version__ = "0.1"
|
|
30
|
+
__description__ = "Module for reading Chromium Pickles."
|
|
31
|
+
__contact__ = "Alex Caithness"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class EasyPickleException(Exception):
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class EasyPickleIterator:
|
|
39
|
+
"""
|
|
40
|
+
A pythonic implementation of the PickleIterator object used in various places in Chrom(e|ium).
|
|
41
|
+
"""
|
|
42
|
+
def __init__(self, data: bytes, alignment: int=4):
|
|
43
|
+
"""
|
|
44
|
+
Takes a bytes buffer and wraps the EasyPickleIterator around it
|
|
45
|
+
:param data: the data to be wrapped
|
|
46
|
+
:param alignment: (optional) the number of bytes to align reads to (default: 4)
|
|
47
|
+
"""
|
|
48
|
+
self._f = io.BytesIO(data)
|
|
49
|
+
self._alignment = alignment
|
|
50
|
+
|
|
51
|
+
self._pickle_length = self.read_uint32()
|
|
52
|
+
if len(data) != self._pickle_length + 4:
|
|
53
|
+
raise EasyPickleException("pickle length invalid")
|
|
54
|
+
|
|
55
|
+
def __enter__(self) -> "EasyPickleIterator":
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
59
|
+
self.close()
|
|
60
|
+
|
|
61
|
+
def close(self):
|
|
62
|
+
self._f.close()
|
|
63
|
+
|
|
64
|
+
def read_aligned(self, length: int) -> bytes:
|
|
65
|
+
"""
|
|
66
|
+
reads the number of bytes specified by the length parameter. Aligns the buffer afterwards if required.
|
|
67
|
+
:param length: the length od data to be read
|
|
68
|
+
:return: the data read (without the alignment padding)
|
|
69
|
+
"""
|
|
70
|
+
raw = self._f.read(length)
|
|
71
|
+
if len(raw) != length:
|
|
72
|
+
raise EasyPickleException(f"Tried to read {length} bytes but only got {len(raw)}")
|
|
73
|
+
|
|
74
|
+
align_count = self._alignment - (length % self._alignment)
|
|
75
|
+
if align_count != self._alignment:
|
|
76
|
+
self._f.seek(align_count, os.SEEK_CUR)
|
|
77
|
+
|
|
78
|
+
return raw
|
|
79
|
+
|
|
80
|
+
def read_uint16(self) -> int:
|
|
81
|
+
raw = self.read_aligned(2)
|
|
82
|
+
return struct.unpack("<H", raw)[0]
|
|
83
|
+
|
|
84
|
+
def read_uint32(self) -> int:
|
|
85
|
+
raw = self.read_aligned(4)
|
|
86
|
+
return struct.unpack("<I", raw)[0]
|
|
87
|
+
|
|
88
|
+
def read_uint64(self) -> int:
|
|
89
|
+
raw = self.read_aligned(8)
|
|
90
|
+
return struct.unpack("<Q", raw)[0]
|
|
91
|
+
|
|
92
|
+
def read_int16(self) -> int:
|
|
93
|
+
raw = self.read_aligned(2)
|
|
94
|
+
return struct.unpack("<h", raw)[0]
|
|
95
|
+
|
|
96
|
+
def read_int32(self) -> int:
|
|
97
|
+
raw = self.read_aligned(4)
|
|
98
|
+
return struct.unpack("<i", raw)[0]
|
|
99
|
+
|
|
100
|
+
def read_int64(self) -> int:
|
|
101
|
+
raw = self.read_aligned(8)
|
|
102
|
+
return struct.unpack("<q", raw)[0]
|
|
103
|
+
|
|
104
|
+
def read_bool(self) -> bool:
|
|
105
|
+
raw = self.read_int32()
|
|
106
|
+
if raw == 0:
|
|
107
|
+
return False
|
|
108
|
+
elif raw == 1:
|
|
109
|
+
return True
|
|
110
|
+
else:
|
|
111
|
+
raise EasyPickleException("bools should only contain 0 or 1")
|
|
112
|
+
|
|
113
|
+
def read_single(self) -> float:
|
|
114
|
+
raw = self.read_aligned(4)
|
|
115
|
+
return struct.unpack("<f", raw)[0]
|
|
116
|
+
|
|
117
|
+
def read_double(self) -> float:
|
|
118
|
+
raw = self.read_aligned(8)
|
|
119
|
+
return struct.unpack("<d", raw)[0]
|
|
120
|
+
|
|
121
|
+
def read_string(self) -> str:
|
|
122
|
+
length = self.read_uint32()
|
|
123
|
+
raw = self.read_aligned(length)
|
|
124
|
+
return raw.decode("utf-8")
|
|
125
|
+
|
|
126
|
+
def read_string16(self) -> str:
|
|
127
|
+
length = self.read_uint32() * 2 # character count
|
|
128
|
+
raw = self.read_aligned(length)
|
|
129
|
+
return raw.decode("utf-16-le")
|
|
130
|
+
|
|
131
|
+
def read_datetime(self) -> datetime.datetime:
|
|
132
|
+
return datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=self.read_uint64())
|
|
133
|
+
|