dissect.target 3.17.dev34__py3-none-any.whl → 3.17.dev36__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/apps/browser/firefox.py +72 -0
 - dissect/target/plugins/os/windows/credhist.py +210 -0
 - {dissect.target-3.17.dev34.dist-info → dissect.target-3.17.dev36.dist-info}/METADATA +1 -1
 - {dissect.target-3.17.dev34.dist-info → dissect.target-3.17.dev36.dist-info}/RECORD +9 -8
 - {dissect.target-3.17.dev34.dist-info → dissect.target-3.17.dev36.dist-info}/COPYRIGHT +0 -0
 - {dissect.target-3.17.dev34.dist-info → dissect.target-3.17.dev36.dist-info}/LICENSE +0 -0
 - {dissect.target-3.17.dev34.dist-info → dissect.target-3.17.dev36.dist-info}/WHEEL +0 -0
 - {dissect.target-3.17.dev34.dist-info → dissect.target-3.17.dev36.dist-info}/entry_points.txt +0 -0
 - {dissect.target-3.17.dev34.dist-info → dissect.target-3.17.dev36.dist-info}/top_level.txt +0 -0
 
| 
         @@ -18,6 +18,7 @@ from dissect.target.plugin import export 
     | 
|
| 
       18 
18 
     | 
    
         
             
            from dissect.target.plugins.apps.browser.browser import (
         
     | 
| 
       19 
19 
     | 
    
         
             
                GENERIC_COOKIE_FIELDS,
         
     | 
| 
       20 
20 
     | 
    
         
             
                GENERIC_DOWNLOAD_RECORD_FIELDS,
         
     | 
| 
      
 21 
     | 
    
         
            +
                GENERIC_EXTENSION_RECORD_FIELDS,
         
     | 
| 
       21 
22 
     | 
    
         
             
                GENERIC_HISTORY_RECORD_FIELDS,
         
     | 
| 
       22 
23 
     | 
    
         
             
                GENERIC_PASSWORD_RECORD_FIELDS,
         
     | 
| 
       23 
24 
     | 
    
         
             
                BrowserPlugin,
         
     | 
| 
         @@ -43,6 +44,7 @@ try: 
     | 
|
| 
       43 
44 
     | 
    
         
             
            except ImportError:
         
     | 
| 
       44 
45 
     | 
    
         
             
                HAS_CRYPTO = False
         
     | 
| 
       45 
46 
     | 
    
         | 
| 
      
 47 
     | 
    
         
            +
            FIREFOX_EXTENSION_RECORD_FIELDS = [("uri", "source_uri"), ("string[]", "optional_permissions")]
         
     | 
| 
       46 
48 
     | 
    
         | 
| 
       47 
49 
     | 
    
         
             
            log = logging.getLogger(__name__)
         
     | 
| 
       48 
50 
     | 
    
         | 
| 
         @@ -76,6 +78,10 @@ class FirefoxPlugin(BrowserPlugin): 
     | 
|
| 
       76 
78 
     | 
    
         
             
                    "browser/firefox/download", GENERIC_DOWNLOAD_RECORD_FIELDS
         
     | 
| 
       77 
79 
     | 
    
         
             
                )
         
     | 
| 
       78 
80 
     | 
    
         | 
| 
      
 81 
     | 
    
         
            +
                BrowserExtensionRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
         
     | 
| 
      
 82 
     | 
    
         
            +
                    "browser/firefox/extension", GENERIC_EXTENSION_RECORD_FIELDS + FIREFOX_EXTENSION_RECORD_FIELDS
         
     | 
| 
      
 83 
     | 
    
         
            +
                )
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
       79 
85 
     | 
    
         
             
                BrowserPasswordRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
         
     | 
| 
       80 
86 
     | 
    
         
             
                    "browser/firefox/password", GENERIC_PASSWORD_RECORD_FIELDS
         
     | 
| 
       81 
87 
     | 
    
         
             
                )
         
     | 
| 
         @@ -305,6 +311,72 @@ class FirefoxPlugin(BrowserPlugin): 
     | 
|
| 
       305 
311 
     | 
    
         
             
                        except SQLError as e:
         
     | 
| 
       306 
312 
     | 
    
         
             
                            self.target.log.warning("Error processing history file: %s", db_file, exc_info=e)
         
     | 
