dissect.target 3.16.dev41__py3-none-any.whl → 3.16.dev44__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.
@@ -17,6 +17,7 @@ from dissect.target.helpers.fsutil import open_decompress
17
17
  from dissect.target.helpers.record import TargetRecordDescriptor, UnixUserRecord
18
18
  from dissect.target.plugin import OperatingSystem, export
19
19
  from dissect.target.plugins.os.unix.linux._os import LinuxPlugin
20
+ from dissect.target.plugins.os.unix.linux.fortios._keys import KERNEL_KEY_MAP
20
21
  from dissect.target.target import Target
21
22
 
22
23
  try:
@@ -92,12 +93,14 @@ class FortiOSPlugin(LinuxPlugin):
92
93
  except ReadError:
93
94
  # The rootfs.gz file could be encrypted.
94
95
  try:
95
- rfs_fh = decrypt_rootfs(rootfs.open(), get_kernel_hash(sysvol))
96
+ kernel_hash = get_kernel_hash(sysvol)
97
+ key, iv = key_iv_for_kernel_hash(kernel_hash)
98
+ rfs_fh = decrypt_rootfs(rootfs.open(), key, iv)
96
99
  vfs = TarFilesystem(rfs_fh, tarinfo=cpio.CpioInfo)
97
100
  except RuntimeError:
98
101
  target.log.warning("Could not decrypt rootfs.gz. Missing `pycryptodome` dependency.")
99
102
  except ValueError as e:
100
- target.log.warning("Could not decrypt rootfs.gz. Unsupported kernel version.")
103
+ target.log.warning("Could not decrypt rootfs.gz. Unknown kernel hash (%s).", kernel_hash)
101
104
  target.log.debug("", exc_info=e)
102
105
  except ReadError as e:
103
106
  target.log.warning("Could not mount rootfs.gz. It could be corrupt.")
@@ -457,58 +460,60 @@ def decrypt_password(input: str) -> str:
457
460
  return "ENC:" + input
458
461
 
459
462
 
460
- def decrypt_rootfs(fh: BinaryIO, kernel_hash: str) -> BinaryIO:
461
- """Attempt to decrypt an encrypted ``rootfs.gz`` file.
463
+ def key_iv_for_kernel_hash(kernel_hash: str) -> tuple[bytes, bytes]:
464
+ """Return decryption key and IV for a specific sha256 kernel hash.
465
+
466
+ The decryption key and IV are used to decrypt the ``rootfs.gz`` file.
467
+
468
+ Args:
469
+ kernel_hash: SHA256 hash of the kernel file.
470
+
471
+ Returns:
472
+ Tuple with decryption key and IV.
473
+
474
+ Raises:
475
+ ValueError: When no decryption keys are available for the given kernel hash.
476
+ """
477
+
478
+ key = bytes.fromhex(KERNEL_KEY_MAP.get(kernel_hash, ""))
479
+ if len(key) == 32:
480
+ # FortiOS 7.4.x uses a KDF to derive the key and IV
481
+ return _kdf_7_4_x(key)
482
+ elif len(key) == 48:
483
+ # FortiOS 7.0.13 and 7.0.14 uses a static key and IV
484
+ return key[:32], key[32:]
485
+ raise ValueError(f"No known decryption keys for kernel hash: {kernel_hash}")
486
+
487
+
488
+ def decrypt_rootfs(fh: BinaryIO, key: bytes, iv: bytes) -> BinaryIO:
489
+ """Attempt to decrypt an encrypted ``rootfs.gz`` file with given key and IV.
462
490
 
463
491
  FortiOS releases as of 7.4.1 / 2023-08-31, have ChaCha20 encrypted ``rootfs.gz`` files.
464
492
  This function attempts to decrypt a ``rootfs.gz`` file using a static key and IV
465
493
  which can be found in the kernel.
466
494
 
467
- Currently supported versions (each release has a new key):
468
- - FortiGate VM 7.0.13
469
- - FortiGate VM 7.0.14
470
- - FortiGate VM 7.4.1
471
- - FortiGate VM 7.4.2
472
- - FortiGate VM 7.4.3
495
+ Known keys can be found in the ``_keys.py`` file.
473
496
 
