dissect.target 3.16.dev22__py3-none-any.whl → 3.16.dev23__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- dissect/target/helpers/protobuf.py +60 -0
- dissect/target/plugins/apps/container/docker.py +288 -54
- dissect/target/target.py +3 -3
- {dissect.target-3.16.dev22.dist-info → dissect.target-3.16.dev23.dist-info}/METADATA +1 -1
- {dissect.target-3.16.dev22.dist-info → dissect.target-3.16.dev23.dist-info}/RECORD +10 -9
- {dissect.target-3.16.dev22.dist-info → dissect.target-3.16.dev23.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.16.dev22.dist-info → dissect.target-3.16.dev23.dist-info}/LICENSE +0 -0
- {dissect.target-3.16.dev22.dist-info → dissect.target-3.16.dev23.dist-info}/WHEEL +0 -0
- {dissect.target-3.16.dev22.dist-info → dissect.target-3.16.dev23.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.16.dev22.dist-info → dissect.target-3.16.dev23.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Any, BinaryIO
|
4
|
+
|
5
|
+
from dissect.cstruct.types.base import BaseType
|
6
|
+
from dissect.cstruct.types.bytesinteger import BytesInteger
|
7
|
+
|
8
|
+
|
9
|
+
class ProtobufVarint(BytesInteger):
|
10
|
+
"""Implements a protobuf integer type for dissect.cstruct that can span a variable amount of bytes.
|
11
|
+
|
12
|
+
Mainly follows the cstruct BytesInteger implementation with minor tweaks
|
13
|
+
to support protobuf's msb varint implementation.
|
14
|
+
|
15
|
+
Resources:
|
16
|
+
- https://protobuf.dev/programming-guides/encoding/
|
17
|
+
- https://github.com/protocolbuffers/protobuf/blob/main/python/google/protobuf/internal/decoder.py
|
18
|
+
"""
|
19
|
+
|
20
|
+
def _read(self, stream: BinaryIO, context: dict[str, Any] = None) -> int:
|
21
|
+
return decode_varint(stream)
|
22
|
+
|
23
|
+
def _write(self, stream: BinaryIO, data: int) -> int:
|
24
|
+
return stream.write(encode_varint(data))
|
25
|
+
|
26
|
+
_read_array = BaseType._read_array
|
27
|
+
|
28
|
+
_write_array = BaseType._write_array
|
29
|
+
|
30
|
+
|
31
|
+
def decode_varint(stream: BinaryIO) -> int:
|
32
|
+
"""Reads a varint from the provided buffer stream.
|
33
|
+
|
34
|
+
If we have not reached the end of a varint, the msb will be 1.
|
35
|
+
We read every byte from our current position until the msb is 0.
|
36
|
+
"""
|
37
|
+
result = 0
|
38
|
+
i = 0
|
39
|
+
while True:
|
40
|
+
byte = stream.read(1)
|
41
|
+
result |= (byte[0] & 0x7F) << (i * 7)
|
42
|
+
i += 1
|
43
|
+
if byte[0] & 0x80 == 0:
|
44
|
+
break
|
45
|
+
|
46
|
+
return result
|
47
|
+
|
48
|
+
|
49
|
+
def encode_varint(number: int) -> bytes:
|
50
|
+
"""Encode a decoded protobuf varint to its original bytes."""
|
51
|
+
buf = []
|
52
|
+
while True:
|
53
|
+
towrite = number & 0x7F
|
54
|
+
number >>= 7
|
55
|
+
if number:
|
56
|
+
buf.append(towrite | 0x80)
|
57
|
+
else:
|
58
|
+
buf.append(towrite)
|
59
|
+
break
|
60
|
+
return bytes(buf)
|
@@ -1,10 +1,22 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import json
|
4
|
+
import logging
|
2
5
|
import re
|
3
|
-
from
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import Iterator, Optional
|
8
|
+
|
9
|
+
from dissect.cstruct import cstruct
|
10
|
+
from dissect.util import ts
|
4
11
|
|
5
12
|
from dissect.target.exceptions import UnsupportedPluginError
|
13
|
+
from dissect.target.helpers.fsutil import open_decompress
|
14
|
+
from dissect.target.helpers.protobuf import ProtobufVarint
|
6
15
|
from dissect.target.helpers.record import TargetRecordDescriptor
|
7
|
-
from dissect.target.plugin import Plugin, export
|
16
|
+
from dissect.target.plugin import Plugin, arg, export
|
17
|
+
from dissect.target.target import Target
|
18
|
+
|
19
|
+
log = logging.getLogger(__name__)
|
8
20
|
|
9
21
|
DockerContainerRecord = TargetRecordDescriptor(
|
10
22
|
"apps/containers/docker/container",
|
@@ -35,11 +47,62 @@ DockerImageRecord = TargetRecordDescriptor(
|
|
35
47
|
],
|
36
48
|
)
|
37
49
|
|
38
|
-
|
50
|
+
DockerLogRecord = TargetRecordDescriptor(
|
51
|
+
"apps/containers/docker/log",
|
52
|
+
[
|
53
|
+
("datetime", "ts"),
|
54
|
+
("string", "container"),
|
55
|
+
("string", "stream"),
|
56
|
+
("string", "message"),
|
57
|
+
],
|
58
|
+
)
|
59
|
+
|
60
|
+
# Resources:
|
61
|
+
# - https://github.com/moby/moby/pull/37092
|
62
|
+
# - https://github.com/cpuguy83/docker/blob/master/daemon/logger/local/doc.go
|
63
|
+
# - https://github.com/moby/moby/blob/master/api/types/plugins/logdriver/entry.proto
|
64
|
+
local_def = """
|
65
|
+
struct entry {
|
66
|
+
uint32 header;
|
67
|
+
|
68
|
+
// source
|
69
|
+
uint8 s_type; // 0x0a
|
70
|
+
varint s_len; // 0x06
|
71
|
+
char source[s_len]; // stdout or stderr
|
72
|
+
|
73
|
+
// timestamp
|
74
|
+
uint8 t_type; // 0x10
|
75
|
+
varint ts; // timestamp in ums
|
76
|
+
|
77
|
+
// message
|
78
|
+
uint8 m_type; // 0x1a
|
79
|
+
varint m_len; // message length
|
80
|
+
char message[m_len];
|
81
|
+
|
82
|
+
// partial_log_metadata not implemented
|
83
|
+
|
84
|
+
uint32 footer;
|
85
|
+
};
|
86
|
+
"""
|
87
|
+
|
88
|
+
c_local = cstruct(endian=">")
|
89
|
+
c_local.addtype("varint", ProtobufVarint(c_local, "varint", size=None, signed=False, alignment=1))
|
90
|
+
c_local.load(local_def, compiled=False)
|
91
|
+
|
92
|
+
RE_DOCKER_NS = re.compile(r"\.(?P<nanoseconds>\d{7,})(?P<postfix>Z|\+\d{2}:\d{2})")
|
93
|
+
RE_ANSI_ESCAPE = re.compile(r"\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
|
94
|
+
|
95
|
+
ASCII_MAP = {
|
96
|
+
"\x08": "[BS]",
|
97
|
+
"\x09": "[TAB]",
|
98
|
+
"\x0A": "", # \n
|
99
|
+
"\x0D": "", # \r
|
100
|
+
}
|
39
101
|
|
40
102
|
|
41
103
|
class DockerPlugin(Plugin):
|
42
|
-
"""
|
104
|
+
"""Parse Docker Daemon artefacts.
|
105
|
+
|
43
106
|
References:
|
44
107
|
- https://didactic-security.com/resources/docker-forensics.pdf
|
45
108
|
- https://didactic-security.com/resources/docker-forensics-cheatsheet.pdf
|
@@ -48,84 +111,218 @@ class DockerPlugin(Plugin):
|
|
48
111
|
|
49
112
|
__namespace__ = "docker"
|
50
113
|
|
51
|
-
|
114
|
+
def __init__(self, target: Target):
|
115
|
+
super().__init__(target)
|
116
|
+
self.installs = set(find_installs(target))
|
52
117
|
|
53
118
|
def check_compatible(self) -> None:
|
54
|
-
if not self.
|
55
|
-
raise UnsupportedPluginError("No Docker
|
119
|
+
if not self.installs:
|
120
|
+
raise UnsupportedPluginError("No Docker install(s) found")
|
56
121
|
|
57
122
|
@export(record=DockerImageRecord)
|
58
123
|
def images(self) -> Iterator[DockerImageRecord]:
|
59
124
|
"""Returns any pulled docker images on the target system."""
|
60
125
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
for
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
126
|
+
for data_root in self.installs:
|
127
|
+
images_path = data_root.joinpath("image/overlay2/repositories.json")
|
128
|
+
|
129
|
+
if images_path.exists():
|
130
|
+
repositories = json.loads(images_path.read_text()).get("Repositories")
|
131
|
+
else:
|
132
|
+
self.target.log.debug("No docker images found, file %s does not exist.", images_path)
|
133
|
+
continue
|
134
|
+
|
135
|
+
for name, tags in repositories.items():
|
136
|
+
for tag, hash in tags.items():
|
137
|
+
image_metadata_path = data_root.joinpath(
|
138
|
+
"image/overlay2/imagedb/content/sha256/", hash.split(":")[-1]
|
139
|
+
)
|
140
|
+
created = None
|
141
|
+
|
142
|
+
if image_metadata_path.exists():
|
143
|
+
image_metadata = json.loads(image_metadata_path.read_text())
|
144
|
+
created = convert_timestamp(image_metadata.get("created"))
|
145
|
+
|
146
|
+
yield DockerImageRecord(
|
147
|
+
name=name,
|
148
|
+
tag=tag,
|
149
|
+
image_id=hash_to_image_id(hash),
|
150
|
+
created=created,
|
151
|
+
hash=hash,
|
152
|
+
_target=self.target,
|
153
|
+
)
|
86
154
|
|
87
155
|
@export(record=DockerContainerRecord)
|
88
156
|
def containers(self) -> Iterator[DockerContainerRecord]:
|
89
157
|
"""Returns any docker containers present on the target system."""
|
90
158
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
if config.get("State").get("Running"):
|
159
|
+
for data_root in self.installs:
|
160
|
+
for config_path in data_root.joinpath("containers").glob("**/config.v2.json"):
|
161
|
+
config = json.loads(config_path.read_text())
|
162
|
+
running = config.get("State").get("Running")
|
163
|
+
if running:
|
97
164
|
ports = config.get("NetworkSettings").get("Ports", {})
|
98
165
|
pid = config.get("Pid")
|
99
166
|
else:
|
100
167
|
ports = config.get("Config").get("ExposedPorts", {})
|
101
|
-
pid =
|
102
|
-
|
168
|
+
pid = None
|
103
169
|
volumes = []
|
104
170
|
if mount_points := config.get("MountPoints"):
|
105
171
|
for mp in mount_points:
|
106
172
|
mount_point = mount_points[mp]
|
107
173
|
volumes.append(f"{mount_point.get('Source')}:{mount_point.get('Destination')}")
|
108
|
-
|
109
174
|
yield DockerContainerRecord(
|
110
175
|
container_id=config.get("ID"),
|
111
176
|
image=config.get("Config").get("Image"),
|
112
177
|
command=config.get("Config").get("Cmd"),
|
113
|
-
created=
|
114
|
-
running=
|
178
|
+
created=convert_timestamp(config.get("Created")),
|
179
|
+
running=running,
|
115
180
|
pid=pid,
|
116
|
-
started=
|
117
|
-
finished=
|
118
|
-
ports=
|
181
|
+
started=convert_timestamp(config.get("State").get("StartedAt")),
|
182
|
+
finished=convert_timestamp(config.get("State").get("FinishedAt")),
|
183
|
+
ports=convert_ports(ports),
|
119
184
|
names=config.get("Name").replace("/", "", 1),
|
120
185
|
volumes=volumes,
|
121
|
-
source=
|
186
|
+
source=config_path,
|
122
187
|
_target=self.target,
|
123
188
|
)
|
124
189
|
|
190
|
+
@export(record=DockerLogRecord)
|
191
|
+
@arg(
|
192
|
+
"--raw-messages",
|
193
|
+
action="store_true",
|
194
|
+
help="preserve ANSI escape sequences and trailing newlines from log messages",
|
195
|
+
)
|
196
|
+
@arg(
|
197
|
+
"--remove-backspaces",
|
198
|
+
action="store_true",
|
199
|
+
help="alter messages by removing ASCII backspaces and the corresponding characters",
|
200
|
+
)
|
201
|
+
def logs(self, raw_messages: bool = False, remove_backspaces: bool = False) -> Iterator[DockerLogRecord]:
|
202
|
+
"""Returns log files (stdout/stderr) from Docker containers.
|
203
|
+
|
204
|
+
The default Docker Daemon log driver is ``json-file``, which
|
205
|
+
performs no log rotation. Another log driver is ``local`` and
|
206
|
+
performs log rotation and compresses log files more efficiently.
|
207
|
+
|
208
|
+
Eventually ``local`` will likely replace ``json-file`` as the
|
209
|
+
default log driver.
|
210
|
+
|
211
|
+
Resources:
|
212
|
+
- https://docs.docker.com/config/containers/logging/configure/
|
213
|
+
- https://docs.docker.com/config/containers/logging/json-file/
|
214
|
+
- https://docs.docker.com/config/containers/logging/local/
|
215
|
+
"""
|
216
|
+
|
217
|
+
for data_root in self.installs:
|
218
|
+
containers_path = data_root.joinpath("containers")
|
219
|
+
|
220
|
+
for log_file in containers_path.glob(("**/*.log*")):
|
221
|
+
container = log_file.parent
|
222
|
+
|
223
|
+
# json log driver
|
224
|
+
if "-json.log" in log_file.name:
|
225
|
+
for log_entry in self._parse_json_log(log_file):
|
226
|
+
yield DockerLogRecord(
|
227
|
+
ts=log_entry.get("time"),
|
228
|
+
container=container.name, # container hash
|
229
|
+
stream=log_entry.get("stream"),
|
230
|
+
message=log_entry.get("log")
|
231
|
+
if raw_messages
|
232
|
+
else strip_log(log_entry.get("log"), remove_backspaces),
|
233
|
+
_target=self.target,
|
234
|
+
)
|
235
|
+
|
236
|
+
# local log driver
|
237
|
+
else:
|
238
|
+
for log_entry in self._parse_local_log(log_file):
|
239
|
+
yield DockerLogRecord(
|
240
|
+
ts=ts.from_unix_us(log_entry.ts // 1000),
|
241
|
+
container=container.parent.name, # container hash
|
242
|
+
stream=log_entry.source,
|
243
|
+
message=log_entry.message
|
244
|
+
if raw_messages
|
245
|
+
else strip_log(log_entry.message, remove_backspaces),
|
246
|
+
_target=self.target,
|
247
|
+
)
|
248
|
+
|
249
|
+
def _parse_local_log(self, path: Path) -> Iterator[c_local.entry]:
|
250
|
+
fh = open_decompress(path, "rb") # can be a .gz file
|
251
|
+
|
252
|
+
while True:
|
253
|
+
try:
|
254
|
+
entry = c_local.entry(fh)
|
255
|
+
if entry.header != entry.footer:
|
256
|
+
self.target.log.warning(
|
257
|
+
"Could not reliably parse log entry at offset %i in file %s."
|
258
|
+
"Entry could be parsed incorrectly. Please report this "
|
259
|
+
"issue as Docker's protobuf could have changed.",
|
260
|
+
fh.tell(),
|
261
|
+
path,
|
262
|
+
)
|
263
|
+
yield entry
|
264
|
+
except EOFError:
|
265
|
+
break
|
266
|
+
|
267
|
+
def _parse_json_log(self, path: Path) -> Iterator[dict]:
|
268
|
+
for line in open_decompress(path, "rt"):
|
269
|
+
try:
|
270
|
+
entry = json.loads(line)
|
271
|
+
except json.JSONDecodeError as e:
|
272
|
+
self.target.log.warning("Could not decode JSON line in file %s", path)
|
273
|
+
self.target.log.debug("", exc_info=e)
|
274
|
+
continue
|
275
|
+
yield entry
|
276
|
+
|
277
|
+
|
278
|
+
def get_data_path(path: Path) -> Optional[str]:
|
279
|
+
"""Returns the configured Docker daemon data-root path."""
|
280
|
+
try:
|
281
|
+
config = json.loads(path.open("rt").read())
|
282
|
+
except json.JSONDecodeError as e:
|
283
|
+
log.warning("Could not read JSON file '%s'", path)
|
284
|
+
log.debug(exc_info=e)
|
285
|
+
|
286
|
+
return config.get("data-root")
|
287
|
+
|
288
|
+
|
289
|
+
def find_installs(target: Target) -> Iterator[Path]:
|
290
|
+
"""Attempt to find additional configured and existing Docker daemon data-root folders.
|
125
291
|
|
126
|
-
|
292
|
+
References:
|
293
|
+
- https://docs.docker.com/config/daemon/
|
127
294
|
"""
|
128
|
-
|
295
|
+
|
296
|
+
default_config_paths = [
|
297
|
+
# Linux
|
298
|
+
"/etc/docker/daemon.json",
|
299
|
+
"/var/snap/docker/current/config/daemon.json",
|
300
|
+
# Windows
|
301
|
+
"sysvol/ProgramData/docker/config/daemon.json",
|
302
|
+
]
|
303
|
+
|
304
|
+
user_config_paths = [
|
305
|
+
# Docker Desktop (macOS/Windows/Linux)
|
306
|
+
".docker/daemon.json",
|
307
|
+
]
|
308
|
+
|
309
|
+
if (default_root := target.fs.path("/var/lib/docker")).exists():
|
310
|
+
yield default_root
|
311
|
+
|
312
|
+
for path in default_config_paths:
|
313
|
+
if (config_file := target.fs.path(path)).exists():
|
314
|
+
if (data_root_path := target.fs.path(get_data_path(config_file))).exists():
|
315
|
+
yield data_root_path
|
316
|
+
|
317
|
+
for path in user_config_paths:
|
318
|
+
for user_details in target.user_details.all_with_home():
|
319
|
+
if (config_file := user_details.home_path.joinpath(path)).exists():
|
320
|
+
if (data_root_path := target.fs.path(get_data_path(config_file))).exists():
|
321
|
+
yield data_root_path
|
322
|
+
|
323
|
+
|
324
|
+
def convert_timestamp(timestamp: str) -> str:
|
325
|
+
"""Docker sometimes uses (unpadded) 9 digit nanosecond precision
|
129
326
|
in their timestamp logs, eg. "2022-12-19T13:37:00.123456789Z".
|
130
327
|
|
131
328
|
Python has no native %n nanosecond strptime directive, so we
|
@@ -134,7 +331,7 @@ def _convert_timestamp(timestamp: str) -> str:
|
|
134
331
|
"""
|
135
332
|
|
136
333
|
timestamp_nanoseconds_plus_postfix = timestamp[19:]
|
137
|
-
match =
|
334
|
+
match = RE_DOCKER_NS.match(timestamp_nanoseconds_plus_postfix)
|
138
335
|
|
139
336
|
# Timestamp does not have nanoseconds if there is no match.
|
140
337
|
if not match:
|
@@ -146,9 +343,8 @@ def _convert_timestamp(timestamp: str) -> str:
|
|
146
343
|
return f"{timestamp[:19]}.{microseconds}{match['postfix']}"
|
147
344
|
|
148
345
|
|
149
|
-
def
|
150
|
-
"""
|
151
|
-
Depending on the state of the container (turned on or off) we
|
346
|
+
def convert_ports(ports: dict[str, list | dict]) -> dict:
|
347
|
+
"""Depending on the state of the container (turned on or off) we
|
152
348
|
can salvage forwarded ports for the container in different
|
153
349
|
parts of the config.v2.json file.
|
154
350
|
|
@@ -171,5 +367,43 @@ def _convert_ports(ports: dict) -> dict:
|
|
171
367
|
return fports
|
172
368
|
|
173
369
|
|
174
|
-
def
|
370
|
+
def hash_to_image_id(hash: str) -> str:
|
371
|
+
"""Convert the hash to an abbrevated docker image id."""
|
175
372
|
return hash.split(":")[-1][:12]
|
373
|
+
|
374
|
+
|
375
|
+
def strip_log(input: str | bytes, exc_backspace: bool = False) -> str:
|
376
|
+
"""Remove ANSI escape sequences from a given input string.
|
377
|
+
|
378
|
+
Also translates ASCII codes such as backspaces to readable format.
|
379
|
+
|
380
|
+
Resources:
|
381
|
+
- https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#general-ascii-codes
|
382
|
+
"""
|
383
|
+
|
384
|
+
if isinstance(input, bytes):
|
385
|
+
input = input.decode("utf-8", errors="backslashreplace")
|
386
|
+
|
387
|
+
out = RE_ANSI_ESCAPE.sub("", input)
|
388
|
+
|
389
|
+
if exc_backspace:
|
390
|
+
out = _replace_backspace(out)
|
391
|
+
|
392
|
+
for hex, name in ASCII_MAP.items():
|
393
|
+
out = out.replace(hex, name)
|
394
|
+
|
395
|
+
return out
|
396
|
+
|
397
|
+
|
398
|
+
def _replace_backspace(input: str) -> str:
|
399
|
+
"""Remove ANSI backspace characters (``\x08``) and 'replay' their effect on the rest of the string.
|
400
|
+
|
401
|
+
For example, with the input ``123\x084``, the output would be ``124``.
|
402
|
+
"""
|
403
|
+
out = ""
|
404
|
+
for char in input:
|
405
|
+
if char == "\x08":
|
406
|
+
out = out[:-1]
|
407
|
+
else:
|
408
|
+
out += char
|
409
|
+
return out
|
dissect/target/target.py
CHANGED
@@ -87,8 +87,8 @@ class Target:
|
|
87
87
|
|
88
88
|
try:
|
89
89
|
self._config = config.load(self.path)
|
90
|
-
except Exception:
|
91
|
-
self.log.
|
90
|
+
except Exception as e:
|
91
|
+
self.log.debug("Error loading config file", exc_info=e)
|
92
92
|
self._config = config.load(None) # This loads an empty config.
|
93
93
|
|
94
94
|
# Fill the disks and/or volumes and/or filesystems and apply() will
|
@@ -372,7 +372,7 @@ class Target:
|
|
372
372
|
recursive: Whether to check the child ``Target`` for more ``Targets``.
|
373
373
|
|
374
374
|
Returns:
|
375
|
-
An
|
375
|
+
An iterator of ``Targets``.
|
376
376
|
"""
|
377
377
|
for child in self.list_children():
|
378
378
|
try:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: dissect.target
|
3
|
-
Version: 3.16.
|
3
|
+
Version: 3.16.dev23
|
4
4
|
Summary: This module ties all other Dissect modules together, it provides a programming API and command line tools which allow easy access to various data sources inside disk images or file collections (a.k.a. targets)
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
6
6
|
License: Affero General Public License v3
|
@@ -5,7 +5,7 @@ dissect/target/filesystem.py,sha256=aLkvZMgeah39Nhlscawh77cm2mzFYI9J5h3uT3Rigtc,
|
|
5
5
|
dissect/target/loader.py,sha256=0-LcZNi7S0qsXR7XGtrzxpuCh9BsLcqNR1T15O7SnBM,7257
|
6
6
|
dissect/target/plugin.py,sha256=ndqz4RpbBCN6wagCBvfHzHkL0l0-gnbHjc7c8Blite4,48473
|
7
7
|
dissect/target/report.py,sha256=06uiP4MbNI8cWMVrC1SasNS-Yg6ptjVjckwj8Yhe0Js,7958
|
8
|
-
dissect/target/target.py,sha256=
|
8
|
+
dissect/target/target.py,sha256=xNJdecZSt2oHcZwf775kOSTFRA-c_hKoScXaDuK-8FI,32155
|
9
9
|
dissect/target/volume.py,sha256=aQZAJiny8jjwkc9UtwIRwy7nINXjCxwpO-_UDfh6-BA,15801
|
10
10
|
dissect/target/containers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
11
|
dissect/target/containers/asdf.py,sha256=DJp0QEFwUjy2MFwKYcYqIR_BS1fQT1Yi9Kcmqt0aChM,1366
|
@@ -56,6 +56,7 @@ dissect/target/helpers/mount.py,sha256=JxhUYyEbDnHfzPpfuWy4nV9OwCJPoDSGdHHNiyvd_
|
|
56
56
|
dissect/target/helpers/mui.py,sha256=i-7XoHbu4WO2fYapK9yGAMW04rFlgRispknc1KQIS5Q,22258
|
57
57
|
dissect/target/helpers/network_managers.py,sha256=tjqkVWn7i3PpBPkYnKUU0XxhqTTJlIjOc7Y2jpzdzA4,24525
|
58
58
|
dissect/target/helpers/polypath.py,sha256=h8p7m_OCNiQljGwoZh5Aflr9H2ot6CZr6WKq1OSw58o,2175
|
59
|
+
dissect/target/helpers/protobuf.py,sha256=NwKrZD4q9v7J8GnZX9gbzMUMV5pR78eAV17jgWOz_EY,1730
|
59
60
|
dissect/target/helpers/record.py,sha256=lWl7k2Mp9Axllm0tXzPGJx2zj2zONsyY_p5g424T0Lc,4826
|
60
61
|
dissect/target/helpers/record_modifier.py,sha256=BiZ_gtqVxuByLWrga1lfglk3X-TcMrJC0quxPpXoIRo,3138
|
61
62
|
dissect/target/helpers/regutil.py,sha256=kX-sSZbW8Qkg29Dn_9zYbaQrwLumrr4Y8zJ1EhHXIAM,27337
|
@@ -120,7 +121,7 @@ dissect/target/plugins/apps/browser/edge.py,sha256=cjMbAGtlTVyJLuha3D0uNbai0mJnk
|
|
120
121
|
dissect/target/plugins/apps/browser/firefox.py,sha256=6dUTNfclNTsqB_GA-4q38tyHPuiw8lgNEmmtfIWbMUY,11373
|
121
122
|
dissect/target/plugins/apps/browser/iexplore.py,sha256=LUXXCjMBBFcFN2ceBpks8qM1PyOvrBPn1guA4WM4oSU,8706
|
122
123
|
dissect/target/plugins/apps/container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
123
|
-
dissect/target/plugins/apps/container/docker.py,sha256=
|
124
|
+
dissect/target/plugins/apps/container/docker.py,sha256=0HWheazdh9arri0hFZgEUximHO_IaF_Dg_kJ7sq59Jw,14487
|
124
125
|
dissect/target/plugins/apps/remoteaccess/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
125
126
|
dissect/target/plugins/apps/remoteaccess/anydesk.py,sha256=lHtgINWXfVpPuCTRyQmT2ZO-1vkoqiXZ7coj8cZ8p4c,3185
|
126
127
|
dissect/target/plugins/apps/remoteaccess/remoteaccess.py,sha256=UQDmDC4Y-KxYl_8kaAh6SG_BLJZ6SeGnxG0gyD8tzaE,833
|
@@ -324,10 +325,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
|
|
324
325
|
dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
|
325
326
|
dissect/target/volumes/md.py,sha256=j1K1iKmspl0C_OJFc7-Q1BMWN2OCC5EVANIgVlJ_fIE,1673
|
326
327
|
dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
|
327
|
-
dissect.target-3.16.
|
328
|
-
dissect.target-3.16.
|
329
|
-
dissect.target-3.16.
|
330
|
-
dissect.target-3.16.
|
331
|
-
dissect.target-3.16.
|
332
|
-
dissect.target-3.16.
|
333
|
-
dissect.target-3.16.
|
328
|
+
dissect.target-3.16.dev23.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
|
329
|
+
dissect.target-3.16.dev23.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
330
|
+
dissect.target-3.16.dev23.dist-info/METADATA,sha256=0sRgs6_clcf3PUsMUj5HEJkVmN398nl1HOMUtvwTe48,11113
|
331
|
+
dissect.target-3.16.dev23.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
332
|
+
dissect.target-3.16.dev23.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
|
333
|
+
dissect.target-3.16.dev23.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
|
334
|
+
dissect.target-3.16.dev23.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
{dissect.target-3.16.dev22.dist-info → dissect.target-3.16.dev23.dist-info}/entry_points.txt
RENAMED
File without changes
|
File without changes
|