| 
       307 
313 
     | 
    
         | 
| 
      
 314 
     | 
    
         
            +
                @export(record=BrowserExtensionRecord)
         
     | 
| 
      
 315 
     | 
    
         
            +
                def extensions(self) -> Iterator[BrowserExtensionRecord]:
         
     | 
| 
      
 316 
     | 
    
         
            +
                    """Return browser extension records for Firefox.
         
     | 
| 
      
 317 
     | 
    
         
            +
             
     | 
| 
      
 318 
     | 
    
         
            +
                    Yields BrowserExtensionRecord with the following fields::
         
     | 
| 
      
 319 
     | 
    
         
            +
                        ts_install (datetime): Extension install timestamp.
         
     | 
| 
      
 320 
     | 
    
         
            +
                        ts_update (datetime): Extension update timestamp.
         
     | 
| 
      
 321 
     | 
    
         
            +
                        browser (string): The browser from which the records are generated.
         
     | 
| 
      
 322 
     | 
    
         
            +
                        id (string): Extension unique identifier.
         
     | 
| 
      
 323 
     | 
    
         
            +
                        name (string): Name of the extension.
         
     | 
| 
      
 324 
     | 
    
         
            +
                        short_name (string): Short name of the extension.
         
     | 
| 
      
 325 
     | 
    
         
            +
                        default_title (string): Default title of the extension.
         
     | 
| 
      
 326 
     | 
    
         
            +
                        description (string): Description of the extension.
         
     | 
| 
      
 327 
     | 
    
         
            +
                        version (string): Version of the extension.
         
     | 
| 
      
 328 
     | 
    
         
            +
                        ext_path (path): Relative path of the extension.
         
     | 
| 
      
 329 
     | 
    
         
            +
                        from_webstore (boolean): Extension from webstore.
         
     | 
| 
      
 330 
     | 
    
         
            +
                        permissions (string[]): Permissions of the extension.
         
     | 
| 
      
 331 
     | 
    
         
            +
                        manifest (varint): Version of the extensions' manifest.
         
     | 
| 
      
 332 
     | 
    
         
            +
                        optional_permissions (string[]): Optional permissions of the extension.
         
     | 
| 
      
 333 
     | 
    
         
            +
                        source_uri (path): Source path from which the extension was downloaded.
         
     | 
| 
      
 334 
     | 
    
         
            +
                        source (path): The source file of the download record.
         
     | 
| 
      
 335 
     | 
    
         
            +
                    """
         
     | 
| 
      
 336 
     | 
    
         
            +
                    for user, _, profile_dir in self._iter_profiles():
         
     | 
| 
      
 337 
     | 
    
         
            +
                        extension_file = profile_dir.joinpath("extensions.json")
         
     | 
| 
      
 338 
     | 
    
         
            +
             
     | 
| 
      
 339 
     | 
    
         
            +
                        if not extension_file.exists():
         
     | 
| 
      
 340 
     | 
    
         
            +
                            self.target.log.warning(
         
     | 
| 
      
 341 
     | 
    
         
            +
                                "No 'extensions.json' addon file found for user %s in directory %s", user, profile_dir
         
     | 
| 
      
 342 
     | 
    
         
            +
                            )
         
     | 
| 
      
 343 
     | 
    
         
            +
                            continue
         
     | 
| 
      
 344 
     | 
    
         
            +
             
     | 
| 
      
 345 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 346 
     | 
    
         
            +
                            extensions = json.load(extension_file.open())
         
     | 
| 
      
 347 
     | 
    
         
            +
             
     | 
| 
      
 348 
     | 
    
         
            +
                            for extension in extensions.get("addons", []):
         
     | 
