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.
Files changed (62) 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/os/unix/bsd/__init__.py +0 -0
  35. dissect/target/plugins/os/unix/esxi/_os.py +2 -2
  36. dissect/target/plugins/os/unix/linux/debian/vyos/_os.py +1 -1
  37. dissect/target/plugins/os/unix/linux/fortios/_os.py +9 -9
  38. dissect/target/plugins/os/unix/linux/services.py +1 -0
  39. dissect/target/plugins/os/unix/linux/sockets.py +2 -2
  40. dissect/target/plugins/os/unix/log/messages.py +53 -8
  41. dissect/target/plugins/os/windows/_os.py +10 -1
  42. dissect/target/plugins/os/windows/catroot.py +178 -63
  43. dissect/target/plugins/os/windows/credhist.py +210 -0
  44. dissect/target/plugins/os/windows/dpapi/crypto.py +12 -1
  45. dissect/target/plugins/os/windows/dpapi/dpapi.py +62 -7
  46. dissect/target/plugins/os/windows/dpapi/master_key.py +22 -2
  47. dissect/target/plugins/os/windows/regf/runkeys.py +6 -4
  48. dissect/target/plugins/os/windows/sam.py +10 -1
  49. dissect/target/target.py +1 -1
  50. dissect/target/tools/dump/run.py +23 -28
  51. dissect/target/tools/dump/state.py +11 -8
  52. dissect/target/tools/dump/utils.py +5 -4
  53. dissect/target/tools/query.py +3 -15
  54. dissect/target/tools/shell.py +48 -8
  55. dissect/target/tools/utils.py +23 -0
  56. {dissect.target-3.16.dev45.dist-info → dissect.target-3.17.dist-info}/METADATA +7 -3
  57. {dissect.target-3.16.dev45.dist-info → dissect.target-3.17.dist-info}/RECORD +62 -55
  58. {dissect.target-3.16.dev45.dist-info → dissect.target-3.17.dist-info}/WHEEL +1 -1
  59. {dissect.target-3.16.dev45.dist-info → dissect.target-3.17.dist-info}/COPYRIGHT +0 -0
  60. {dissect.target-3.16.dev45.dist-info → dissect.target-3.17.dist-info}/LICENSE +0 -0
  61. {dissect.target-3.16.dev45.dist-info → dissect.target-3.17.dist-info}/entry_points.txt +0 -0
  62. {dissect.target-3.16.dev45.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()