dissect.target 3.16.dev44__py3-none-any.whl → 3.17__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.
Files changed (63) hide show
  1. dissect/target/container.py +1 -0
  2. dissect/target/containers/fortifw.py +190 -0
  3. dissect/target/filesystem.py +192 -67
  4. dissect/target/filesystems/dir.py +14 -1
  5. dissect/target/filesystems/overlay.py +103 -0
  6. dissect/target/helpers/compat/path_common.py +19 -5
  7. dissect/target/helpers/configutil.py +30 -7
  8. dissect/target/helpers/network_managers.py +101 -73
  9. dissect/target/helpers/record_modifier.py +4 -1
  10. dissect/target/loader.py +3 -1
  11. dissect/target/loaders/dir.py +23 -5
  12. dissect/target/loaders/itunes.py +3 -3
  13. dissect/target/loaders/mqtt.py +309 -0
  14. dissect/target/loaders/overlay.py +31 -0
  15. dissect/target/loaders/target.py +12 -9
  16. dissect/target/loaders/vb.py +2 -2
  17. dissect/target/loaders/velociraptor.py +5 -4
  18. dissect/target/plugin.py +1 -1
  19. dissect/target/plugins/apps/browser/brave.py +10 -0
  20. dissect/target/plugins/apps/browser/browser.py +43 -0
  21. dissect/target/plugins/apps/browser/chrome.py +10 -0
  22. dissect/target/plugins/apps/browser/chromium.py +234 -12
  23. dissect/target/plugins/apps/browser/edge.py +10 -0
  24. dissect/target/plugins/apps/browser/firefox.py +512 -19
  25. dissect/target/plugins/apps/browser/iexplore.py +2 -2
  26. dissect/target/plugins/apps/container/docker.py +24 -4
  27. dissect/target/plugins/apps/ssh/openssh.py +4 -0
  28. dissect/target/plugins/apps/ssh/putty.py +45 -14
  29. dissect/target/plugins/apps/ssh/ssh.py +40 -0
  30. dissect/target/plugins/apps/vpn/openvpn.py +115 -93
  31. dissect/target/plugins/child/docker.py +24 -0
  32. dissect/target/plugins/filesystem/ntfs/mft.py +1 -1
  33. dissect/target/plugins/filesystem/walkfs.py +2 -2
  34. dissect/target/plugins/general/users.py +6 -0
  35. dissect/target/plugins/os/unix/bsd/__init__.py +0 -0
  36. dissect/target/plugins/os/unix/esxi/_os.py +2 -2
  37. dissect/target/plugins/os/unix/linux/debian/vyos/_os.py +1 -1
  38. dissect/target/plugins/os/unix/linux/fortios/_os.py +9 -9
  39. dissect/target/plugins/os/unix/linux/services.py +1 -0
  40. dissect/target/plugins/os/unix/linux/sockets.py +2 -2
  41. dissect/target/plugins/os/unix/log/messages.py +53 -8
  42. dissect/target/plugins/os/windows/_os.py +10 -1
  43. dissect/target/plugins/os/windows/catroot.py +178 -63
  44. dissect/target/plugins/os/windows/credhist.py +210 -0
  45. dissect/target/plugins/os/windows/dpapi/crypto.py +12 -1
  46. dissect/target/plugins/os/windows/dpapi/dpapi.py +62 -7
  47. dissect/target/plugins/os/windows/dpapi/master_key.py +22 -2
  48. dissect/target/plugins/os/windows/regf/runkeys.py +6 -4
  49. dissect/target/plugins/os/windows/sam.py +10 -1
  50. dissect/target/target.py +1 -1
  51. dissect/target/tools/dump/run.py +23 -28
  52. dissect/target/tools/dump/state.py +11 -8
  53. dissect/target/tools/dump/utils.py +5 -4
  54. dissect/target/tools/query.py +3 -15
  55. dissect/target/tools/shell.py +48 -8
  56. dissect/target/tools/utils.py +23 -0
  57. {dissect.target-3.16.dev44.dist-info → dissect.target-3.17.dist-info}/METADATA +7 -3
  58. {dissect.target-3.16.dev44.dist-info → dissect.target-3.17.dist-info}/RECORD +63 -56
  59. {dissect.target-3.16.dev44.dist-info → dissect.target-3.17.dist-info}/WHEEL +1 -1
  60. {dissect.target-3.16.dev44.dist-info → dissect.target-3.17.dist-info}/COPYRIGHT +0 -0
  61. {dissect.target-3.16.dev44.dist-info → dissect.target-3.17.dist-info}/LICENSE +0 -0
  62. {dissect.target-3.16.dev44.dist-info → dissect.target-3.17.dist-info}/entry_points.txt +0 -0
  63. {dissect.target-3.16.dev44.dist-info → dissect.target-3.17.dist-info}/top_level.txt +0 -0
@@ -258,3 +258,4 @@ register("vdi", "VdiContainer")
258
258
  register("hdd", "HddContainer")
259
259
  register("hds", "HdsContainer")
260
260
  register("split", "SplitContainer")
261
+ register("fortifw", "FortiFirmwareContainer")
@@ -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()