| 
      
 349 
     | 
    
         
            +
                                yield self.BrowserExtensionRecord(
         
     | 
| 
      
 350 
     | 
    
         
            +
                                    ts_install=extension.get("installDate", 0) // 1000,
         
     | 
| 
      
 351 
     | 
    
         
            +
                                    ts_update=extension.get("updateDate", 0) // 1000,
         
     | 
| 
      
 352 
     | 
    
         
            +
                                    browser="firefox",
         
     | 
| 
      
 353 
     | 
    
         
            +
                                    id=extension.get("id"),
         
     | 
| 
      
 354 
     | 
    
         
            +
                                    name=extension.get("defaultLocale", {}).get("name"),
         
     | 
| 
      
 355 
     | 
    
         
            +
                                    short_name=None,
         
     | 
| 
      
 356 
     | 
    
         
            +
                                    default_title=None,
         
     | 
| 
      
 357 
     | 
    
         
            +
                                    description=extension.get("defaultLocale", {}).get("description"),
         
     | 
| 
      
 358 
     | 
    
         
            +
                                    version=extension.get("version"),
         
     | 
| 
      
 359 
     | 
    
         
            +
                                    ext_path=extension.get("path"),
         
     | 
| 
      
 360 
     | 
    
         
            +
                                    from_webstore=None,
         
     | 
| 
      
 361 
     | 
    
         
            +
                                    permissions=extension.get("userPermissions", {}).get("permissions"),
         
     | 
| 
      
 362 
     | 
    
         
            +
                                    manifest_version=extension.get("manifestVersion"),
         
     | 
| 
      
 363 
     | 
    
         
            +
                                    source_uri=extension.get("sourceURI"),
         
     | 
| 
      
 364 
     | 
    
         
            +
                                    optional_permissions=extension.get("optionalPermissions", {}).get("permissions"),
         
     | 
| 
      
 365 
     | 
    
         
            +
                                    source=extension_file,
         
     | 
| 
      
 366 
     | 
    
         
            +
                                    _target=self.target,
         
     | 
| 
      
 367 
     | 
    
         
            +
                                    _user=user.user,
         
     | 
| 
      
 368 
     | 
    
         
            +
                                )
         
     | 
| 
      
 369 
     | 
    
         
            +
             
     | 
| 
      
 370 
     | 
    
         
            +
                        except FileNotFoundError:
         
     | 
| 
      
 371 
     | 
    
         
            +
                            self.target.log.info(
         
     | 
| 
      
 372 
     | 
    
         
            +
                                "No 'extensions.json' addon file found for user %s in directory %s", user, profile_dir
         
     | 
| 
      
 373 
     | 
    
         
            +
                            )
         
     | 
| 
      
 374 
     | 
    
         
            +
                        except json.JSONDecodeError:
         
     | 
| 
      
 375 
     | 
    
         
            +
                            self.target.log.warning(
         
     | 
| 
      
 376 
     | 
    
         
            +
                                "extensions.json file in directory %s is malformed, consider inspecting the file manually",
         
     | 
| 
      
 377 
     | 
    
         
            +
                                profile_dir,
         
     | 
| 
      
 378 
     | 
    
         
            +
                            )
         
     | 
| 
      
 379 
     | 
    
         
            +
             
     | 
| 
       308 
380 
     | 
    
         
             
                @export(record=BrowserPasswordRecord)
         
     | 
| 
       309 
381 
     | 
    
         
             
                def passwords(self) -> Iterator[BrowserPasswordRecord]:
         
     | 
| 
       310 
