Source code for volatility3.plugins.windows.hashdump

# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import binascii
import hashlib
import logging
from struct import pack, unpack
from typing import List, Optional, Tuple

from Crypto.Cipher import AES, ARC4, DES
from Crypto.Hash import MD5

from volatility3.framework import interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.symbols.windows.extensions import registry
from volatility3.plugins.windows.registry import hivelist

vollog = logging.getLogger(__name__)


[docs]class Hashdump(interfaces.plugins.PluginInterface): """Dumps user hashes from memory""" _required_framework_version = (2, 0, 0) _version = (1, 1, 0)
[docs] @classmethod def get_requirements(cls): return [ requirements.ModuleRequirement( name="kernel", description="Windows kernel", architectures=["Intel32", "Intel64"], ), requirements.PluginRequirement( name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0) ), ]
odd_parity = [ 1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14, 16, 16, 19, 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31, 32, 32, 35, 35, 37, 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47, 49, 49, 50, 50, 52, 52, 55, 55, 56, 56, 59, 59, 61, 61, 62, 62, 64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74, 76, 76, 79, 79, 81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91, 91, 93, 93, 94, 94, 97, 97, 98, 98, 100, 100, 103, 103, 104, 104, 107, 107, 109, 109, 110, 110, 112, 112, 115, 115, 117, 117, 118, 118, 121, 121, 122, 122, 124, 124, 127, 127, 128, 128, 131, 131, 133, 133, 134, 134, 137, 137, 138, 138, 140, 140, 143, 143, 145, 145, 146, 146, 148, 148, 151, 151, 152, 152, 155, 155, 157, 157, 158, 158, 161, 161, 162, 162, 164, 164, 167, 167, 168, 168, 171, 171, 173, 173, 174, 174, 176, 176, 179, 179, 181, 181, 182, 182, 185, 185, 186, 186, 188, 188, 191, 191, 193, 193, 194, 194, 196, 196, 199, 199, 200, 200, 203, 203, 205, 205, 206, 206, 208, 208, 211, 211, 213, 213, 214, 214, 217, 217, 218, 218, 220, 220, 223, 223, 224, 224, 227, 227, 229, 229, 230, 230, 233, 233, 234, 234, 236, 236, 239, 239, 241, 241, 242, 242, 244, 244, 247, 247, 248, 248, 251, 251, 253, 253, 254, 254, ] # Permutation matrix for boot key bootkey_perm_table = [ 0x8, 0x5, 0x4, 0x2, 0xB, 0x9, 0xD, 0x3, 0x0, 0x6, 0x1, 0xC, 0xE, 0xA, 0xF, 0x7, ] # Constants for SAM decrypt algorithm aqwerty = b"!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0" anum = b"0123456789012345678901234567890123456789\0" antpassword = b"NTPASSWORD\0" almpassword = b"LMPASSWORD\0" lmkey = b"KGS!@#$%" empty_lm = b"\xaa\xd3\xb4\x35\xb5\x14\x04\xee\xaa\xd3\xb4\x35\xb5\x14\x04\xee" empty_nt = b"\x31\xd6\xcf\xe0\xd1\x6a\xe9\x31\xb7\x3c\x59\xd7\xe0\xc0\x89\xc0"
[docs] @classmethod def get_hive_key(cls, hive: registry.RegistryHive, key: str): result = None try: if hive: result = hive.get_key(key) except KeyError: vollog.info( f"Unable to load the required registry key {hive.get_name()}\\{key} from this memory image" ) return result
[docs] @classmethod def get_user_keys( cls, samhive: registry.RegistryHive ) -> List[interfaces.objects.ObjectInterface]: user_key_path = "SAM\\Domains\\Account\\Users" user_key = cls.get_hive_key(samhive, user_key_path) if not user_key: return [] return [k for k in user_key.get_subkeys() if k.Name != "Names"]
[docs] @classmethod def get_bootkey(cls, syshive: registry.RegistryHive) -> Optional[bytes]: cs = 1 lsa_base = f"ControlSet{cs:03}" + "\\Control\\Lsa" lsa_keys = ["JD", "Skew1", "GBG", "Data"] lsa = cls.get_hive_key(syshive, lsa_base) if not lsa: return None bootkey = "" for lk in lsa_keys: key = cls.get_hive_key(syshive, lsa_base + "\\" + lk) class_data = None if key: class_data = syshive.read(key.Class + 4, key.ClassLength) if class_data is None: return None bootkey += class_data.decode("utf-16-le") bootkey_str = binascii.unhexlify(bootkey) bootkey_scrambled = bytes( [bootkey_str[cls.bootkey_perm_table[i]] for i in range(len(bootkey_str))] ) return bootkey_scrambled
[docs] @classmethod def get_hbootkey( cls, samhive: registry.RegistryHive, bootkey: bytes ) -> Optional[bytes]: sam_account_path = "SAM\\Domains\\Account" if not bootkey: return None sam_account_key = cls.get_hive_key(samhive, sam_account_path) if not sam_account_key: return None sam_data = None for v in sam_account_key.get_values(): if v.get_name() == "F": sam_data = samhive.read(v.Data + 4, v.DataLength) if not sam_data: return None revision = sam_data[0x00] if revision == 2: md5 = hashlib.md5() md5.update(sam_data[0x70:0x80] + cls.aqwerty + bootkey + cls.anum) rc4_key = md5.digest() rc4 = ARC4.new(rc4_key) hbootkey = rc4.encrypt( sam_data[0x80:0xA0] ) # lgtm [py/weak-cryptographic-algorithm] return hbootkey elif revision == 3: # AES encrypted iv = sam_data[0x78:0x88] encryptedHBootKey = sam_data[0x88:0xA8] cipher = AES.new(bootkey, AES.MODE_CBC, iv) hbootkey = cipher.decrypt(encryptedHBootKey) return hbootkey[:16] return None
[docs] @classmethod def decrypt_single_salted_hash( cls, rid, hbootkey: bytes, enc_hash: bytes, _lmntstr, salt: bytes ) -> Optional[bytes]: (des_k1, des_k2) = cls.sid_to_key(rid) des1 = DES.new(des_k1, DES.MODE_ECB) des2 = DES.new(des_k2, DES.MODE_ECB) cipher = AES.new(hbootkey[:16], AES.MODE_CBC, salt) obfkey = cipher.decrypt(enc_hash) return des1.decrypt(obfkey[:8]) + des2.decrypt( obfkey[8:16] ) # lgtm [py/weak-cryptographic-algorithm]
[docs] @classmethod def get_user_hashes( cls, user: registry.CM_KEY_NODE, samhive: registry.RegistryHive, hbootkey: bytes ) -> Optional[Tuple[bytes, bytes]]: ## Will sometimes find extra user with rid = NAMES, returns empty strings right now try: rid = int(str(user.get_name()), 16) except ValueError: return None sam_data = None for v in user.get_values(): if v.get_name() == "V": sam_data = samhive.read(v.Data + 4, v.DataLength) if not sam_data: return None lm_offset = unpack("<L", sam_data[0x9C:0xA0])[0] + 0xCC lm_len = unpack("<L", sam_data[0xA0:0xA4])[0] nt_offset = unpack("<L", sam_data[0xA8:0xAC])[0] + 0xCC nt_len = unpack("<L", sam_data[0xAC:0xB0])[0] lm_revision = sam_data[lm_offset + 2 : lm_offset + 3] lmhash = None if lm_revision == b"\x01": if lm_len == 20: enc_lm_hash = sam_data[lm_offset + 0x04 : lm_offset + 0x14] lmhash = cls.decrypt_single_hash( rid, hbootkey, enc_lm_hash, cls.almpassword ) elif lm_revision == b"\x02": if lm_len == 56: lm_salt = sam_data[lm_offset + 4 : lm_offset + 20] enc_lm_hash = sam_data[lm_offset + 20 : lm_offset + 52] lmhash = cls.decrypt_single_salted_hash( rid, hbootkey, enc_lm_hash, cls.almpassword, lm_salt ) # NT hash decryption nthash = None nt_revision = sam_data[nt_offset + 2 : nt_offset + 3] if nt_revision == b"\x01": if nt_len == 20: enc_nt_hash = sam_data[nt_offset + 4 : nt_offset + 20] nthash = cls.decrypt_single_hash( rid, hbootkey, enc_nt_hash, cls.antpassword ) elif nt_revision == b"\x02": if nt_len == 56: nt_salt = sam_data[nt_offset + 8 : nt_offset + 24] enc_nt_hash = sam_data[nt_offset + 24 : nt_offset + 56] nthash = cls.decrypt_single_salted_hash( rid, hbootkey, enc_nt_hash, cls.antpassword, nt_salt ) return lmhash, nthash
[docs] @classmethod def sid_to_key(cls, sid: int) -> Tuple[bytes, bytes]: """Takes rid of a user and converts it to a key to be used by the DES cipher""" bytestr1 = [ sid & 0xFF, (sid >> 8) & 0xFF, (sid >> 16) & 0xFF, (sid >> 24) & 0xFF, ] bytestr1 += bytestr1[0:3] bytestr2 = [bytestr1[3]] + bytestr1[0:3] bytestr2 += bytestr2[0:3] return cls.sidbytes_to_key(bytes(bytestr1)), cls.sidbytes_to_key( bytes(bytestr2) )
[docs] @classmethod def sidbytes_to_key(cls, s: bytes) -> bytes: """Builds final DES key from the strings generated in sid_to_key""" key = [ s[0] >> 1, ((s[0] & 0x01) << 6) | (s[1] >> 2), ((s[1] & 0x03) << 5) | (s[2] >> 3), ((s[2] & 0x07) << 4) | (s[3] >> 4), ((s[3] & 0x0F) << 3) | (s[4] >> 5), ((s[4] & 0x1F) << 2) | (s[5] >> 6), ((s[5] & 0x3F) << 1) | (s[6] >> 7), s[6] & 0x7F, ] for i in range(8): key[i] = key[i] << 1 key[i] = cls.odd_parity[key[i]] return bytes(key)
[docs] @classmethod def decrypt_single_hash( cls, rid: int, hbootkey: bytes, enc_hash: bytes, lmntstr: bytes ): (des_k1, des_k2) = cls.sid_to_key(rid) des1 = DES.new(des_k1, DES.MODE_ECB) des2 = DES.new(des_k2, DES.MODE_ECB) md5 = MD5.new() md5.update(hbootkey[:0x10] + pack("<L", rid) + lmntstr) rc4_key = md5.digest() rc4 = ARC4.new(rc4_key) obfkey = rc4.encrypt(enc_hash) # lgtm [py/weak-cryptographic-algorithm] return des1.decrypt(obfkey[:8]) + des2.decrypt( obfkey[8:] ) # lgtm [py/weak-cryptographic-algorithm]
[docs] @classmethod def get_user_name( cls, user: registry.CM_KEY_NODE, samhive: registry.RegistryHive ) -> Optional[bytes]: value = None for v in user.get_values(): if v.get_name() == "V": value = samhive.read(v.Data + 4, v.DataLength) if not value: return None name_offset = unpack("<L", value[0x0C:0x10])[0] + 0xCC name_length = unpack("<L", value[0x10:0x14])[0] if name_length > len(value): return None username = value[name_offset : name_offset + name_length] return username
# replaces the dump_hashes method in vol2 def _generator( self, syshive: registry.RegistryHive, samhive: registry.RegistryHive ): if syshive is None: vollog.debug("SYSTEM address is None: No system hive found") if samhive is None: vollog.debug("SAM address is None: No SAM hive found") bootkey = self.get_bootkey(syshive) hbootkey = self.get_hbootkey(samhive, bootkey) if hbootkey: for user in self.get_user_keys(samhive): ret = self.get_user_hashes(user, samhive, hbootkey) if ret: lmhash, nthash = ret ## temporary fix to prevent UnicodeDecodeError backtraces ## however this can cause truncated user names as a result name = self.get_user_name(user, samhive) if name is None: name = renderers.NotAvailableValue() else: name = str(name, "utf-16-le", errors="ignore") lmout = str(binascii.hexlify(lmhash or self.empty_lm), "latin-1") ntout = str(binascii.hexlify(nthash or self.empty_nt), "latin-1") rid = int(str(user.get_name()), 16) yield (0, (name, rid, lmout, ntout)) else: vollog.warning("Hbootkey is not valid")
[docs] def run(self): offset = self.config.get("offset", None) syshive = None samhive = None kernel = self.context.modules[self.config["kernel"]] for hive in hivelist.HiveList.list_hives( self.context, self.config_path, kernel.layer_name, kernel.symbol_table_name, hive_offsets=None if offset is None else [offset], ): if hive.get_name().split("\\")[-1].upper() == "SYSTEM": syshive = hive if hive.get_name().split("\\")[-1].upper() == "SAM": samhive = hive return renderers.TreeGrid( [("User", str), ("rid", int), ("lmhash", str), ("nthash", str)], self._generator(syshive, samhive), )