class RegionMap(object): """ Mostly used in SimAbstractMemory, RegionMap stores a series of mappings between concrete memory address ranges and memory regions, like stack frames and heap regions. """ def __init__(self, is_stack): """ Constructor :param is_stack: Whether this is a region map for stack frames or not. Different strategies apply for stack regions. """ self.is_stack = is_stack # An AVLTree, which maps stack addresses to region IDs self._address_to_region_id = AVLTree() # A dict, which maps region IDs to memory address ranges self._region_id_to_address = { } # # Properties # def __repr__(self): return "RegionMap<%s>" % ( "S" if self.is_stack else "H" ) @property def is_empty(self): return len(self._address_to_region_id) == 0 @property def stack_base(self): if not self.is_stack: raise SimRegionMapError('Calling "stack_base" on a non-stack region map.') return self._address_to_region_id.max_key() @property def region_ids(self): return self._region_id_to_address.keys() # # Public methods # def copy(self): r = RegionMap(is_stack=self.is_stack) # A shallow copy should be enough, since we never modify any RegionDescriptor object in-place if len(self._address_to_region_id) > 0: # TODO: There is a bug in bintrees 2.0.2 that prevents us from copying a non-empty AVLTree object # TODO: Consider submit a pull request r._address_to_region_id = self._address_to_region_id.copy() r._region_id_to_address = self._region_id_to_address.copy() return r def map(self, absolute_address, region_id, related_function_address=None): """ Add a mapping between an absolute address and a region ID. If this is a stack region map, all stack regions beyond (lower than) this newly added regions will be discarded. :param absolute_address: An absolute memory address. :param region_id: ID of the memory region. :param related_function_address: A related function address, mostly used for stack regions. """ if self.is_stack: # Sanity check if not region_id.startswith('stack_'): raise SimRegionMapError('Received a non-stack memory ID "%d" in a stack region map' % region_id) # Remove all stack regions that are lower than the one to add while True: try: addr = self._address_to_region_id.floor_key(absolute_address) descriptor = self._address_to_region_id[addr] # Remove this mapping del self._address_to_region_id[addr] # Remove this region ID from the other mapping del self._region_id_to_address[descriptor.region_id] except KeyError: break else: if absolute_address in self._address_to_region_id: descriptor = self._address_to_region_id[absolute_address] # Remove this mapping del self._address_to_region_id[absolute_address] del self._region_id_to_address[descriptor.region_id] # Add this new region mapping desc = RegionDescriptor( region_id, absolute_address, related_function_address=related_function_address ) self._address_to_region_id[absolute_address] = desc self._region_id_to_address[region_id] = desc def unmap_by_address(self, absolute_address): """ Removes a mapping based on its absolute address. :param absolute_address: An absolute address """ desc = self._address_to_region_id[absolute_address] del self._address_to_region_id[absolute_address] del self._region_id_to_address[desc.region_id] def absolutize(self, region_id, relative_address): """ Convert a relative address in some memory region to an absolute address. :param region_id: The memory region ID :param relative_address: The relative memory offset in that memory region :return: An absolute address if converted, or an exception is raised when region id does not exist. """ if region_id == 'global': # The global region always bases 0 return relative_address if region_id not in self._region_id_to_address: raise SimRegionMapError('Non-existent region ID "%s"' % region_id) base_address = self._region_id_to_address[region_id].base_address return base_address + relative_address def relativize(self, absolute_address, target_region_id=None): """ Convert an absolute address to the memory offset in a memory region. Note that if an address belongs to heap region is passed in to a stack region map, it will be converted to an offset included in the closest stack frame, and vice versa for passing a stack address to a heap region. Therefore you should only pass in address that belongs to the same category (stack or non-stack) of this region map. :param absolute_address: An absolute memory address :return: A tuple of the closest region ID, the relative offset, and the related function address. """ if target_region_id is None: if self.is_stack: # Get the base address of the stack frame it belongs to base_address = self._address_to_region_id.ceiling_key(absolute_address) else: try: base_address = self._address_to_region_id.floor_key(absolute_address) except KeyError: # Not found. It belongs to the global region then. return 'global', absolute_address, None descriptor = self._address_to_region_id[base_address] else: if target_region_id == 'global': # Just return the absolute address return 'global', absolute_address, None if target_region_id not in self._region_id_to_address: raise SimRegionMapError('Trying to relativize to a non-existent region "%s"' % target_region_id) descriptor = self._region_id_to_address[target_region_id] base_address = descriptor.base_address return descriptor.region_id, absolute_address - base_address, descriptor.related_function_address
class RegionMap(object): """ Mostly used in SimAbstractMemory, RegionMap stores a series of mappings between concrete memory address ranges and memory regions, like stack frames and heap regions. """ def __init__(self, is_stack): """ Constructor :param is_stack: Whether this is a region map for stack frames or not. Different strategies apply for stack regions. """ self.is_stack = is_stack # An AVLTree, which maps stack addresses to region IDs self._address_to_region_id = AVLTree() # A dict, which maps region IDs to memory address ranges self._region_id_to_address = {} # # Properties # def __repr__(self): return "RegionMap<%s>" % ("S" if self.is_stack else "H") @property def is_empty(self): return len(self._address_to_region_id) == 0 @property def stack_base(self): if not self.is_stack: raise SimRegionMapError('Calling "stack_base" on a non-stack region map.') return self._address_to_region_id.max_key() @property def region_ids(self): return self._region_id_to_address.keys() # # Public methods # def copy(self): r = RegionMap(is_stack=self.is_stack) # A shallow copy should be enough, since we never modify any RegionDescriptor object in-place if len(self._address_to_region_id) > 0: # TODO: There is a bug in bintrees 2.0.2 that prevents us from copying a non-empty AVLTree object # TODO: Consider submit a pull request r._address_to_region_id = self._address_to_region_id.copy() r._region_id_to_address = self._region_id_to_address.copy() return r def map(self, absolute_address, region_id, related_function_address=None): """ Add a mapping between an absolute address and a region ID. If this is a stack region map, all stack regions beyond (lower than) this newly added regions will be discarded. :param absolute_address: An absolute memory address. :param region_id: ID of the memory region. :param related_function_address: A related function address, mostly used for stack regions. """ if self.is_stack: # Sanity check if not region_id.startswith("stack_"): raise SimRegionMapError('Received a non-stack memory ID "%d" in a stack region map' % region_id) # Remove all stack regions that are lower than the one to add while True: try: addr = self._address_to_region_id.floor_key(absolute_address) descriptor = self._address_to_region_id[addr] # Remove this mapping del self._address_to_region_id[addr] # Remove this region ID from the other mapping del self._region_id_to_address[descriptor.region_id] except KeyError: break else: if absolute_address in self._address_to_region_id: descriptor = self._address_to_region_id[absolute_address] # Remove this mapping del self._address_to_region_id[absolute_address] del self._region_id_to_address[descriptor.region_id] # Add this new region mapping desc = RegionDescriptor(region_id, absolute_address, related_function_address=related_function_address) self._address_to_region_id[absolute_address] = desc self._region_id_to_address[region_id] = desc def unmap_by_address(self, absolute_address): """ Removes a mapping based on its absolute address. :param absolute_address: An absolute address """ desc = self._address_to_region_id[absolute_address] del self._address_to_region_id[absolute_address] del self._region_id_to_address[desc.region_id] def absolutize(self, region_id, relative_address): """ Convert a relative address in some memory region to an absolute address. :param region_id: The memory region ID :param relative_address: The relative memory offset in that memory region :return: An absolute address if converted, or an exception is raised when region id does not exist. """ if region_id == "global": # The global region always bases 0 return relative_address if region_id not in self._region_id_to_address: raise SimRegionMapError('Non-existent region ID "%s"' % region_id) base_address = self._region_id_to_address[region_id].base_address return base_address + relative_address def relativize(self, absolute_address, target_region_id=None): """ Convert an absolute address to the memory offset in a memory region. Note that if an address belongs to heap region is passed in to a stack region map, it will be converted to an offset included in the closest stack frame, and vice versa for passing a stack address to a heap region. Therefore you should only pass in address that belongs to the same category (stack or non-stack) of this region map. :param absolute_address: An absolute memory address :return: A tuple of the closest region ID, the relative offset, and the related function address. """ if target_region_id is None: if self.is_stack: # Get the base address of the stack frame it belongs to base_address = self._address_to_region_id.ceiling_key(absolute_address) else: try: base_address = self._address_to_region_id.floor_key(absolute_address) except KeyError: # Not found. It belongs to the global region then. return "global", absolute_address, None descriptor = self._address_to_region_id[base_address] else: if target_region_id == "global": # Just return the absolute address return "global", absolute_address, None if target_region_id not in self._region_id_to_address: raise SimRegionMapError('Trying to relativize to a non-existent region "%s"' % target_region_id) descriptor = self._region_id_to_address[target_region_id] base_address = descriptor.base_address return descriptor.region_id, absolute_address - base_address, descriptor.related_function_address
class CFBlanket(Analysis): """ A Control-Flow Blanket is a representation for storing all instructions, data entries, and bytes of a full program. """ def __init__(self, cfg=None): self._blanket = AVLTree() self._ffi = cffi.FFI() if cfg is not None: self._from_cfg(cfg) else: _l.debug( "CFG is not specified. Initialize CFBlanket from the knowledge base." ) for func in self.kb.functions.values(): self.add_function(func) def floor_item(self, addr): return self._blanket.floor_item(addr) def floor_items(self, addr=None): if addr is None: addr = self._blanket.min_key() else: try: addr = self.floor_addr(addr) except KeyError: try: addr = self.ceiling_addr(addr) except KeyError: # Nothing to yield raise StopIteration while True: try: item = self._blanket[addr] yield (addr, item) item_size = item.size if item.size > 0 else 1 # pylint: disable=no-member addr = self.ceiling_addr(addr + item_size) except KeyError: break def floor_addr(self, addr): return self._blanket.floor_key(addr) def ceiling_item(self, addr): return self._blanket.ceiling_item(addr) def ceiling_addr(self, addr): return self._blanket.ceiling_key(addr) def __getitem__(self, addr): return self._blanket[addr] def add_obj(self, addr, obj): """ :param addr: :param obj: :return: """ self._blanket[addr] = obj def add_function(self, func): """ Add a function and all blocks of this function to the blanket. :param angr.Function func: The function to add. :return: """ for block in func.blocks: self.add_obj(block.addr, block) def dbg_repr(self): """ The debugging representation of this CFBlanket. :return: The debugging representation of this CFBlanket. :rtype: str """ output = [] for obj in self.project.loader.all_objects: for section in obj.sections: if section.memsize == 0: continue min_addr, max_addr = section.min_addr, section.max_addr output.append("### Object %s" % repr(section)) output.append("### Range %#x-%#x" % (min_addr, max_addr)) pos = min_addr while pos < max_addr: try: addr, thing = self.floor_item(pos) output.append("%#x: %s" % (addr, repr(thing))) if thing.size == 0: pos += 1 else: pos += thing.size except KeyError: pos += 1 output.append("") return "\n".join(output) def _from_cfg(self, cfg): """ Initialize CFBlanket from a CFG instance. :param cfg: A CFG instance. :return: None """ # Let's first add all functions first for func in cfg.kb.functions.values(): self.add_function(func) self._mark_unknowns() def _mark_unknowns(self): """ Mark all unmapped regions. :return: None """ for obj in self.project.loader.all_objects: if isinstance(obj, cle.ELF): # sections? if obj.sections: for section in obj.sections: if not section.memsize or not section.vaddr: continue min_addr, max_addr = section.min_addr, section.max_addr self._mark_unknowns_core(min_addr, max_addr, obj=obj, section=section) elif obj.segments: for segment in obj.segments: if not segment.memsize: continue min_addr, max_addr = segment.min_addr, segment.max_addr self._mark_unknowns_core(min_addr, max_addr, obj=obj, segment=segment) else: # is it empty? _l.warning("Empty ELF object %s.", repr(obj)) elif isinstance(obj, cle.PE): if obj.sections: for section in obj.sections: if not section.memsize: continue min_addr, max_addr = section.min_addr, section.max_addr self._mark_unknowns_core(min_addr, max_addr, obj=obj, section=section) else: # is it empty? _l.warning("Empty PE object %s.", repr(obj)) else: min_addr, max_addr = obj.min_addr, obj.max_addr self._mark_unknowns_core(min_addr, max_addr, obj=obj) def _mark_unknowns_core(self, min_addr, max_addr, obj=None, segment=None, section=None): try: addr = self.floor_addr(min_addr) if addr < min_addr: raise KeyError except KeyError: # there is no other lower address try: next_addr = self.ceiling_addr(min_addr) if next_addr >= max_addr: raise KeyError except KeyError: next_addr = max_addr size = next_addr - min_addr if obj is None or isinstance(obj, cle.ExternObject): bytes_ = None else: try: _l.debug( "Loading bytes from object %s, section %s, segmeng %s, addresss %#x.", obj, section, segment, min_addr) bytes_ptr, _ = self.project.loader.memory.read_bytes_c( min_addr) bytes_ = self._ffi.unpack( self._ffi.cast('char*', bytes_ptr), size) # type: str except KeyError: # The address does not exist bytes_ = None self.add_obj( min_addr, Unknown(min_addr, size, bytes_=bytes_, object_=obj, segment=segment, section=section)) addr = min_addr while addr < max_addr: last_addr, last_item = self.floor_item(addr) if last_addr < min_addr: # impossible raise Exception('Impossible') if last_item.size == 0: # Make sure everything has a non-zero size last_item_size = 1 else: last_item_size = last_item.size end_addr = last_addr + last_item_size if end_addr < max_addr: try: next_addr = self.ceiling_addr(end_addr) except KeyError: next_addr = max_addr if next_addr > end_addr: # there is a gap size = next_addr - end_addr if obj is None or isinstance(obj, cle.ExternObject): bytes_ = None else: try: _l.debug( "Loading bytes from object %s, section %s, segmeng %s, addresss %#x.", obj, section, segment, next_addr) bytes_ptr, _ = self.project.loader.memory.read_bytes_c( next_addr) bytes_ = self._ffi.unpack( self._ffi.cast('char*', bytes_ptr), size) # type: str except KeyError: # The address does not exist bytes_ = None self.add_obj( end_addr, Unknown(end_addr, size, bytes_=bytes_, object_=obj, segment=segment, section=section)) addr = next_addr else: addr = max_addr