382 
     | 
    
         
             
                    """Return Firefox browser password records.
         
     | 
| 
         @@ -0,0 +1,210 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            import hashlib
         
     | 
| 
      
 2 
     | 
    
         
            +
            import logging
         
     | 
| 
      
 3 
     | 
    
         
            +
            from dataclasses import dataclass, field
         
     | 
| 
      
 4 
     | 
    
         
            +
            from pathlib import Path
         
     | 
| 
      
 5 
     | 
    
         
            +
            from typing import BinaryIO, Iterator, Optional
         
     | 
| 
      
 6 
     | 
    
         
            +
            from uuid import UUID
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            from dissect.cstruct import cstruct
         
     | 
| 
      
 9 
     | 
    
         
            +
            from dissect.util.sid import read_sid
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            from dissect.target.exceptions import UnsupportedPluginError
         
     | 
| 
      
 12 
     | 
    
         
            +
            from dissect.target.helpers import keychain
         
     | 
| 
      
 13 
     | 
    
         
            +
            from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
         
     | 
| 
      
 14 
     | 
    
         
            +
            from dissect.target.helpers.record import create_extended_descriptor
         
     | 
| 
      
 15 
     | 
    
         
            +
            from dissect.target.plugin import Plugin, export
         
     | 
| 
      
 16 
     | 
    
         
            +
            from dissect.target.plugins.general.users import UserDetails
         
     | 
| 
      
 17 
     | 
    
         
            +
            from dissect.target.plugins.os.windows.dpapi.crypto import (
         
     | 
| 
      
 18 
     | 
    
         
            +
                CipherAlgorithm,
         
     | 
| 
      
 19 
     | 
    
         
            +
                HashAlgorithm,
         
     | 
| 
      
 20 
     | 
    
         
            +
                derive_password_hash,
         
     | 
| 
      
 21 
     | 
    
         
            +
            )
         
     | 
| 
      
 22 
     | 
    
         
            +
            from dissect.target.target import Target
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            log = logging.getLogger(__name__)
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            CredHistRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
         
     | 
| 
      
 28 
     | 
    
         
            +
                "windows/credential/history",
         
     | 
| 
      
 29 
     | 
    
         
            +
                [
         
     | 
| 
      
 30 
     | 
    
         
            +
                    ("string", "guid"),
         
     | 
| 
      
 31 
     | 
    
         
            +
                    ("boolean", "decrypted"),
         
     | 
| 
      
 32 
     | 
    
         
            +
                    ("string", "sha1"),
         
     | 
| 
      
 33 
     | 
    
         
            +
                    ("string", "nt"),
         
     | 
| 
      
 34 
     | 
    
         
            +
                ],
         
     | 
| 
      
 35 
     | 
    
         
            +
            )
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
            credhist_def = """
         
     | 
| 
      
 39 
     | 
    
         
            +
            struct entry {
         
     | 
| 
      
 40 
     | 
    
         
            +
                DWORD   dwVersion;
         
     | 
| 
      
 41 
     | 
    
         
            +
                CHAR    guidLink[16];
         
     | 
| 
      
 42 
     | 
    
         
            +
                DWORD   dwNextLinkSize;
         
     | 
| 
      
 43 
     | 
    
         
            +
                DWORD   dwCredLinkType;
         
     | 
| 
      
 44 
     | 
    
         
            +
                DWORD   algHash;                    // ALG_ID
         
     | 
| 
      
 45 
     | 
    
         
            +
                DWORD   dwPbkdf2IterationCount;
         
     | 
| 
      
 46 
     | 
    
         
            +
                DWORD   dwSidSize;
         
     | 
| 
      
 47 
     | 
    
         
            +
                DWORD   algCrypt;                   // ALG_ID
         
     | 
| 
      
 48 
     | 
    
         
            +
                DWORD   dwShaHashSize;
         
     | 
| 
      
 49 
     | 
    
         
            +
                DWORD   dwNtHashSize;
         
     | 
| 
      
 50 
     | 
    
         
            +
                CHAR    pSalt[16];
         
     | 
| 
      
 51 
     | 
    
         
            +
                CHAR    pSid[dwSidSize];
         
     | 
| 
      
 52 
     | 
    
         
            +
                CHAR    encrypted[0];
         
     | 
| 
      
 53 
     | 
    
         
            +
            };
         
     | 
| 
      
 54 
     | 
    
         
            +
            """
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
            c_credhist = cstruct()
         
     | 
| 
      
 57 
     | 
    
         
            +
            c_credhist.load(credhist_def)
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
            @dataclass
         
     | 
| 
      
 61 
     | 
    
         
            +
            class CredHistEntry:
         
     | 
| 
      
 62 
     | 
    
         
            +
                version: int
         
     | 
| 
      
 63 
     | 
    
         
            +
                guid: str
         
     | 
| 
      
 64 
     | 
    
         
            +
                user_sid: str
         
     | 
| 
      
 65 
     | 
    
         
            +
                sha1: Optional[bytes]
         
     | 
| 
      
 66 
     | 
    
         
            +
                nt: Optional[bytes]
         
     | 
| 
      
 67 
     | 
    
         
            +
                hash_alg: HashAlgorithm = field(repr=False)
         
     | 
| 
      
 68 
     | 
    
         
            +
                cipher_alg: CipherAlgorithm = field(repr=False)
         
     | 
| 
      
 69 
     | 
    
         
            +
                raw: c_credhist.entry = field(repr=False)
         
     | 
| 
      
 70 
     | 
    
         
            +
                decrypted: bool = False
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                def decrypt(self, password_hash: bytes) -> None:
         
     | 
| 
      
 73 
     | 
    
         
            +
                    """Decrypt this CREDHIST entry using the provided password hash. Modifies ``CredHistEntry.sha1``
         
     | 
