Source code for volatility3.framework.automagic.mac

# This file is Copyright 2019 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
import os
import struct
from typing import Optional

from volatility3.framework import constants, exceptions, interfaces, layers
from volatility3.framework.automagic import symbol_cache, symbol_finder
from volatility3.framework.configuration import requirements
from volatility3.framework.layers import intel, scanners
from volatility3.framework.symbols import mac

vollog = logging.getLogger(__name__)


[docs]class MacIntelStacker(interfaces.automagic.StackerLayerInterface): stack_order = 35 exclusion_list = ['windows', 'linux']
[docs] @classmethod def stack(cls, context: interfaces.context.ContextInterface, layer_name: str, progress_callback: constants.ProgressCallback = None) -> Optional[interfaces.layers.DataLayerInterface]: """Attempts to identify mac within this layer.""" # Version check the SQlite cache required = (1, 0, 0) if not requirements.VersionRequirement.matches_required(required, symbol_cache.SqliteCache.version): vollog.info( f"SQLiteCache version not suitable: required {required} found {symbol_cache.SqliteCache.version}") return None # Bail out by default unless we can stack properly layer = context.layers[layer_name] new_layer = None join = interfaces.configuration.path_join # Never stack on top of an intel layer # FIXME: Find a way to improve this check if isinstance(layer, intel.Intel): return None identifiers_path = os.path.join(constants.CACHE_PATH, constants.IDENTIFIERS_FILENAME) mac_banners = symbol_cache.SqliteCache(identifiers_path).get_identifier_dictionary( operating_system = 'mac') # If we have no banners, don't bother scanning if not mac_banners: vollog.info("No Mac banners found - if this is a mac plugin, please check your symbol files location") return None mss = scanners.MultiStringScanner([x for x in mac_banners if x]) for banner_offset, banner in layer.scan(context = context, scanner = mss, progress_callback = progress_callback): dtb = None vollog.debug(f"Identified banner: {repr(banner)}") isf_path = mac_banners.get(banner, None) if isf_path: table_name = context.symbol_space.free_table_name('MacintelStacker') table = mac.MacKernelIntermedSymbols(context = context, config_path = join('temporary', table_name), name = table_name, isf_url = isf_path) context.symbol_space.append(table) kaslr_shift = cls.find_aslr(context = context, symbol_table = table_name, layer_name = layer_name, compare_banner = banner, compare_banner_offset = banner_offset, progress_callback = progress_callback) if kaslr_shift == 0: vollog.log(constants.LOGLEVEL_VVV, f"Invalid kalsr_shift found at offset: {banner_offset}") continue bootpml4_addr = cls.virtual_to_physical_address(table.get_symbol("BootPML4").address + kaslr_shift) new_layer_name = context.layers.free_layer_name("MacDTBTempLayer") config_path = join("automagic", "MacIntelHelper", new_layer_name) context.config[join(config_path, "memory_layer")] = layer_name context.config[join(config_path, "page_map_offset")] = bootpml4_addr layer = layers.intel.Intel32e(context, config_path = config_path, name = new_layer_name, metadata = {'os': 'Mac'}) idlepml4_ptr = table.get_symbol("IdlePML4").address + kaslr_shift try: idlepml4_str = layer.read(idlepml4_ptr, 4) except exceptions.InvalidAddressException: vollog.log(constants.LOGLEVEL_VVVV, f"Skipping invalid idlepml4_ptr: 0x{idlepml4_ptr:0x}") continue idlepml4_addr = struct.unpack("<I", idlepml4_str)[0] tmp_dtb = idlepml4_addr if tmp_dtb % 4096: vollog.log(constants.LOGLEVEL_VVV, f"Skipping non-page aligned DTB: 0x{tmp_dtb:0x}") continue dtb = tmp_dtb # Build the new layer new_layer_name = context.layers.free_layer_name("IntelLayer") config_path = join("automagic", "MacIntelHelper", new_layer_name) context.config[join(config_path, "memory_layer")] = layer_name context.config[join(config_path, "page_map_offset")] = dtb context.config[join(config_path, MacSymbolFinder.banner_config_key)] = str(banner, 'latin-1') new_layer = intel.Intel32e(context, config_path = config_path, name = new_layer_name, metadata = {'os': 'mac'}) new_layer.config['kernel_virtual_offset'] = kaslr_shift if new_layer and dtb: vollog.debug(f"DTB was found at: 0x{dtb:0x}") return new_layer vollog.debug("No suitable mac banner could be matched") return None
[docs] @classmethod def find_aslr(cls, context: interfaces.context.ContextInterface, symbol_table: str, layer_name: str, compare_banner: str = "", compare_banner_offset: int = 0, progress_callback: constants.ProgressCallback = None) -> int: """Determines the offset of the actual DTB in physical space and its symbol offset.""" version_symbol = symbol_table + constants.BANG + 'version' version_json_address = context.symbol_space.get_symbol(version_symbol).address version_major_symbol = symbol_table + constants.BANG + 'version_major' version_major_json_address = context.symbol_space.get_symbol(version_major_symbol).address version_major_phys_offset = cls.virtual_to_physical_address(version_major_json_address) version_minor_symbol = symbol_table + constants.BANG + 'version_minor' version_minor_json_address = context.symbol_space.get_symbol(version_minor_symbol).address version_minor_phys_offset = cls.virtual_to_physical_address(version_minor_json_address) if not compare_banner_offset or not compare_banner: offset_generator = cls._scan_generator(context, layer_name, progress_callback) else: offset_generator = [(compare_banner_offset, compare_banner)] aslr_shift = 0 for offset, banner in offset_generator: banner_major, banner_minor = [int(x) for x in banner[22:].split(b".")[0:2]] tmp_aslr_shift = offset - cls.virtual_to_physical_address(version_json_address) major_string = context.layers[layer_name].read(version_major_phys_offset + tmp_aslr_shift, 4) major = struct.unpack("<I", major_string)[0] if major != banner_major: continue minor_string = context.layers[layer_name].read(version_minor_phys_offset + tmp_aslr_shift, 4) minor = struct.unpack("<I", minor_string)[0] if minor != banner_minor: continue if tmp_aslr_shift & 0xfff != 0: continue aslr_shift = tmp_aslr_shift & 0xffffffff break vollog.log(constants.LOGLEVEL_VVVV, f"Mac find_aslr returned: {aslr_shift:0x}") return aslr_shift
[docs] @classmethod def virtual_to_physical_address(cls, addr: int) -> int: """Converts a virtual mac address to a physical one (does not account of ASLR)""" if addr > 0xffffff8000000000: addr = addr - 0xffffff8000000000 else: addr = addr - 0xff8000000000 return addr
@classmethod def _scan_generator(cls, context, layer_name, progress_callback): darwin_signature = rb"Darwin Kernel Version \d{1,3}\.\d{1,3}\.\d{1,3}: [^\x00]+\x00" for offset in context.layers[layer_name].scan(scanner = scanners.RegExScanner(darwin_signature), context = context, progress_callback = progress_callback): banner = context.layers[layer_name].read(offset, 128) idx = banner.find(b"\x00") if idx != -1: banner = banner[:idx] yield offset, banner
[docs]class MacSymbolFinder(symbol_finder.SymbolFinder): """Mac symbol loader based on uname signature strings.""" banner_config_key = 'kernel_banner' operating_system = 'mac' find_aslr = MacIntelStacker.find_aslr symbol_class = "volatility3.framework.symbols.mac.MacKernelIntermedSymbols" exclusion_list = ['windows', 'linux']