dissect.target 3.16.dev41__py3-none-any.whl → 3.16.dev44__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,,