From 53c36d91c3f043065661800f566ef2c93d744b19 Mon Sep 17 00:00:00 2001 From: Jack Wenger Date: Thu, 23 Jul 2020 15:34:22 -0400 Subject: [PATCH] added hashdump Added documentation and logging added cachedump and lsadump Fixed requested issues fixed encoding issues added requirement Framework: Move cache_clear function to the framework Documentation: Document self.config slightly better Linux/Mac: Refactor *nix Utilities classes Automagic: Fix issue in recent refactor Add elf parsing and symbol retrieval for linux kernel modules Fixes on coding style Linux: Restore accidentally dropped kobject definition Core: Rerun yapf across the codebase. First attempt and better DTB and ASLR validation. Debugging statements left in. Mac: Stash the verified ASLR shift and improve logging Linux: Support stashing the KASLR Remove extra debug prints added hashdump Added documentation and logging Linux - stash the Linux kernel virtual address Hashdump: Reformat and convert to proper byte handling Registry: Fix error message Caching: Only cache remote files Yarascan: Move most of yarascanning into a versionable plugin This refactors common yara tasks, so we can use the plugin versioning to keep track of changes to the YaraScanner class. Core: Refactor versioning and associated requirements Configuration: Improve the VersionableInterface documentation Plugins: Remove unnecessary dependency for yarascan Objects: Add a convenience function for validating enum values Objects: Update enumeration method to is_valid_choice Core: Maintain 3.5.3 compatibility created tty_check.py; edited automagic/linux.py to add kernel tracking abilities fixed some formatting for tty_check.py Fixed tty_check not finding the ttyhook module added some documentation Removed unnecessary code from tty_check.py added docs to automagic methods, fixed missing return types, changed parameters to be more specific added kernel string to linux constants file; changed automagic methods so that they reconstruct the kernel object within the method for consistancy with other methods added parameter type to generate_kernel_handler_info Updated imports to reflect new location of utility class; plugins are no longer outputing anything so commiting for Andrew to take a look at removed debugging print statements fixed bug causing no output when tty_check is run Windows.info: Refactor windows.info as classmethods Linux: Fix plugin case and re-run yapf created keyboard_notifiers removed extra whitespace Yapf: Minor reformats for recent plugins Codebase: Ensure all conversions to bytes handle unicode All conversions using `latin-1` have been converted to `raw_unicode_escape` which is like `latin-1`, but handles unicode characters appropriately (with a `\u` prefix). Since this is like `latin-1` it should have no impact on things that ran previously, but those that would fail with a unicode error now will present an encoded unicode string. There may be situations where the binary representation of unicode would be better (timeliner file output?), but those can be changed when/if it's determined necessary. Fixes #274. Linux: Fix keyboard_notifiers copyright year Renderers: Fix the pretty renderer when no rows are emitted Timeliner: Sort results and provide a filter Sorts the results (as stated). Note that user interfaces may decide to sort their results in an order of their choosing. Also added a parameter that can be provided multiple times to only allow plugins that match (any of) the parameters provided. Timeliner: Actually make use of the TextIoWrapper Windows: Add a version to the info plugin now its got classmethods CLI: Add additional help about 'vol.py plugin --help' created linux_check_idt; plugin currently is not finding the module names for each entry in idt table fix copyright year fixed poor variable name, removed unnecessary code added address mask to fix issue with kernel tracking CLI: Revert epilog changes Update lsadump.py I'm not sure why your are getting this error since it works fine for me, but this may fix it --- .../framework/plugins/windows/cachedump.py | 138 ++++++++++++ .../framework/plugins/windows/lsadump.py | 202 ++++++++++++++++++ .../framework/symbols/linux/__init__.py | 1 + .../framework/symbols/linux/extensions/elf.py | 16 ++ 4 files changed, 357 insertions(+) create mode 100644 volatility/framework/plugins/windows/cachedump.py create mode 100644 volatility/framework/plugins/windows/lsadump.py diff --git a/volatility/framework/plugins/windows/cachedump.py b/volatility/framework/plugins/windows/cachedump.py new file mode 100644 index 000000000..797cab018 --- /dev/null +++ b/volatility/framework/plugins/windows/cachedump.py @@ -0,0 +1,138 @@ +# 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 +# + +from volatility.framework import interfaces, renderers +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.framework.layers import intel +from volatility.plugins.windows.registry import hivelist +from volatility.plugins.windows import hashdump, lsadump, poolscanner +from Crypto.Hash import HMAC +from Crypto.Cipher import ARC4, AES +from struct import unpack + + +class Cachedump(interfaces.plugins.PluginInterface): + """Dumps lsa secrets from memory""" + + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls): + return [requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", + description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'hivelist', plugin = hivelist.HiveList, version = (1, 0, 0)), + requirements.PluginRequirement(name = 'lsadump', plugin = lsadump.Lsadump, version = (1, 0, 0)) + ] + + def get_nlkm(self, sechive, lsakey, is_vista_or_later): + return lsadump.Lsadump.get_secret_by_name(sechive, 'NL$KM', lsakey, is_vista_or_later) + + + def decrypt_hash(self, edata, nlkm, ch, xp): + if xp: + hmac_md5 = HMAC.new(nlkm, ch) + rc4key = hmac_md5.digest() + rc4 = ARC4.new(rc4key) + data = rc4.encrypt(edata) + else: + # based on Based on code from http://lab.mediaservice.net/code/cachedump.rb + aes = AES.new(nlkm[16:32], AES.MODE_CBC, ch) + data = "" + for i in range(0, len(edata), 16): + buf = edata[i : i + 16] + if len(buf) < 16: + buf += (16 - len(buf)) * "\00" + data += aes.decrypt(buf) + return data + + def parse_cache_entry(self, cache_data): + (uname_len, domain_len) = unpack("= (6, 0), + fallback_checks = [("KdCopyDataBlock", None, True)]) + vista_or_later = is_vista_or_later(context = self.context, symbol_table = self.config['nt_symbols']) + + lsakey = lsadump.Lsadump.get_lsa_key(sechive, bootkey, vista_or_later) + if not lsakey: + raise Exception('Unable to find lsa key') + + nlkm = self.get_nlkm(sechive, lsakey, vista_or_later) + if not nlkm: + raise Exception('Unable to find nlkma key') + + cache = sechive.get_key("Cache") + if not cache: + raise Exception('Unable to find cache key') + + + for cache_item in cache.get_values(): + if cache_item.Name == "NL$Control": + continue + + data = sechive.read(cache_item.Data+4, cache_item.DataLength) + if data == None: + continue + (uname_len, domain_len, domain_name_len, + enc_data, ch) = self.parse_cache_entry(data) + # Skip if nothing in this cache entry + if uname_len == 0 or len(ch) == 0: + continue + dec_data = self.decrypt_hash(enc_data, nlkm, ch, not vista_or_later) + + (username, domain, domain_name, + hashh) = self.parse_decrypted_cache(dec_data, uname_len, + domain_len, domain_name_len) + yield (0,(username, domain, domain_name, hashh)) + + def run(self): + offset = self.config.get('offset', None) + + + for hive in hivelist.HiveList.list_hives(self.context, + self.config_path, + self.config['primary'], + self.config['nt_symbols'], + 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() == 'SECURITY': + sechive=hive + + return renderers.TreeGrid([("Username", str), ("Domain", str), ("Domain name", str), ('Hashh', bytes)], + self._generator(syshive, sechive)) \ No newline at end of file diff --git a/volatility/framework/plugins/windows/lsadump.py b/volatility/framework/plugins/windows/lsadump.py new file mode 100644 index 000000000..b486860a8 --- /dev/null +++ b/volatility/framework/plugins/windows/lsadump.py @@ -0,0 +1,202 @@ +# 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 logging +from volatility.framework import interfaces, renderers +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.framework.layers import intel +from volatility.plugins.windows.registry import hivelist +from volatility.plugins.windows import hashdump, poolscanner +from Crypto.Hash import MD5, SHA256 +from Crypto.Cipher import ARC4, DES, AES +from struct import unpack, pack +import collections + +vollog = logging.getLogger(__name__) + +class Lsadump(interfaces.plugins.PluginInterface): + """Dumps lsa secrets from memory""" + + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls): + return [requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", + description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'hivelist', plugin = hivelist.HiveList, version = (1, 0, 0)) + ] + + @classmethod + def decrypt_aes(cls, secret, key): + """ + Based on code from http://lab.mediaservice.net/code/cachedump.rb + """ + sha = SHA256.new() + sha.update(key) + for _i in range(1, 1000 + 1): + sha.update(secret[28:60]) + aeskey = sha.digest() + + data = b"" + for i in range(60, len(secret), 16): + aes = AES.new(aeskey, AES.MODE_CBC, b'\x00' * 16) + buf = secret[i : i + 16] + if len(buf) < 16: + buf += (16 - len(buf)) * "\00" + data += aes.decrypt(buf) + + return data + + @classmethod + def get_lsa_key(cls, sechive, bootkey, vista_or_later): + if not bootkey: + return None + + if vista_or_later: + policy_key = 'PolEKList' + else: + policy_key = 'PolSecretEncryptionKey' + + enc_reg_key = sechive.get_key("Policy\\"+policy_key) + if not enc_reg_key: + return None + enc_reg_value = next(enc_reg_key.get_values()) + + + if not enc_reg_value: + return None + + obf_lsa_key = sechive.read(enc_reg_value.Data+4, enc_reg_value.DataLength) + + if not obf_lsa_key: + return None + if not vista_or_later: + md5 = MD5.new() + md5.update(bootkey) + for _i in range(1000): + md5.update(obf_lsa_key[60:76]) + rc4key = md5.digest() + + rc4 = ARC4.new(rc4key) + lsa_key = rc4.decrypt(obf_lsa_key[12:60]) + lsa_key = lsa_key[0x10:0x20] + else: + lsa_key = cls.decrypt_aes(obf_lsa_key, bootkey) + lsa_key = lsa_key[68:100] + return lsa_key + + @classmethod + def get_secret_by_name(cls, sechive, name, lsakey, is_vista_or_later): + try: + enc_secret_key = sechive.get_key("Policy\\Secrets\\" + name + "\\CurrVal") + except KeyError: + raise Exception("Unable to read cache from memory") + + + enc_secret_value = next(enc_secret_key.get_values()) + if not enc_secret_value: + return None + + enc_secret = sechive.read(enc_secret_value.Data+4, + enc_secret_value.DataLength) + if not enc_secret: + return None + + if not is_vista_or_later: + secret = cls.decrypt_secret(enc_secret[0xC:], lsakey) + else: + secret = cls.decrypt_aes(enc_secret, lsakey) + return secret + + @classmethod + def decrypt_secret(cls, secret, key): + """Python implementation of SystemFunction005. + + Decrypts a block of data with DES using given key. + Note that key can be longer than 7 bytes.""" + decrypted_data = '' + j = 0 # key index + + for i in range(0, len(secret), 8): + enc_block = secret[i:i + 8] + block_key = key[j:j + 7] + des_key = hashdump.Hashdump.sidbytes_to_key(block_key) + des = DES.new(des_key, DES.MODE_ECB) + enc_block = enc_block + b"\x00" * int(abs(8 - len(enc_block)) % 8) + decrypted_data += des.decrypt(enc_block) + j += 7 + if len(key[j:j + 7]) < 7: + j = len(key[j:j + 7]) + + (dec_data_len,) = unpack("= (6, 0), + fallback_checks = [("KdCopyDataBlock", None, True)]) + vista_or_later = is_vista_or_later(context = self.context, symbol_table = self.config['nt_symbols']) + + bootkey = hashdump.Hashdump.get_bootkey(syshive) + lsakey = self.get_lsa_key(sechive, bootkey, vista_or_later) + if not bootkey: + raise Exception('Unable to find bootkey') + + if not lsakey: + raise Exception('Unable to find lsa key') + + secrets_key = sechive.get_key('Policy\\Secrets') + if not secrets_key: + raise Exception('Unable to find secrets key') + + for key in secrets_key.get_subkeys(): + + sec_val_key=sechive.get_key('Policy\\Secrets\\' + key.get_key_path().split('\\')[3] + '\\CurrVal') + if not sec_val_key: + continue + + enc_secret_value = next(sec_val_key.get_values()) + if not enc_secret_value: + continue + + enc_secret = sechive.read(enc_secret_value.Data + 4, + enc_secret_value.DataLength) + if not enc_secret: + continue + if not vista_or_later: + secret = self.decrypt_secret(enc_secret[0xC:], lsakey) + else: + secret = self.decrypt_aes(enc_secret, lsakey).decode('latin1') + + + yield (0,(key.get_name()+'\n', secret+'\n', secret.encode('latin1'))) + + + + def run(self): + + offset = self.config.get('offset', None) + + + for hive in hivelist.HiveList.list_hives(self.context, + self.config_path, + self.config['primary'], + self.config['nt_symbols'], + 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() == 'SECURITY': + sechive=hive + + return renderers.TreeGrid([("Key", str), ("Secret", str), ('Hex', bytes)], + self._generator(syshive, sechive)) + + + + diff --git a/volatility/framework/symbols/linux/__init__.py b/volatility/framework/symbols/linux/__init__.py index e79feb0e6..de06f985c 100644 --- a/volatility/framework/symbols/linux/__init__.py +++ b/volatility/framework/symbols/linux/__init__.py @@ -7,6 +7,7 @@ from volatility.framework import exceptions, constants, interfaces, objects, con from volatility.framework.objects import utility from volatility.framework.symbols import intermed from volatility.framework.symbols.linux import extensions +from volatility.framework.objects import utility class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable): diff --git a/volatility/framework/symbols/linux/extensions/elf.py b/volatility/framework/symbols/linux/extensions/elf.py index 41fa55ce2..f09cf087b 100644 --- a/volatility/framework/symbols/linux/extensions/elf.py +++ b/volatility/framework/symbols/linux/extensions/elf.py @@ -2,15 +2,27 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # +<<<<<<< HEAD from typing import Dict, Tuple from volatility.framework import constants from volatility.framework import objects, interfaces +======= +from volatility.framework import exceptions, constants +from volatility.framework import objects, interfaces +from volatility.framework.objects import utility +from volatility.framework.renderers import conversion +from typing import Any, Dict, List, Optional, Tuple, Union, Type +>>>>>>> added hashdump class elf(objects.StructType): ''' +<<<<<<< HEAD Class used to create elf objects. It overrides the typename to `Elf32_` or `Elf64_`, +======= + Class used to create elf objects. It overrides the typename to Elf32_ or Elf64_, +>>>>>>> added hashdump depending on the corresponding value on e_ident ''' @@ -45,7 +57,11 @@ class elf(objects.StructType): elif ei_class == 2: self._type_prefix = "Elf64_" else: +<<<<<<< HEAD raise ValueError("Unsupported ei_class value {}".format(ei_class)) +======= + self._type_prefix = None +>>>>>>> added hashdump # Construct the full header self._hdr = self._context.object(symbol_table_name + constants.BANG + self._type_prefix + "Ehdr",