474
497
  Resources:
475
498
  - https://docs.fortinet.com/document/fortimanager/7.4.2/release-notes/519207/special-notices
476
499
  - Reversing kernel (fgt_verifier_iv, fgt_verifier_decrypt, fgt_verifier_initrd)
500
+
501
+ Args:
502
+ fh: File-like object to the encrypted rootfs.gz file.
503
+ key: ChaCha20 key.
504
+ iv: ChaCha20 iv.
505
+
506
+ Returns:
507
+ File-like object to the decrypted rootfs.gz file.
508
+
509
+ Raises:
510
+ ValueError: When decryption failed.
511
+ RuntimeError: When PyCryptodome is not available.
477
512
  """
478
513
 
479
514
  if not HAS_PYCRYPTODOME:
480
515
  raise RuntimeError("PyCryptodome module not available")
481
516
 
482
- # SHA256 hashes of kernel files
483
- KERNEL_KEY_MAP = {
484
- # FortiGate VM 7.0.13
485
- "25cb2c8a419cde1f42d38fc6cbc95cf8b53db41096d0648015674d8220eba6bf": (
486
- bytes.fromhex("c87e13e1f7d21c1aca81dc13329c3a948d6e420d3a859f3958bd098747873d08"),
487
- bytes.fromhex("87486a24637e9a66f09ec182eee25594"),
488
- ),
489
- # FortiGate VM 7.0.14
490
- "67d4c913b1ceb7a62e2076ca835ebfdc67e65c7716fc604caa7552512f171197": (
491
- bytes.fromhex("9ba00c035bcaa97717d936f8268a973eb1dd64d19388153fad5f7849b8fdf0d8"),
492
- bytes.fromhex("9df4ba40dbddcf5ec9d2983681eb1940"),
493
- ),
494
- # FortiGate VM 7.4.1
495
- "a008b47327293e48502a121ee8709f243ad5da4e63d6f663c253db27bd01ea28": _kdf_7_4_x(
496
- "366486c0f2c6322ec23e4f33a98caa1b19d41c74bb4f25f6e8e2087b0655b30f"
497
- ),
498
- # FortiGate VM 7.4.2
499
- "c392cf83ab484e0b2419b2711b02cdc88a73db35634c10340037243394a586eb": _kdf_7_4_x(
500
- "480767be539de28ee773497fa731dd6368adc9946df61da8e1253fa402ba0302"
501
- ),
502
- # FortiGate VM 7.4.3
503
- "ba0450947e51844588b29bd302d2a1a3802f7718cf6840011c1b34f1c1f0bb89": _kdf_7_4_x(
504
- "4cf7a950b99cf29b0343e7ba6c609e49d9766f16c6d2f075f72ad400542f0765"
505
- ),
506
- }
507
-
508
- if not (key_data := KERNEL_KEY_MAP.get(kernel_hash)):
509
- raise ValueError("Failed to decrypt: Unknown kernel hash.")
510
-
511
- key, iv = key_data
512
517
  # First 8 bytes = counter, last 8 bytes = nonce
513
518
  # PyCryptodome interally divides this seek by 64 to get a (position, offset) tuple
514
519
  # We're interested in updating the position in the ChaCha20 internal state, so to make
@@ -3,10 +3,16 @@
3
3
  from __future__ import print_function
4
4
 
5
5
  import argparse
6
+ import itertools
6
7
  import logging
7
8
 
8
9
  from dissect.target import Target
9
- from dissect.target.exceptions import RegistryError, TargetError
10
+ from dissect.target.exceptions import (
11
+ RegistryError,
12
+ RegistryKeyNotFoundError,
13
+ TargetError,
14
+ )
15
+ from dissect.target.helpers.regutil import RegistryKey
10
16
  from dissect.target.tools.utils import (
11
17
  catch_sigpipe,
12
18
  configure_generic_arguments,
@@ -29,7 +35,8 @@ def main():
29
35
  parser.add_argument("targets", metavar="TARGETS", nargs="+", help="Targets to load")
30
36
  parser.add_argument("-k", "--key", required=True, help="key to query")
31
37
  parser.add_argument("-kv", "--value", help="value to query")
32
- parser.add_argument("-d", "--depth", type=int, const=0, nargs="?", default=1)
38
+ parser.add_argument("-d", "--depth", type=int, const=0, nargs="?", default=1, help="max depth of subkeys to print")
39
+ parser.add_argument("-l", "--length", type=int, default=100, help="max length of key value to print")
33
40
 
34
41
  configure_generic_arguments(parser)
35
42
  args = parser.parse_args()
@@ -38,34 +45,50 @@ def main():
38
45
 
39
46
  try:
40
47
  for target in Target.open_all(args.targets):
48
+ if not target.has_function("registry"):
49
+ target.log.error("Target has no Windows Registry")
50
+ continue
51
+
41
52
  try:
42
- if args.value:
43
- for key in target.registry.keys(args.key):
44
- try:
45
- print(key.value(args.value))
46
- except RegistryError:
47
- continue
48
- else:
53
+ keys = target.registry.keys(args.key)
54
+ first_key = next(keys)
55
+
56
+ print(target)
57
+
58
+ for key in itertools.chain([first_key], keys):
49
59
  try:
50
- print(target)
51
- for key in target.registry.keys(args.key):
52
- recursor(key, args.depth, 0)
60
+ if args.value:
61
+ print(key.value(args.value))
62
+ else:
63
+ recursor(key, args.depth, 0, args.length)
53
64
  except RegistryError:
54
65
  log.exception("Failed to find registry value")
55
- except Exception:
56
- log.exception("Failed to iterate key")
66
+
67
+ except (RegistryKeyNotFoundError, StopIteration):
68
+ target.log.error("Key %r does not exist", args.key)
69
+
70
+ except Exception as e:
71
+ target.log.error("Failed to iterate key: %s", e)
72
+ target.log.debug("", exc_info=e)
57
73
  except TargetError as e:
58
74
  log.error(e)
59
75
  log.debug("", exc_info=e)
60
76
  parser.exit(1)
61
77
 
62
78
 
63
- def recursor(key, depth, indent):
64
- print(" " * indent + f"+ {key.name!r} ({key.ts})")
79
+ def recursor(key: RegistryKey, depth: int, indent: int, max_length: int = 100) -> None:
80
+ class_name = ""
81
+ if key.class_name:
82
+ class_name = f" ({key.class_name})"
83
+
84
+ print(" " * indent + f"+ {key.name!r} ({key.ts})" + class_name)
65
85
 
66
86
  for r in key.values():
67
87
  try:
68
- print(" " * indent + f" - {r.name!r} {repr(r.value)[:100]}")
88
+ value = repr(r.value)
89
+ if len(value) > max_length:
90
+ value = value[:max_length] + "..."
91
+ print(" " * indent + f" - {r.name!r} {value}")
69
92
  except NotImplementedError:
70
93
  continue
71
94
 
@@ -73,7 +96,7 @@ def recursor(key, depth, indent):
73
96
  return
74
97
 
75
98
  for subkey in key.subkeys():
76
- recursor(subkey, depth - 1, indent + 2)
99
+ recursor(subkey, depth - 1, indent + 2, max_length)
77
100
 
78
101
 
79
102
  if __name__ == "__main__":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.16.dev41
3
+ Version: 3.16.dev44
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
@@ -35,7 +35,7 @@ Requires-Dist: flow.record ~=3.14.0
35
35
  Requires-Dist: structlog
36
36
  Provides-Extra: cb
37
37
  Requires-Dist: dissect.target[full] ; extra == 'cb'
38
- Requires-Dist: carbon-black-cloud-sdk-python ~=1.4.3 ; extra == 'cb'
38
+ Requires-Dist: carbon-black-cloud-sdk ~=1.4.3 ; extra == 'cb'
39
39
  Provides-Extra: full
40
40
  Requires-Dist: asn1crypto ; extra == 'full'
41
41
  Requires-Dist: dissect.btrfs <2.0.dev,>=1.0.dev ; extra == 'full'
@@ -223,7 +223,8 @@ dissect/target/plugins/os/unix/linux/debian/dpkg.py,sha256=DPBLQiHAF7ZS8IorRsGAi
223
223
  dissect/target/plugins/os/unix/linux/debian/vyos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
224
224
  dissect/target/plugins/os/unix/linux/debian/vyos/_os.py,sha256=q8qG2FLJhUbpjfwlNCmWAhFdTWMzSWUh7s7H8m4x7Fw,1741
225
225
  dissect/target/plugins/os/unix/linux/fortios/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
226
- dissect/target/plugins/os/unix/linux/fortios/_os.py,sha256=TK5Fcn0CORq-k_Cf3n5UvY1QHQFShry0HdgFALMEibc,19634
226
+ dissect/target/plugins/os/unix/linux/fortios/_keys.py,sha256=jDDHObfsUn9BGoIir9p4J_-rg9rI1rgoOfnL3R3lg4o,123358
227
+ dissect/target/plugins/os/unix/linux/fortios/_os.py,sha256=gFFzByku_3qpSrHpnqJv6xIbIe3V4iGXdUxxGD_-EFA,19435
227
228
  dissect/target/plugins/os/unix/linux/fortios/generic.py,sha256=tT4-lE0Z_DeDIN3zHrQbE8JB3cRJop1_TiEst-Au0bs,1230
228
229
  dissect/target/plugins/os/unix/linux/fortios/locale.py,sha256=VDdk60sqe2JTfftssO05C667-_BpI3kcqKOTVzO3ueU,5209
229
230
  dissect/target/plugins/os/unix/linux/redhat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -317,7 +318,7 @@ dissect/target/tools/info.py,sha256=3smHr8I71yj3kCjsQ5nXkOHI9T_N8UwvkVa1CNOxB-s,
317
318
  dissect/target/tools/logging.py,sha256=5ZnumtMWLyslxfrUGZ4ntRyf3obOOhmn8SBjKfdLcEg,4174
318
319
  dissect/target/tools/mount.py,sha256=L_0tSmiBdW4aSaF0vXjB0bAkTC0kmT2N1hrbW6s5Jow,3254
319
320
  dissect/target/tools/query.py,sha256=1LbvUKSmXOCMb4xqP3t86JkOgFzKlc7mLCqcczfLht8,16018
320
- dissect/target/tools/reg.py,sha256=tII0MLqJ-3lOt7jE-zHUDqYrk0P4euPjiSS_99FT6LE,2378
321
+ dissect/target/tools/reg.py,sha256=FDsiBBDxjWVUBTRj8xn82vZe-J_d9piM-TKS3PHZCcM,3193
321
322
  dissect/target/tools/shell.py,sha256=EBRNKiIV3ljaXKAXraA6DmrIw8Cy5h9irAuwlblP3zo,43251
322
323
  dissect/target/tools/utils.py,sha256=bhVZ3-8YynpHkBl4m1T4IpSpCArAXnEjjYwAFGW5JPg,10595
323
324
  dissect/target/tools/dump/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -332,10 +333,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
332
333
  dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
333
334
  dissect/target/volumes/md.py,sha256=j1K1iKmspl0C_OJFc7-Q1BMWN2OCC5EVANIgVlJ_fIE,1673
334
335
  dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
335
- dissect.target-3.16.dev41.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
336
- dissect.target-3.16.dev41.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
337
- dissect.target-3.16.dev41.dist-info/METADATA,sha256=FsrZyLCUUmg0r0ffyWK_i0vhYmYxnk2Z_piZijkEGbo,11107
338
- dissect.target-3.16.dev41.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
339
- dissect.target-3.16.dev41.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
340
- dissect.target-3.16.dev41.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
341
- dissect.target-3.16.dev41.dist-info/RECORD,,
336
+ dissect.target-3.16.dev44.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
337
+ dissect.target-3.16.dev44.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
338
+ dissect.target-3.16.dev44.dist-info/METADATA,sha256=FVsQADibmaOBO3JcCbWI9r2XQ0uXQLKL-yeCztWemQM,11100
339
+ dissect.target-3.16.dev44.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
340
+ dissect.target-3.16.dev44.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
341
+ dissect.target-3.16.dev44.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
342
+ dissect.target-3.16.dev44.dist-info/RECORD,,