dissect.target 3.16.dev45__py3-none-any.whl → 3.17__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- dissect/target/container.py +1 -0
- dissect/target/containers/fortifw.py +190 -0
- dissect/target/filesystem.py +192 -67
- dissect/target/filesystems/dir.py +14 -1
- dissect/target/filesystems/overlay.py +103 -0
- dissect/target/helpers/compat/path_common.py +19 -5
- dissect/target/helpers/configutil.py +30 -7
- dissect/target/helpers/network_managers.py +101 -73
- dissect/target/helpers/record_modifier.py +4 -1
- dissect/target/loader.py +3 -1
- dissect/target/loaders/dir.py +23 -5
- dissect/target/loaders/itunes.py +3 -3
- dissect/target/loaders/mqtt.py +309 -0
- dissect/target/loaders/overlay.py +31 -0
- dissect/target/loaders/target.py +12 -9
- dissect/target/loaders/vb.py +2 -2
- dissect/target/loaders/velociraptor.py +5 -4
- dissect/target/plugin.py +1 -1
- dissect/target/plugins/apps/browser/brave.py +10 -0
- dissect/target/plugins/apps/browser/browser.py +43 -0
- dissect/target/plugins/apps/browser/chrome.py +10 -0
- dissect/target/plugins/apps/browser/chromium.py +234 -12
- dissect/target/plugins/apps/browser/edge.py +10 -0
- dissect/target/plugins/apps/browser/firefox.py +512 -19
- dissect/target/plugins/apps/browser/iexplore.py +2 -2
- dissect/target/plugins/apps/container/docker.py +24 -4
- dissect/target/plugins/apps/ssh/openssh.py +4 -0
- dissect/target/plugins/apps/ssh/putty.py +45 -14
- dissect/target/plugins/apps/ssh/ssh.py +40 -0
- dissect/target/plugins/apps/vpn/openvpn.py +115 -93
- dissect/target/plugins/child/docker.py +24 -0
- dissect/target/plugins/filesystem/ntfs/mft.py +1 -1
- dissect/target/plugins/filesystem/walkfs.py +2 -2
- dissect/target/plugins/os/unix/bsd/__init__.py +0 -0
- dissect/target/plugins/os/unix/esxi/_os.py +2 -2
- dissect/target/plugins/os/unix/linux/debian/vyos/_os.py +1 -1
- dissect/target/plugins/os/unix/linux/fortios/_os.py +9 -9
- dissect/target/plugins/os/unix/linux/services.py +1 -0
- dissect/target/plugins/os/unix/linux/sockets.py +2 -2
- dissect/target/plugins/os/unix/log/messages.py +53 -8
- dissect/target/plugins/os/windows/_os.py +10 -1
- dissect/target/plugins/os/windows/catroot.py +178 -63
- dissect/target/plugins/os/windows/credhist.py +210 -0
- dissect/target/plugins/os/windows/dpapi/crypto.py +12 -1
- dissect/target/plugins/os/windows/dpapi/dpapi.py +62 -7
- dissect/target/plugins/os/windows/dpapi/master_key.py +22 -2
- dissect/target/plugins/os/windows/regf/runkeys.py +6 -4
- dissect/target/plugins/os/windows/sam.py +10 -1
- dissect/target/target.py +1 -1
- dissect/target/tools/dump/run.py +23 -28
- dissect/target/tools/dump/state.py +11 -8
- dissect/target/tools/dump/utils.py +5 -4
- dissect/target/tools/query.py +3 -15
- dissect/target/tools/shell.py +48 -8
- dissect/target/tools/utils.py +23 -0
- {dissect.target-3.16.dev45.dist-info → dissect.target-3.17.dist-info}/METADATA +7 -3
- {dissect.target-3.16.dev45.dist-info → dissect.target-3.17.dist-info}/RECORD +62 -55
- {dissect.target-3.16.dev45.dist-info → dissect.target-3.17.dist-info}/WHEEL +1 -1
- {dissect.target-3.16.dev45.dist-info → dissect.target-3.17.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.16.dev45.dist-info → dissect.target-3.17.dist-info}/LICENSE +0 -0
- {dissect.target-3.16.dev45.dist-info → dissect.target-3.17.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.16.dev45.dist-info → dissect.target-3.17.dist-info}/top_level.txt +0 -0
dissect/target/container.py
CHANGED
@@ -0,0 +1,190 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import gzip
|
4
|
+
import io
|
5
|
+
import logging
|
6
|
+
import zlib
|
7
|
+
from itertools import cycle, islice
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import BinaryIO
|
10
|
+
|
11
|
+
from dissect.util.stream import AlignedStream, RangeStream, RelativeStream
|
12
|
+
|
13
|
+
from dissect.target.container import Container
|
14
|
+
from dissect.target.tools.utils import catch_sigpipe
|
15
|
+
|
16
|
+
log = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
|
19
|
+
def find_xor_key(fh: BinaryIO) -> bytes:
|
20
|
+
"""Find the XOR key for the firmware file by using known plaintext of zeros.
|
21
|
+
|
22
|
+
File-like object ``fh`` must be seeked to the correct offset where it should decode to all zeroes (0x00).
|
23
|
+
|
24
|
+
Arguments:
|
25
|
+
fh: File-like object to read from.
|
26
|
+
|
27
|
+
Returns:
|
28
|
+
bytes: XOR key, note that the XOR key is not validated and may be incorrect.
|
29
|
+
"""
|
30
|
+
key = bytearray()
|
31
|
+
|
32
|
+
pos = fh.tell()
|
33
|
+
buf = fh.read(32)
|
34
|
+
fh.seek(pos)
|
35
|
+
|
36
|
+
if pos % 512 == 0:
|
37
|
+
xor_char = 0xFF
|
38
|
+
else:
|
39
|
+
fh.seek(pos - 1)
|
40
|
+
xor_char = ord(fh.read(1))
|
41
|
+
|
42
|
+
for i, k_char in enumerate(buf):
|
43
|
+
idx = (i + pos) & 0x1F
|
44
|
+
key.append((xor_char ^ k_char ^ idx) & 0xFF)
|
45
|
+
xor_char = k_char
|
46
|
+
|
47
|
+
# align xor key
|
48
|
+
koffset = 32 - (pos & 0x1F)
|
49
|
+
key = islice(cycle(key), koffset, koffset + 32)
|
50
|
+
return bytes(key)
|
51
|
+
|
52
|
+
|
53
|
+
class FortiFirmwareFile(AlignedStream):
|
54
|
+
"""Fortinet firmware file, handles transparant decompression and deobfuscation of the firmware file."""
|
55
|
+
|
56
|
+
def __init__(self, fh: BinaryIO):
|
57
|
+
self.fh = fh
|
58
|
+
self.trailer_offset = None
|
59
|
+
self.trailer_data = None
|
60
|
+
self.xor_key = None
|
61
|
+
self.is_gzipped = False
|
62
|
+
|
63
|
+
size = None
|
64
|
+
|
65
|
+
# Check if the file is gzipped
|
66
|
+
self.fh.seek(0)
|
67
|
+
header = self.fh.read(4)
|
68
|
+
if header.startswith(b"\x1f\x8b"):
|
69
|
+
self.is_gzipped = True
|
70
|
+
|
71
|
+
# Find the extra metadata behind the gzip compressed data
|
72
|
+
# as a bonus we can also calculate the size of the firmware here
|
73
|
+
dec = zlib.decompressobj(wbits=16 + zlib.MAX_WBITS)
|
74
|
+
self.fh.seek(0)
|
75
|
+
size = 0
|
76
|
+
while True:
|
77
|
+
data = self.fh.read(io.DEFAULT_BUFFER_SIZE)
|
78
|
+
if not data:
|
79
|
+
break
|
80
|
+
d = dec.decompress(dec.unconsumed_tail + data)
|
81
|
+
size += len(d)
|
82
|
+
|
83
|
+
# Ignore the trailer data of the gzip file if we have any
|
84
|
+
if dec.unused_data:
|
85
|
+
self.trailer_offset = self.fh.seek(-len(dec.unused_data), io.SEEK_END)
|
86
|
+
self.trailer_data = self.fh.read()
|
87
|
+
log.info("Found trailer offset: %d, data: %r", self.trailer_offset, self.trailer_data)
|
88
|
+
self.fh = RangeStream(self.fh, 0, self.trailer_offset)
|
89
|
+
|
90
|
+
self.fh.seek(0)
|
91
|
+
self.fh = gzip.GzipFile(fileobj=self.fh)
|
92
|
+
|
93
|
+
# Find the xor key based on known offsets where the firmware should decode to zero bytes
|
94
|
+
for zero_offset in (0x30, 0x40, 0x400):
|
95
|
+
self.fh.seek(zero_offset)
|
96
|
+
xor_key = find_xor_key(self.fh)
|
97
|
+
if xor_key.isalnum():
|
98
|
+
self.xor_key = xor_key
|
99
|
+
log.info("Found key %r @ offset %s", self.xor_key, zero_offset)
|
100
|
+
break
|
101
|
+
else:
|
102
|
+
log.info("No xor key found")
|
103
|
+
|
104
|
+
# Determine the size of the firmware file if we didn't calculate it yet
|
105
|
+
if size is None:
|
106
|
+
size = self.fh.seek(0, io.SEEK_END)
|
107
|
+
|
108
|
+
log.info("firmware size: %s", size)
|
109
|
+
log.info("xor key: %r", self.xor_key)
|
110
|
+
log.info("gzipped: %s", self.is_gzipped)
|
111
|
+
self.fh.seek(0)
|
112
|
+
|
113
|
+
# Align the stream to 512 bytes which simplifies the XOR deobfuscation code
|
114
|
+
super().__init__(size=size, align=512)
|
115
|
+
|
116
|
+
def _read(self, offset: int, length: int) -> bytes:
|
117
|
+
self.fh.seek(offset)
|
118
|
+
buf = self.fh.read(length)
|
119
|
+
|
120
|
+
if not self.xor_key:
|
121
|
+
return buf
|
122
|
+
|
123
|
+
buf = bytearray(buf)
|
124
|
+
xor_char = 0xFF
|
125
|
+
for i, cur_char in enumerate(buf):
|
126
|
+
if (i + offset) % 512 == 0:
|
127
|
+
xor_char = 0xFF
|
128
|
+
idx = (i + offset) & 0x1F
|
129
|
+
buf[i] = ((self.xor_key[idx] ^ cur_char ^ xor_char) - idx) & 0xFF
|
130
|
+
xor_char = cur_char
|
131
|
+
|
132
|
+
return bytes(buf)
|
133
|
+
|
134
|
+
|
135
|
+
class FortiFirmwareContainer(Container):
|
136
|
+
__type__ = "fortifw"
|
137
|
+
|
138
|
+
def __init__(self, fh: BinaryIO | Path, *args, **kwargs):
|
139
|
+
if not hasattr(fh, "read"):
|
140
|
+
fh = fh.open("rb")
|
141
|
+
|
142
|
+
# Open the firmware file
|
143
|
+
self.ff = FortiFirmwareFile(fh)
|
144
|
+
|
145
|
+
# seek to MBR
|
146
|
+
self.fw = RelativeStream(self.ff, 0x200)
|
147
|
+
super().__init__(self.fw, self.ff.size, *args, **kwargs)
|
148
|
+
|
149
|
+
@staticmethod
|
150
|
+
def detect_fh(fh: BinaryIO, original: list | BinaryIO) -> bool:
|
151
|
+
return False
|
152
|
+
|
153
|
+
@staticmethod
|
154
|
+
def detect_path(path: Path, original: list | BinaryIO) -> bool:
|
155
|
+
# all Fortinet firmware files end with `-FORTINET.out`
|
156
|
+
return str(path).lower().endswith("-fortinet.out")
|
157
|
+
|
158
|
+
def read(self, length: int) -> bytes:
|
159
|
+
return self.fw.read(length)
|
160
|
+
|
161
|
+
def seek(self, offset: int, whence: int = io.SEEK_SET) -> int:
|
162
|
+
return self.fw.seek(offset, whence)
|
163
|
+
|
164
|
+
def tell(self) -> int:
|
165
|
+
return self.fw.tell()
|
166
|
+
|
167
|
+
def close(self) -> None:
|
168
|
+
pass
|
169
|
+
|
170
|
+
|
171
|
+
@catch_sigpipe
|
172
|
+
def main(argv: list[str] | None = None) -> None:
|
173
|
+
import argparse
|
174
|
+
import shutil
|
175
|
+
import sys
|
176
|
+
|
177
|
+
parser = argparse.ArgumentParser(description="Decompress and deobfuscate Fortinet firmware file to stdout.")
|
178
|
+
parser.add_argument("file", type=argparse.FileType("rb"), help="Fortinet firmware file")
|
179
|
+
parser.add_argument("--verbose", "-v", action="store_true", help="verbose output")
|
180
|
+
args = parser.parse_args(argv)
|
181
|
+
|
182
|
+
if args.verbose:
|
183
|
+
logging.basicConfig(level=logging.INFO)
|
184
|
+
|
185
|
+
ff = FortiFirmwareFile(args.file)
|
186
|
+
shutil.copyfileobj(ff, sys.stdout.buffer)
|
187
|
+
|
188
|
+
|
189
|
+
if __name__ == "__main__":
|
190
|
+
main()
|