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.
- dissect/target/plugins/os/unix/linux/fortios/_keys.py +1250 -0
- dissect/target/plugins/os/unix/linux/fortios/_os.py +45 -40
- dissect/target/tools/reg.py +41 -18
- {dissect.target-3.16.dev41.dist-info → dissect.target-3.16.dev44.dist-info}/METADATA +2 -2
- {dissect.target-3.16.dev41.dist-info → dissect.target-3.16.dev44.dist-info}/RECORD +10 -9
- {dissect.target-3.16.dev41.dist-info → dissect.target-3.16.dev44.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.16.dev41.dist-info → dissect.target-3.16.dev44.dist-info}/LICENSE +0 -0
- {dissect.target-3.16.dev41.dist-info → dissect.target-3.16.dev44.dist-info}/WHEEL +0 -0
- {dissect.target-3.16.dev41.dist-info → dissect.target-3.16.dev44.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.16.dev41.dist-info → dissect.target-3.16.dev44.dist-info}/top_level.txt +0 -0
@@ -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
|
-
|
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.
|
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
|
461
|
-
"""
|
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
|
-
|
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
|
dissect/target/tools/reg.py
CHANGED
@@ -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
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
56
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
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/
|
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=
|
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.
|
336
|
-
dissect.target-3.16.
|
337
|
-
dissect.target-3.16.
|
338
|
-
dissect.target-3.16.
|
339
|
-
dissect.target-3.16.
|
340
|
-
dissect.target-3.16.
|
341
|
-
dissect.target-3.16.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|
{dissect.target-3.16.dev41.dist-info → dissect.target-3.16.dev44.dist-info}/entry_points.txt
RENAMED
File without changes
|
File without changes
|