flare-floss/floss/decoding_manager.py
yuno 441fb9d17c
Merge pull request #1243 from vee1e/fix/is-import-taint
fix: also check location type in import taint
2026-04-01 14:35:11 +02:00

223 lines
7 KiB
Python

# Copyright 2017 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import List, Tuple
from dataclasses import dataclass
import viv_utils
import envi.memory
import vivisect.const
import viv_utils.emulator_drivers
from envi import Emulator
import floss.utils
import floss.logging_
from . import api_hooks
from .const import DS_MAX_ADDRESS_REVISITS_EMULATION
logger = floss.logging_.getLogger(__name__)
MAX_MAPS_SIZE = 1024 * 1024 * 100 # 100MB max memory allocated in an emulator instance
def is_import(emu, va):
"""
Return True if the given VA is that of an imported function.
"""
t = emu.getVivTaint(va)
if t is None:
return False
_, ttype, tinfo = t
return ttype == "import" and isinstance(tinfo, tuple) and len(tinfo) >= 3 and tinfo[2] == vivisect.const.LOC_IMPORT
# type aliases for envi.memory map
MemoryMapDescriptor = Tuple[
# va
int,
# size
int,
# perms
int,
# name
str,
]
# type aliases for envi.memory map
MemoryMap = Tuple[
# start
int,
# end
int,
# descriptor
MemoryMapDescriptor,
# content
bytes,
]
# type aliases for envi.memory map
Memory = List[MemoryMap]
@dataclass
class Snapshot:
"""
A snapshot of the state of the CPU and memory.
Attributes:
memory: a snapshot of the memory contents
sp: the stack counter
pc: the instruction pointer
"""
memory: Memory
sp: int
pc: int
def get_map_size(emu):
size = 0
for mapva, mapsize, mperm, mfname in emu.getMemoryMaps():
mapsize += size
return mapsize
class MapsTooLargeError(Exception):
pass
def make_snapshot(emu: Emulator) -> Snapshot:
"""
Create a snapshot of the current CPU and memory.
"""
if get_map_size(emu) > MAX_MAPS_SIZE:
logger.debug("emulator mapped too much memory: 0x%x", get_map_size(emu))
raise MapsTooLargeError()
return Snapshot(emu.getMemorySnap(), emu.getStackCounter(), emu.getProgramCounter())
@dataclass
class Delta:
"""
a pair of snapshots from before and after an operation.
facilitates diffing the state of an emulator.
"""
pre: Snapshot
post: Snapshot
class DeltaCollectorHook(viv_utils.emulator_drivers.Hook):
"""
hook that collects Deltas at each imported API call.
"""
def __init__(self, pre_snap: Snapshot):
super().__init__()
self._pre_snap = pre_snap
self.deltas: List[Delta] = []
def __call__(self, emu, api, argv):
if is_import(emu, emu.getProgramCounter()):
# TODO add apis to ignore here, e.g.
# "kernel32.GetSystemTime", "ntdll.RtlFreeHeap", "ntdll.RtlAllocateHeap",
# callname = driver._emu.getCallApi(driver._emu.getProgramCounter())[3]
try:
# TODO optimize - may leverage writelog
# reduce duplicate deltas
# reduce redundant (unchanged) data in each delta
self.deltas.append(Delta(self._pre_snap, make_snapshot(emu)))
except MapsTooLargeError:
_, _, _, name, _ = api
logger.debug("despite call to import %s, maps too large, not extracting strings", name)
pass
def emulate_function(
emu: Emulator, function_index, fva: int, return_address: int, max_instruction_count: int
) -> List[Delta]:
"""
Emulate a function and collect snapshots at each interesting place.
These interesting places include calls to imported API functions
and the final state of the emulator.
Emulation continues until the return address is hit, or
the given max_instruction_count is hit.
Some library functions are shimmed, such as memory allocation routines.
This helps "normal" routines emulate correct using standard library function.
These include:
- GetProcessHeap
- RtlAllocateHeap
- AllocateHeap
- malloc
:type function_index: viv_utils.FunctionIndex
:param fva: The start address of the function to emulate.
:param return_address: The expected return address of the function.
Emulation stops here.
:param max_instruction_count: The max number of instructions to emulate.
This helps avoid unexpected infinite loops.
"""
try:
pre_snap = make_snapshot(emu)
except MapsTooLargeError:
logger.warning("initial snapshot mapped too much memory, can't extract strings")
return []
delta_collector = DeltaCollectorHook(pre_snap)
try:
logger.debug("Emulating function at 0x%08x", fva)
driver = viv_utils.emulator_drivers.DebuggerEmulatorDriver(
emu, repmax=256, max_hit=DS_MAX_ADDRESS_REVISITS_EMULATION, max_insn=max_instruction_count
)
monitor = api_hooks.ApiMonitor(function_index)
driver.add_monitor(monitor)
driver.add_hook(delta_collector)
with api_hooks.defaultHooks(driver):
driver.run_to_va(return_address)
except viv_utils.emulator_drivers.BreakpointHit as e:
# TODO track/shortcut instances of this
if e.reason == "max_insn":
logger.debug("Halting as emulation has escaped!")
except envi.InvalidInstruction as e:
logger.debug("vivisect encountered an invalid instruction. will continue processing. %s", e)
except envi.UnsupportedInstruction as e:
logger.debug("vivisect encountered an unsupported instruction. will continue processing. %s", e)
except envi.BreakpointHit as e:
logger.debug("vivisect encountered an unexpected emulation breakpoint. will continue processing. %s", e)
except envi.exc.SegmentationViolation as e:
tos_val = floss.utils.get_stack_value(emu, 0)
logger.debug("%s: top of stack (return address): 0x%x", e, tos_val)
except envi.exc.DivideByZero as e:
logger.debug("vivisect encountered an emulation error. will continue processing. %s", e)
except viv_utils.emulator_drivers.StopEmulation:
pass
except Exception:
# we cheat here a bit and skip over various errors, check this for improvements and debugging
logger.debug("vivisect encountered an unexpected exception. will continue processing.", exc_info=True)
logger.debug("Ended emulation at 0x%08x", emu.getProgramCounter())
deltas = delta_collector.deltas
try:
deltas.append(Delta(pre_snap, make_snapshot(emu)))
except MapsTooLargeError:
logger.debug("failed to create final snapshot, emulator mapped too much memory, skipping")
pass
return deltas