dissect.target 3.18.dev4__py3-none-any.whl → 3.18.dev6__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.
- dissect/target/helpers/cache.py +3 -1
- dissect/target/loaders/mqtt.py +147 -2
- {dissect.target-3.18.dev4.dist-info → dissect.target-3.18.dev6.dist-info}/METADATA +1 -1
- {dissect.target-3.18.dev4.dist-info → dissect.target-3.18.dev6.dist-info}/RECORD +9 -9
- {dissect.target-3.18.dev4.dist-info → dissect.target-3.18.dev6.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.18.dev4.dist-info → dissect.target-3.18.dev6.dist-info}/LICENSE +0 -0
- {dissect.target-3.18.dev4.dist-info → dissect.target-3.18.dev6.dist-info}/WHEEL +0 -0
- {dissect.target-3.18.dev4.dist-info → dissect.target-3.18.dev6.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.18.dev4.dist-info → dissect.target-3.18.dev6.dist-info}/top_level.txt +0 -0
dissect/target/helpers/cache.py
CHANGED
@@ -147,7 +147,9 @@ class Cache:
|
|
147
147
|
if os.access(cache_file, os.R_OK, effective_ids=bool(os.supports_effective_ids)):
|
148
148
|
if os.stat(cache_file).st_size != 0:
|
149
149
|
try:
|
150
|
-
|
150
|
+
reader = self.open_reader(cache_file, output)
|
151
|
+
target.log.info("Using cache for function: %s", self.fname)
|
152
|
+
return reader
|
151
153
|
except Exception as e:
|
152
154
|
target.log.warning("Cache will NOT be used. Error opening cache file: %s", cache_file)
|
153
155
|
target.log.debug("", exc_info=e)
|
dissect/target/loaders/mqtt.py
CHANGED
@@ -1,13 +1,18 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import atexit
|
3
4
|
import logging
|
5
|
+
import math
|
6
|
+
import os
|
4
7
|
import ssl
|
8
|
+
import sys
|
5
9
|
import time
|
6
10
|
import urllib
|
7
11
|
from dataclasses import dataclass
|
8
12
|
from functools import lru_cache
|
9
13
|
from pathlib import Path
|
10
14
|
from struct import pack, unpack_from
|
15
|
+
from threading import Thread
|
11
16
|
from typing import Any, Callable, Iterator, Optional, Union
|
12
17
|
|
13
18
|
import paho.mqtt.client as mqtt
|
@@ -51,6 +56,34 @@ class SeekMessage:
|
|
51
56
|
data: bytes = b""
|
52
57
|
|
53
58
|
|
59
|
+
class MQTTTransferRatePerSecond:
|
60
|
+
def __init__(self, window_size: int = 10):
|
61
|
+
self.window_size = window_size
|
62
|
+
self.timestamps = []
|
63
|
+
self.bytes = []
|
64
|
+
|
65
|
+
def record(self, timestamp: float, byte_count: int) -> MQTTTransferRatePerSecond:
|
66
|
+
while self.timestamps and (timestamp - self.timestamps[0] > self.window_size):
|
67
|
+
self.timestamps.pop(0)
|
68
|
+
self.bytes.pop(0)
|
69
|
+
|
70
|
+
self.timestamps.append(timestamp)
|
71
|
+
self.bytes.append(byte_count)
|
72
|
+
return self
|
73
|
+
|
74
|
+
def value(self, current_time: float) -> float:
|
75
|
+
if not self.timestamps:
|
76
|
+
return 0
|
77
|
+
|
78
|
+
elapsed_time = current_time - self.timestamps[0]
|
79
|
+
if elapsed_time == 0:
|
80
|
+
return 0
|
81
|
+
|
82
|
+
total_bytes = self.bytes[-1] - self.bytes[0]
|
83
|
+
|
84
|
+
return total_bytes / elapsed_time
|
85
|
+
|
86
|
+
|
54
87
|
class MQTTStream(AlignedStream):
|
55
88
|
def __init__(self, stream: MQTTConnection, disk_id: int, size: Optional[int] = None):
|
56
89
|
self.stream = stream
|
@@ -62,12 +95,108 @@ class MQTTStream(AlignedStream):
|
|
62
95
|
return data
|
63
96
|
|
64
97
|
|
98
|
+
class MQTTDiagnosticLine:
|
99
|
+
def __init__(self, connection: MQTTConnection, total_peers: int):
|
100
|
+
self.connection = connection
|
101
|
+
self.total_peers = total_peers
|
102
|
+
self._columns, self._rows = os.get_terminal_size(0)
|
103
|
+
atexit.register(self._detach)
|
104
|
+
self._attach()
|
105
|
+
|
106
|
+
def _attach(self) -> None:
|
107
|
+
# save cursor position
|
108
|
+
sys.stderr.write("\0337")
|
109
|
+
# set top and bottom margins of the scrolling region to default
|
110
|
+
sys.stderr.write("\033[r")
|
111
|
+
# restore cursor position
|
112
|
+
sys.stderr.write("\0338")
|
113
|
+
# move cursor down one line in the same column; if at the bottom, the screen scrolls up
|
114
|
+
sys.stderr.write("\033D")
|
115
|
+
# move cursor up one line in the same column; if at the top, screen scrolls down
|
116
|
+
sys.stderr.write("\033M")
|
117
|
+
# save cursor position again
|
118
|
+
sys.stderr.write("\0337")
|
119
|
+
# restrict scrolling to a region from the first line to one before the last line
|
120
|
+
sys.stderr.write(f"\033[1;{self._rows - 1}r")
|
121
|
+
# restore cursor position after setting scrolling region
|
122
|
+
sys.stderr.write("\0338")
|
123
|
+
|
124
|
+
def _detach(self) -> None:
|
125
|
+
# save cursor position
|
126
|
+
sys.stderr.write("\0337")
|
127
|
+
# move cursor to the specified position (last line, first column)
|
128
|
+
sys.stderr.write(f"\033[{self._rows};1H")
|
129
|
+
# clear from cursor to end of the line
|
130
|
+
sys.stderr.write("\033[K")
|
131
|
+
# reset scrolling region to include the entire display
|
132
|
+
sys.stderr.write("\033[r")
|
133
|
+
# restore cursor position
|
134
|
+
sys.stderr.write("\0338")
|
135
|
+
# ensure the written content is displayed (flush output)
|
136
|
+
sys.stderr.flush()
|
137
|
+
|
138
|
+
def display(self) -> None:
|
139
|
+
# prepare: set background color to blue and text color to white at the beginning of the line
|
140
|
+
prefix = "\x1b[44m\x1b[37m\r"
|
141
|
+
# reset all attributes (colors, styles) to their defaults afterwards
|
142
|
+
suffix = "\x1b[0m"
|
143
|
+
# separator to set background color to red and text style to bold
|
144
|
+
separator = "\x1b[41m\x1b[1m"
|
145
|
+
logo = "TARGETD"
|
146
|
+
|
147
|
+
start = time.time()
|
148
|
+
transfer_rate = MQTTTransferRatePerSecond(window_size=7)
|
149
|
+
|
150
|
+
while True:
|
151
|
+
time.sleep(0.05)
|
152
|
+
peers = "?"
|
153
|
+
try:
|
154
|
+
peers = len(self.connection.broker.peers(self.connection.host))
|
155
|
+
except Exception:
|
156
|
+
pass
|
157
|
+
|
158
|
+
recv = self.connection.broker.bytes_received
|
159
|
+
now = time.time()
|
160
|
+
transfer = transfer_rate.record(now, recv).value(now) / 1000 # convert to KB/s
|
161
|
+
failures = self.connection.retries
|
162
|
+
seconds_elapsed = round(now - start) % 60
|
163
|
+
minutes_elapsed = math.floor((now - start) / 60) % 60
|
164
|
+
hours_elapsed = math.floor((now - start) / 60**2)
|
165
|
+
timer = f"{hours_elapsed:02d}:{minutes_elapsed:02d}:{seconds_elapsed:02d}"
|
166
|
+
display = f"{timer} {peers}/{self.total_peers} peers {transfer:>8.2f} KB p/s {failures:>4} failures"
|
167
|
+
rest = self._columns - len(display)
|
168
|
+
padding = (rest - len(logo)) * " "
|
169
|
+
|
170
|
+
# save cursor position
|
171
|
+
sys.stderr.write("\0337")
|
172
|
+
# move cursor to specified position (last line, first column)
|
173
|
+
sys.stderr.write(f"\033[{self._rows};1H")
|
174
|
+
# disable line wrapping
|
175
|
+
sys.stderr.write("\033[?7l")
|
176
|
+
# reset all attributes
|
177
|
+
sys.stderr.write("\033[0m")
|
178
|
+
# write the display line with prefix, calculated display content, padding, separator, and logo
|
179
|
+
sys.stderr.write(prefix + display + padding + separator + logo + suffix)
|
180
|
+
# enable line wrapping again
|
181
|
+
sys.stderr.write("\033[?7h")
|
182
|
+
# restore cursor position
|
183
|
+
sys.stderr.write("\0338")
|
184
|
+
# flush output to ensure it is displayed
|
185
|
+
sys.stderr.flush()
|
186
|
+
|
187
|
+
def start(self) -> None:
|
188
|
+
t = Thread(target=self.display)
|
189
|
+
t.daemon = True
|
190
|
+
t.start()
|
191
|
+
|
192
|
+
|
65
193
|
class MQTTConnection:
|
66
194
|
broker = None
|
67
195
|
host = None
|
68
196
|
prev = -1
|
69
197
|
factor = 1
|
70
198
|
prefetch_factor_inc = 10
|
199
|
+
retries = 0
|
71
200
|
|
72
201
|
def __init__(self, broker: Broker, host: str):
|
73
202
|
self.broker = broker
|
@@ -125,6 +254,7 @@ class MQTTConnection:
|
|
125
254
|
# message might have not reached agent, resend...
|
126
255
|
self.broker.seek(self.host, disk_id, offset, flength, optimization_strategy)
|
127
256
|
attempts = 0
|
257
|
+
self.retries += 1
|
128
258
|
|
129
259
|
return message.data
|
130
260
|
|
@@ -138,6 +268,8 @@ class Broker:
|
|
138
268
|
mqtt_client = None
|
139
269
|
connected = False
|
140
270
|
case = None
|
271
|
+
bytes_received = 0
|
272
|
+
monitor = False
|
141
273
|
|
142
274
|
diskinfo = {}
|
143
275
|
index = {}
|
@@ -217,6 +349,9 @@ class Broker:
|
|
217
349
|
if casename != self.case:
|
218
350
|
return
|
219
351
|
|
352
|
+
if self.monitor:
|
353
|
+
self.bytes_received += len(msg.payload)
|
354
|
+
|
220
355
|
if response == "DISKS":
|
221
356
|
self._on_disk(hostname, msg.payload)
|
222
357
|
elif response == "READ":
|
@@ -238,9 +373,12 @@ class Broker:
|
|
238
373
|
self.mqtt_client.publish(f"{self.case}/{host}/INFO")
|
239
374
|
|
240
375
|
def topology(self, host: str) -> None:
|
241
|
-
self.topo
|
376
|
+
if host not in self.topo:
|
377
|
+
self.topo[host] = []
|
242
378
|
self.mqtt_client.subscribe(f"{self.case}/{host}/ID")
|
243
379
|
time.sleep(1) # need some time to avoid race condition, i.e. MQTT might react too fast
|
380
|
+
# send a simple clear command (invalid, just clears the prev. msg) just in case TOPO is stale
|
381
|
+
self.mqtt_client.publish(f"{self.case}/{host}/CLR")
|
244
382
|
self.mqtt_client.publish(f"{self.case}/{host}/TOPO")
|
245
383
|
|
246
384
|
def connect(self) -> None:
|
@@ -272,6 +410,7 @@ class Broker:
|
|
272
410
|
@arg("--mqtt-crt", dest="crt", help="client certificate file")
|
273
411
|
@arg("--mqtt-ca", dest="ca", help="certificate authority file")
|
274
412
|
@arg("--mqtt-command", dest="command", help="direct command to client(s)")
|
413
|
+
@arg("--mqtt-diag", action="store_true", dest="diag", help="show MQTT diagnostic information")
|
275
414
|
class MQTTLoader(Loader):
|
276
415
|
"""Load remote targets through a broker."""
|
277
416
|
|
@@ -292,6 +431,7 @@ class MQTTLoader(Loader):
|
|
292
431
|
def find_all(path: Path, **kwargs) -> Iterator[str]:
|
293
432
|
cls = MQTTLoader
|
294
433
|
num_peers = 1
|
434
|
+
|
295
435
|
if cls.broker is None:
|
296
436
|
if (uri := kwargs.get("parsed_path")) is None:
|
297
437
|
raise LoaderError("No URI connection details have been passed.")
|
@@ -299,8 +439,13 @@ class MQTTLoader(Loader):
|
|
299
439
|
cls.broker = Broker(**options)
|
300
440
|
cls.broker.connect()
|
301
441
|
num_peers = int(options.get("peers", 1))
|
442
|
+
cls.connection = MQTTConnection(cls.broker, path)
|
443
|
+
if options.get("diag", None):
|
444
|
+
cls.broker.monitor = True
|
445
|
+
MQTTDiagnosticLine(cls.connection, num_peers).start()
|
446
|
+
else:
|
447
|
+
cls.connection = MQTTConnection(cls.broker, path)
|
302
448
|
|
303
|
-
cls.connection = MQTTConnection(cls.broker, path)
|
304
449
|
cls.peers = cls.connection.topo(num_peers)
|
305
450
|
yield from cls.peers
|
306
451
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: dissect.target
|
3
|
-
Version: 3.18.
|
3
|
+
Version: 3.18.dev6
|
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
|
@@ -44,7 +44,7 @@ dissect/target/filesystems/vmtar.py,sha256=LlKWkTIuLemQmG9yGqL7980uC_AOL77_GWhbJ
|
|
44
44
|
dissect/target/filesystems/xfs.py,sha256=kIyFGKYlyFYC7H3jaEv-lNKtBW4ZkD92H0WpfGcr1ww,4498
|
45
45
|
dissect/target/filesystems/zip.py,sha256=WT1bQhzX_1MXXVZTKrJniae4xqRqMZ8FsfbvhgGQRTQ,4462
|
46
46
|
dissect/target/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
47
|
-
dissect/target/helpers/cache.py,sha256=
|
47
|
+
dissect/target/helpers/cache.py,sha256=TXlJBdFRz6V9zKs903am4Yawr0maYw5kZY0RqklDQJM,8568
|
48
48
|
dissect/target/helpers/config.py,sha256=6917CZ6eDHaK_tOoiVEIndyhRXO6r6eCBIleq6f47PQ,2346
|
49
49
|
dissect/target/helpers/configutil.py,sha256=Q4qVD_XSawG0MYrTzAjezWL-pCZMAW14Zmg54KGUddU,25637
|
50
50
|
dissect/target/helpers/cyber.py,sha256=Ki5oSU0GgQxjgC_yWoeieGP7GOY5blQCzNX7vy7Pgas,16782
|
@@ -86,7 +86,7 @@ dissect/target/loaders/itunes.py,sha256=rKOhlDRypQBGkuSZudMDS1Mlb9XV6BD5FRvM7tGq
|
|
86
86
|
dissect/target/loaders/kape.py,sha256=t5TfrGLqPeIpUUpXzIl6aHsqXMEGDqJ5YwDCs07DiBA,1237
|
87
87
|
dissect/target/loaders/local.py,sha256=Ul-LCd_fY7SyWOVR6nH-NqbkuNpxoZVmffwrkvQElU8,16453
|
88
88
|
dissect/target/loaders/log.py,sha256=cCkDIRS4aPlX3U-n_jUKaI2FPSV3BDpfqKceaU7rBbo,1507
|
89
|
-
dissect/target/loaders/mqtt.py,sha256=
|
89
|
+
dissect/target/loaders/mqtt.py,sha256=wba1AFopF4ANvA5YiXd6_8T-5kRBLsiKA5xCPmrD7Xk,16024
|
90
90
|
dissect/target/loaders/multiraw.py,sha256=4a3ZST0NwjnfPDxHkcEfAcX2ddUlT_C-rcrMHNg1wp4,1046
|
91
91
|
dissect/target/loaders/ova.py,sha256=6h4O-7i87J394C6KgLsPkdXRAKNwtPubzLNS3vBGs7U,744
|
92
92
|
dissect/target/loaders/overlay.py,sha256=tj99HKvNG5_JbGfb1WCv4KNSbXXSnEcPQY5XT-JUxn8,992
|
@@ -340,10 +340,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
|
|
340
340
|
dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
|
341
341
|
dissect/target/volumes/md.py,sha256=j1K1iKmspl0C_OJFc7-Q1BMWN2OCC5EVANIgVlJ_fIE,1673
|
342
342
|
dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
|
343
|
-
dissect.target-3.18.
|
344
|
-
dissect.target-3.18.
|
345
|
-
dissect.target-3.18.
|
346
|
-
dissect.target-3.18.
|
347
|
-
dissect.target-3.18.
|
348
|
-
dissect.target-3.18.
|
349
|
-
dissect.target-3.18.
|
343
|
+
dissect.target-3.18.dev6.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
|
344
|
+
dissect.target-3.18.dev6.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
345
|
+
dissect.target-3.18.dev6.dist-info/METADATA,sha256=pep2cQIYZZY6yeV07oBPPNq-6e2LnrUig_hkv-5IiEk,11299
|
346
|
+
dissect.target-3.18.dev6.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
347
|
+
dissect.target-3.18.dev6.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
|
348
|
+
dissect.target-3.18.dev6.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
|
349
|
+
dissect.target-3.18.dev6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|