| 
      
 74 
     | 
    
         
            +
                    and ``CredHistEntry.nt`` values.
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                    If the decrypted ``nt`` value is 16 bytes we assume the decryption was successful.
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 79 
     | 
    
         
            +
                        password_hash: Bytes of SHA1 password hash digest.
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                    Raises:
         
     | 
| 
      
 82 
     | 
    
         
            +
                        ValueError: If the decryption seems to have failed.
         
     | 
| 
      
 83 
     | 
    
         
            +
                    """
         
     | 
| 
      
 84 
     | 
    
         
            +
                    data = self.cipher_alg.decrypt_with_hmac(
         
     | 
| 
      
 85 
     | 
    
         
            +
                        data=self.raw.encrypted,
         
     | 
| 
      
 86 
     | 
    
         
            +
                        key=derive_password_hash(password_hash, self.user_sid),
         
     | 
| 
      
 87 
     | 
    
         
            +
                        iv=self.raw.pSalt,
         
     | 
| 
      
 88 
     | 
    
         
            +
                        hash_algorithm=self.hash_alg,
         
     | 
| 
      
 89 
     | 
    
         
            +
                        rounds=self.raw.dwPbkdf2IterationCount,
         
     | 
| 
      
 90 
     | 
    
         
            +
                    )
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                    sha_size = self.raw.dwShaHashSize
         
     | 
| 
      
 93 
     | 
    
         
            +
                    nt_size = self.raw.dwNtHashSize
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                    sha1 = data[:sha_size]
         
     | 
| 
      
 96 
     | 
    
         
            +
                    nt = data[sha_size : sha_size + nt_size].rstrip(b"\x00")
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                    if len(nt) != 16:
         
     | 
| 
      
 99 
     | 
    
         
            +
                        raise ValueError("Decrypting failed, invalid password hash?")
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                    self.decrypted = True
         
     | 
| 
      
 102 
     | 
    
         
            +
                    self.sha1 = sha1
         
     | 
| 
      
 103 
     | 
    
         
            +
                    self.nt = nt
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
            class CredHistFile:
         
     | 
| 
      
 107 
     | 
    
         
            +
                def __init__(self, fh: BinaryIO) -> None:
         
     | 
| 
      
 108 
     | 
    
         
            +
                    self.fh = fh
         
     | 
| 
      
 109 
     | 
    
         
            +
                    self.entries = list(self._parse())
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                def __repr__(self) -> str:
         
     | 
| 
      
 112 
     | 
    
         
            +
                    return f"<CredHistFile fh='{self.fh}' entries={len(self.entries)}>"
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                def _parse(self) -> Iterator[CredHistEntry]:
         
     | 
| 
      
 115 
     | 
    
         
            +
                    self.fh.seek(0)
         
     | 
| 
      
 116 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 117 
     | 
    
         
            +
                        while True:
         
     | 
| 
      
 118 
     | 
    
         
            +
                            entry = c_credhist.entry(self.fh)
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                            # determine size of encrypted data and add to entry
         
     | 
| 
      
 121 
     | 
    
         
            +
                            cipher_alg = CipherAlgorithm.from_id(entry.algCrypt)
         
     | 
| 
      
 122 
     | 
    
         
            +
                            enc_size = entry.dwShaHashSize + entry.dwNtHashSize
         
     | 
| 
      
 123 
     | 
    
         
            +
                            enc_size += enc_size % cipher_alg.block_length
         
     | 
| 
      
 124 
     | 
    
         
            +
                            entry.encrypted = self.fh.read(enc_size)
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                            yield CredHistEntry(
         
     | 
| 
      
 127 
     | 
    
         
            +
                                version=entry.dwVersion,
         
     | 
| 
      
 128 
     | 
    
         
            +
                                guid=UUID(bytes_le=entry.guidLink),
         
     | 
| 
      
 129 
     | 
    
         
            +
                                user_sid=read_sid(entry.pSid),
         
     | 
| 
      
 130 
     | 
    
         
            +
                                hash_alg=HashAlgorithm.from_id(entry.algHash),
         
     | 
| 
      
 131 
     | 
    
         
            +
                                cipher_alg=cipher_alg,
         
     | 
| 
      
 132 
     | 
    
         
            +
                                sha1=None,
         
     | 
| 
      
 133 
     | 
    
         
            +
                                nt=None,
         
     | 
| 
      
 134 
     | 
    
         
            +
                                raw=entry,
         
     | 
| 
      
 135 
     | 
    
         
            +
                            )
         
     | 
| 
      
 136 
     | 
    
         
            +
                    except EOFError:
         
     | 
| 
      
 137 
     | 
    
         
            +
                        pass
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                def decrypt(self, password_hash: bytes) -> None:
         
     | 
| 
      
 140 
     | 
    
         
            +
                    """Decrypt a CREDHIST chain using the provided password SHA1 hash."""
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                    for entry in reversed(self.entries):
         
     | 
| 
      
 143 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 144 
     | 
    
         
            +
                            entry.decrypt(password_hash)
         
     | 
| 
      
 145 
     | 
    
         
            +
                        except ValueError as e:
         
     | 
| 
      
 146 
     | 
    
         
            +
                            log.warning("Could not decrypt entry %s with password %s", entry.guid, password_hash.hex())
         
     | 
| 
      
 147 
     | 
    
         
            +
                            log.debug("", exc_info=e)
         
     | 
| 
      
 148 
     | 
    
         
            +
                            continue
         
     | 
| 
      
 149 
     | 
    
         
            +
                        password_hash = entry.sha1
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
            class CredHistPlugin(Plugin):
         
     | 
| 
      
 153 
     | 
    
         
            +
                """Windows CREDHIST file parser.
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
                Windows XP:         ``C:\\Documents and Settings\\username\\Application Data\\Microsoft\\Protect\\CREDHIST``
         
     | 
