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"]