| 
      
 156 
     | 
    
         
            +
                Windows 7 and up:   ``C:\\Users\\username\\AppData\\Roaming\\Microsoft\\Protect\\CREDHIST``
         
     | 
| 
      
 157 
     | 
    
         
            +
             
     | 
| 
      
 158 
     | 
    
         
            +
                Resources:
         
     | 
| 
      
 159 
     | 
    
         
            +
                    - https://www.passcape.com/index.php?section=docsys&cmd=details&id=28#41
         
     | 
| 
      
 160 
     | 
    
         
            +
                """
         
     | 
| 
      
 161 
     | 
    
         
            +
             
     | 
| 
      
 162 
     | 
    
         
            +
                def __init__(self, target: Target):
         
     | 
| 
      
 163 
     | 
    
         
            +
                    super().__init__(target)
         
     | 
| 
      
 164 
     | 
    
         
            +
                    self.files = list(self._find_files())
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
                def _find_files(self) -> Iterator[tuple[UserDetails, Path]]:
         
     | 
| 
      
 167 
     | 
    
         
            +
                    hashes = set()
         
     | 
| 
      
 168 
     | 
    
         
            +
                    for user_details in self.target.user_details.all_with_home():
         
     | 
| 
      
 169 
     | 
    
         
            +
                        for path in ["AppData/Roaming/Microsoft/Protect/CREDHIST", "Application Data/Microsoft/Protect/CREDHIST"]:
         
     | 
| 
      
 170 
     | 
    
         
            +
                            credhist_path = user_details.home_path.joinpath(path)
         
     | 
| 
      
 171 
     | 
    
         
            +
                            if credhist_path.exists() and (hash := credhist_path.get().hash()) not in hashes:
         
     | 
| 
      
 172 
     | 
    
         
            +
                                hashes.add(hash)
         
     | 
| 
      
 173 
     | 
    
         
            +
                                yield user_details.user, credhist_path
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
                def check_compatible(self) -> None:
         
     | 
| 
      
 176 
     | 
    
         
            +
                    if not self.files:
         
     | 
| 
      
 177 
     | 
    
         
            +
                        raise UnsupportedPluginError("No CREDHIST files found on target.")
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
                @export(record=CredHistRecord)
         
     | 
| 
      
 180 
     | 
    
         
            +
                def credhist(self) -> Iterator[CredHistRecord]:
         
     | 
| 
      
 181 
     | 
    
         
            +
                    """Yield and decrypt all Windows CREDHIST entries on the target."""
         
     | 
| 
      
 182 
     | 
    
         
            +
                    passwords = keychain_passwords()
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
                    if not passwords:
         
     | 
| 
      
 185 
     | 
    
         
            +
                        self.target.log.warning("No passwords provided in keychain, cannot decrypt CREDHIST hashes")
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
                    for user, path in self.files:
         
     | 
| 
      
 188 
     | 
    
         
            +
                        credhist = CredHistFile(path.open("rb"))
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
                        for password in passwords:
         
     | 
| 
      
 191 
     | 
    
         
            +
                            credhist.decrypt(hashlib.sha1(password.encode("utf-16-le")).digest())
         
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
                        for entry in credhist.entries:
         
     | 
| 
      
 194 
     | 
    
         
            +
                            yield CredHistRecord(
         
     | 
| 
      
 195 
     | 
    
         
            +
                                guid=entry.guid,
         
     | 
| 
      
 196 
     | 
    
         
            +
                                decrypted=entry.decrypted,
         
     | 
| 
      
 197 
     | 
    
         
            +
                                sha1=entry.sha1.hex() if entry.sha1 else None,
         
     | 
| 
      
 198 
     | 
    
         
            +
                                nt=entry.nt.hex() if entry.nt else None,
         
     | 
| 
      
 199 
     | 
    
         
            +
                                _user=user,
         
     | 
| 
      
 200 
     | 
    
         
            +
                                _target=self.target,
         
     | 
| 
      
 201 
     | 
    
         
            +
                            )
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
             
     | 
| 
      
 204 
     | 
    
         
            +
            def keychain_passwords() -> set:
         
     | 
| 
      
 205 
     | 
    
         
            +
                passphrases = set()
         
     | 
| 
      
 206 
     | 
    
         
            +
                for key in keychain.get_keys_for_provider("user") + keychain.get_keys_without_provider():
         
     | 
| 
      
 207 
     | 
    
         
            +
                    if key.key_type == keychain.KeyType.PASSPHRASE:
         
     | 
| 
      
 208 
     | 
    
         
            +
                        passphrases.add(key.value)
         
     | 
| 
      
 209 
     | 
    
         
            +
                passphrases.add("")
         
     | 
| 
      
 210 
     | 
    
         
            +
                return passphrases
         
     | 
| 
         @@ -1,6 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            Metadata-Version: 2.1
         
     | 
| 
       2 
2 
     | 
    
         
             
            Name: dissect.target
         
     | 
| 
       3 
     | 
    
         
            -
            Version: 3.17. 
     | 
| 
      
 3 
     | 
    
         
            +
            Version: 3.17.dev36
         
     | 
| 
       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
         
     | 
| 
         @@ -124,7 +124,7 @@ dissect/target/plugins/apps/browser/browser.py,sha256=rBIwcgdl73gm-8APwx2jEUAYXR 
     | 
|
| 
       124 
124 
     | 
    
         
             
            dissect/target/plugins/apps/browser/chrome.py,sha256=hxS8gqpBwoCrPaxNpllIa6K9DtsSGzn6XXcUaHyes6w,3048
         
     | 
| 
       125 
125 
     | 
    
         
             
            dissect/target/plugins/apps/browser/chromium.py,sha256=1oaQhMN5mJysw0VIVpTEmRCAifgv-mUQxZwrGmGHqAQ,27875
         
     | 
| 
       126 
126 
     | 
    
         
             
            dissect/target/plugins/apps/browser/edge.py,sha256=woXzZtHPWmfcV8vbxGKHELKru5JRb32MAXs43_b4K4E,2883
         
     | 
| 
       127 
     | 
    
         
            -
            dissect/target/plugins/apps/browser/firefox.py,sha256= 
     | 
| 
      
 127 
     | 
    
         
            +
            dissect/target/plugins/apps/browser/firefox.py,sha256=Msicw-13AJWbXRRF6m_p4L84rXAjsIYGFRve29cPY2M,30806
         
     | 
| 
       128 
128 
     | 
    
         
             
            dissect/target/plugins/apps/browser/iexplore.py,sha256=MqMonoaM5lj0ZFqGwS4F-P1eLmnLvX7VQGE9S3hxXag,8739
         
     | 
| 
       129 
129 
     | 
    
         
             
            dissect/target/plugins/apps/container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         
     | 
| 
       130 
130 
     | 
    
         
             
            dissect/target/plugins/apps/container/docker.py,sha256=67Eih9AfUbqsP-HlnlwoHi4rSAnVCZWM76sEyO_1m18,15316
         
     | 
| 
         @@ -260,6 +260,7 @@ dissect/target/plugins/os/windows/amcache.py,sha256=ZZNOs3bILTf0AGkDkhoatndl0j39 
     | 
|
| 
       260 
260 
     | 
    
         
             
            dissect/target/plugins/os/windows/catroot.py,sha256=eSfVqXvWWZpXoxKB1FT_evjXXNmlD7wHhA3lYpfQDeQ,11043
         
     | 
| 
       261 
261 
     | 
    
         
             
            dissect/target/plugins/os/windows/cim.py,sha256=jsrpu6TZpBUh7VWI9AV2Ib5bebTwsvqOwRfa5gjJd7c,3056
         
     | 
| 
       262 
262 
     | 
    
         
             
            dissect/target/plugins/os/windows/clfs.py,sha256=begVsZ-CY97Ksh6S1g03LjyBgu8ERY2hfNDWYPj0GXI,4872
         
     | 
| 
      
 263 
     | 
    
         
            +
            dissect/target/plugins/os/windows/credhist.py,sha256=FX_pW-tU9esdvDTSx913kf_CpGE_1jbD6bkjDb-cxHk,7069
         
     | 
| 
       263 
264 
     | 
    
         
             
            dissect/target/plugins/os/windows/datetime.py,sha256=tuBOkewmbCW8sFXcYp5p82oM5RCsVwmtC79BDCTLz8k,9472
         
     | 
| 
       264 
265 
     | 
    
         
             
            dissect/target/plugins/os/windows/defender.py,sha256=Vp_IP6YKm4igR765WvXJrHQ3RMu7FJKM3VOoR8AybV8,23737
         
     | 
| 
       265 
266 
     | 
    
         
             
            dissect/target/plugins/os/windows/env.py,sha256=-u9F9xWy6PUbQmu5Tv_MDoVmy6YB-7CbHokIK_T3S44,13891
         
     | 
| 
         @@ -339,10 +340,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z 
     | 
|
| 
       339 
340 
     | 
    
         
             
            dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
         
     | 
| 
       340 
341 
     | 
    
         
             
            dissect/target/volumes/md.py,sha256=j1K1iKmspl0C_OJFc7-Q1BMWN2OCC5EVANIgVlJ_fIE,1673
         
     | 
| 
       341 
342 
     | 
    
         
             
            dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
         
     | 
| 
       342 
     | 
    
         
            -
            dissect.target-3.17. 
     | 
| 
       343 
     | 
    
         
            -
            dissect.target-3.17. 
     | 
| 
       344 
     | 
    
         
            -
            dissect.target-3.17. 
     | 
| 
       345 
     | 
    
         
            -
            dissect.target-3.17. 
     | 
| 
       346 
     | 
    
         
            -
            dissect.target-3.17. 
     | 
| 
       347 
     | 
    
         
            -
            dissect.target-3.17. 
     | 
| 
       348 
     | 
    
         
            -
            dissect.target-3.17. 
     | 
| 
      
 343 
     | 
    
         
            +
            dissect.target-3.17.dev36.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
         
     | 
| 
      
 344 
     | 
    
         
            +
            dissect.target-3.17.dev36.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
         
     | 
| 
      
 345 
     | 
    
         
            +
            dissect.target-3.17.dev36.dist-info/METADATA,sha256=jvQ5nLWplAsNNZMyR1nIBwBQZEe-FnQRkk56_YpDAtw,11300
         
     | 
| 
      
 346 
     | 
    
         
            +
            dissect.target-3.17.dev36.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
         
     | 
| 
      
 347 
     | 
    
         
            +
            dissect.target-3.17.dev36.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
         
     | 
| 
      
 348 
     | 
    
         
            +
            dissect.target-3.17.dev36.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
         
     | 
| 
      
 349 
     | 
    
         
            +
            dissect.target-3.17.dev36.dist-info/RECORD,,
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
    
        {dissect.target-3.17.dev34.dist-info → dissect.target-3.17.dev36.dist-info}/entry_points.txt
    RENAMED
    
    | 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     |