angr.analyses.reaching_definitions.engine_ail 源代码

# pylint:disable=missing-class-docstring,too-many-boolean-expressions
from __future__ import annotations
from itertools import chain
from collections.abc import Iterable
import logging
from typing import cast

from archinfo.types import RegisterOffset
import claripy
import ailment
from claripy import FSORT_DOUBLE, FSORT_FLOAT

from angr.engines.light import SpOffset
from angr.engines.light.engine import SimEngineNostmtAIL
from angr.errors import SimEngineError, SimMemoryMissingError
from angr.calling_conventions import default_cc, SimRegArg, SimTypeBottom
from angr.storage.memory_mixins.paged_memory.pages.multi_values import MultiValues, mv_is_bv
from angr.knowledge_plugins.key_definitions.atoms import Atom, Register, Tmp, MemoryLocation
from angr.knowledge_plugins.key_definitions.constants import OP_BEFORE, OP_AFTER
from angr.knowledge_plugins.key_definitions.live_definitions import Definition, LiveDefinitions
from angr.code_location import CodeLocation, ExternalCodeLocation
from .subject import SubjectType
from .rd_state import ReachingDefinitionsState
from .function_handler import FunctionHandler, FunctionCallData

l = logging.getLogger(name=__name__)


[文档] class SimEngineRDAIL( SimEngineNostmtAIL[ ReachingDefinitionsState, MultiValues[claripy.ast.BV | claripy.ast.FP], None, ReachingDefinitionsState ] ):
[文档] def __init__( self, project, function_handler: FunctionHandler, stack_pointer_tracker=None, use_callee_saved_regs_at_return=True, bp_as_gpr: bool = False, ): super().__init__(project) self._function_handler = function_handler self._visited_blocks = None self._dep_graph = None self._stack_pointer_tracker = stack_pointer_tracker self._use_callee_saved_regs_at_return = use_callee_saved_regs_at_return self.bp_as_gpr = bp_as_gpr
def _is_top(self, expr): """ MultiValues are not really "top" in the stricter sense. They are just a collection of values, some of which might be top """ return False def _top(self, bits) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: return MultiValues(self.state.top(bits))
[文档] def process( self, state, *, dep_graph=None, visited_blocks=None, block=None, fail_fast=False, whitelist=None, **kwargs ): self._dep_graph = dep_graph self._visited_blocks = visited_blocks try: result_state = super().process(state, whitelist=whitelist, block=block) except SimEngineError: if fail_fast is True: raise result_state = state return result_state
def _process_block_end(self, block, stmt_data, whitelist): return self.state # # Private methods # def _expr_bv(self, expr: ailment.expression.Expression) -> MultiValues[claripy.ast.BV]: result = self._expr(expr) assert mv_is_bv(result) return result def _expr_pair( self, arg0: ailment.expression.Expression, arg1: ailment.expression.Expression ) -> ( tuple[MultiValues[claripy.ast.BV], MultiValues[claripy.ast.BV]] | tuple[MultiValues[claripy.ast.FP], MultiValues[claripy.ast.FP]] ): r0 = self._expr(arg0) r1 = self._expr(arg1) assert type(r0) is type(r1) return r0, r1 # type: ignore def _external_codeloc(self): return ExternalCodeLocation(self.state.codeloc.context) def _set_codeloc(self): # TODO do we want a better mechanism to specify context updates? new_codeloc = CodeLocation( self.block.addr, self.stmt_idx, ins_addr=self.ins_addr, block_idx=self.block.idx, context=self.state.codeloc.context, ) self.state.move_codelocs(new_codeloc) self.state.analysis.model.at_new_stmt(new_codeloc) # # AIL statement handlers # def _stmt(self, stmt): if self.state.analysis: self.state.analysis.stmt_observe(self.stmt_idx, stmt, self.block, self.state, OP_BEFORE) self.state.analysis.insn_observe(self.ins_addr, stmt, self.block, self.state, OP_BEFORE) self._set_codeloc() super()._stmt(stmt) if self.state.analysis: self.state.analysis.stmt_observe(self.stmt_idx, stmt, self.block, self.state, OP_AFTER) self.state.analysis.insn_observe(self.ins_addr, stmt, self.block, self.state, OP_AFTER) def _handle_stmt_Assignment(self, stmt): src = self._expr(stmt.src) dst = stmt.dst if isinstance(dst, ailment.Tmp): self.state.kill_and_add_definition(Tmp(dst.tmp_idx, dst.size), src) self.tmps[dst.tmp_idx] = src elif isinstance(dst, ailment.Register): reg = Register(RegisterOffset(dst.reg_offset), dst.size) self.state.kill_and_add_definition(reg, src) if dst.reg_offset == self.arch.sp_offset: self.state._sp_adjusted = True # TODO: Special logic that frees all definitions above the current stack pointer else: l.warning("Unsupported type of Assignment dst %s.", type(dst).__name__) def _handle_stmt_Store(self, stmt: ailment.Stmt.Store) -> None: data = self._expr(stmt.data) addr = self._expr_bv(stmt.addr) size: int = stmt.size if stmt.guard is not None: self._expr(stmt.guard) addr_v = addr.one_value() if addr_v is not None and not self.state.is_top(addr_v): if self.state.is_stack_address(addr_v): stack_offset = self.state.get_stack_offset(addr_v) if stack_offset is not None: memory_location = MemoryLocation(SpOffset(self.arch.bits, stack_offset), size, endness=stmt.endness) else: memory_location = None elif self.state.is_heap_address(addr_v): memory_location = None else: memory_location = MemoryLocation(addr_v.concrete_value, size, endness=stmt.endness) if memory_location is not None: self.state.kill_and_add_definition(memory_location, data, endness=stmt.endness) def _handle_stmt_Jump(self, stmt): _ = self._expr(stmt.target) def _handle_stmt_ConditionalJump(self, stmt): _ = self._expr(stmt.condition) # pylint:disable=unused-variable if stmt.true_target is not None: _ = self._expr(stmt.true_target) # pylint:disable=unused-variable if stmt.false_target is not None: _ = self._expr(stmt.false_target) # pylint:disable=unused-variable ip = Register(cast(RegisterOffset, self.arch.ip_offset), self.arch.bytes) self.state.kill_definitions(ip) def _handle_stmt_Call(self, stmt: ailment.Stmt.Call): data = self._handle_Call_base(stmt, is_expr=False) src = data.ret_values if src is None: return dst = stmt.ret_expr if isinstance(dst, ailment.Tmp): _, defs = self.state.kill_and_add_definition(Tmp(dst.tmp_idx, dst.size), src, uses=data.ret_values_deps) self.tmps[dst.tmp_idx] = src elif isinstance(dst, ailment.Register): full_reg_offset, full_reg_size = self.arch.registers[ self.arch.register_names[RegisterOffset(dst.reg_offset)] ] if dst.size != full_reg_size: # we need to extend the value to overwrite the entire register otv = {} next_off = 0 if full_reg_offset < dst.reg_offset: otv[0] = {claripy.BVV(0, (dst.reg_offset - full_reg_offset) * 8)} next_off = dst.reg_offset - full_reg_offset for off, items in src.items(): otv[next_off + off] = set(items) next_off += len(src) // 8 if next_off < full_reg_size: otv[next_off] = {claripy.BVV(0, (full_reg_size - next_off) * 8)} src = MultiValues(offset_to_values=otv) reg = Register(full_reg_offset, full_reg_size) _, defs = self.state.kill_and_add_definition(reg, src, uses=data.ret_values_deps) else: defs = set() if self.state.analysis: self.state.analysis.function_calls[data.callsite_codeloc].ret_defns.update(defs) def _handle_Call_base(self, stmt: ailment.Stmt.Call, is_expr: bool = False) -> FunctionCallData: if isinstance(stmt.target, ailment.Expr.Expression): target = self._expr(stmt.target) # pylint:disable=unused-variable func_name = None elif isinstance(stmt.target, str): func_name = stmt.target target = None else: target = stmt.target func_name = None ip = Register(cast(RegisterOffset, self.arch.ip_offset), self.arch.bytes) self.state.kill_definitions(ip) statement = self.block.statements[self.stmt_idx] caller_will_handle_single_ret = True if hasattr(statement, "dst") and statement.dst != stmt.ret_expr: caller_will_handle_single_ret = False data = FunctionCallData( self.state.codeloc, self._function_handler.make_function_codeloc( target, self.state.codeloc, self.state.analysis.model.func_addr ), target, cc=stmt.calling_convention, prototype=stmt.prototype, name=func_name, args_values=[self._expr(arg) for arg in stmt.args] if stmt.args is not None else None, redefine_locals=stmt.args is None and not is_expr, caller_will_handle_single_ret=caller_will_handle_single_ret, ret_atoms={Atom.from_ail_expr(stmt.ret_expr, self.arch)} if stmt.ret_expr is not None else None, ) self._function_handler.handle_function(self.state, data) if hasattr(stmt, "arg_defs"): for arg_def in stmt.arg_defs: arg_def: Definition if arg_def in self.state.all_definitions: self.state.kill_definitions(arg_def.atom) # kill all cc_ops if "cc_op" in self.arch.registers: def killreg(name: str): offset, size = self.arch.registers[name] self.state.kill_definitions(Register(offset, size)) killreg("cc_op") killreg("cc_dep1") killreg("cc_dep2") killreg("cc_ndep") return data def _handle_stmt_Return(self, stmt: ailment.Stmt.Return): # pylint:disable=unused-argument cc = None prototype = None if self.state.analysis.subject.type == SubjectType.Function: cc = self.state.analysis.subject.content.calling_convention prototype = self.state.analysis.subject.content.prototype # import ipdb; ipdb.set_trace() if cc is None: # fall back to the default calling convention cc_cls = default_cc( self.project.arch.name, platform=self.project.simos.name if self.project.simos is not None else None, default=None, ) if cc_cls is None: l.warning("Unknown default calling convention for architecture %s.", self.project.arch.name) cc = None else: cc = cc_cls(self.project.arch) if self._use_callee_saved_regs_at_return and cc is not None: # handle callee-saved registers: add uses for these registers so that the restoration statements are not # considered dead assignments. for reg in self.arch.register_list: if ( reg.general_purpose and reg.name not in cc.CALLER_SAVED_REGS and reg.name not in cc.ARG_REGS and reg.vex_offset not in { self.arch.sp_offset, self.arch.bp_offset, self.arch.ip_offset, } and (isinstance(cc.RETURN_VAL, SimRegArg) and reg.name != cc.RETURN_VAL.reg_name) ): self.state.add_register_use(reg.vex_offset, reg.size) if stmt.ret_exprs: # Handle return expressions for ret_expr in stmt.ret_exprs: self._expr(ret_expr) return # No return expressions are available. # consume registers that are potentially useful # return value if ( cc is not None and prototype is not None and prototype.returnty is not None and not isinstance(prototype.returnty, SimTypeBottom) ): ret_val = cc.return_val(prototype.returnty) if isinstance(ret_val, SimRegArg): if ret_val.clear_entire_reg: offset, size = cc.arch.registers[ret_val.reg_name] else: offset = cc.arch.registers[ret_val.reg_name][0] + ret_val.reg_offset size = ret_val.size self.state.add_register_use(offset, size) else: l.error("Cannot handle CC with non-register return value location") # base pointer # TODO: Check if the stack base pointer is used as a stack base pointer in this function or not self.state.add_register_use(self.project.arch.bp_offset, self.project.arch.bytes) # We don't add sp since stack pointers are supposed to be get rid of in AIL. this is definitely a hack though # self.state.add_use(Register(self.project.arch.sp_offset, self.project.arch.bits // 8)) def _handle_stmt_DirtyStatement(self, stmt: ailment.Stmt.DirtyStatement): self._expr(stmt.dirty) # # AIL expression handlers # def _handle_expr_Tmp(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: self.state.add_tmp_use(expr.tmp_idx) try: return self.tmps[expr.tmp_idx] except KeyError: return self._top(expr.bits) def _handle_expr_Call(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: data = self._handle_Call_base(expr, is_expr=True) result = data.ret_values if result is None: return self._top(expr.bits) # truncate result if needed if len(result) > expr.bits: assert mv_is_bv(result) result = cast( MultiValues[claripy.ast.BV | claripy.ast.FP], result.extract((len(result) - expr.bits) // 8, expr.bits // 8, "Iend_BE"), ) if data.ret_values_deps is not None: for dep in data.ret_values_deps: result = self.state.annotate_mv_with_def(result, dep) return result def _handle_expr_Register(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: self.state: ReachingDefinitionsState reg_offset = expr.reg_offset size = expr.size # bits = size * 8 # Special handling for SP and BP if self._stack_pointer_tracker is not None: if reg_offset == self.arch.sp_offset: sb_offset = self._stack_pointer_tracker.offset_before(self.ins_addr, self.arch.sp_offset) if sb_offset is not None: return MultiValues(v=self.state._initial_stack_pointer() + sb_offset) elif reg_offset == self.arch.bp_offset and not self.bp_as_gpr: sb_offset = self._stack_pointer_tracker.offset_before(self.ins_addr, self.arch.bp_offset) if sb_offset is not None: return MultiValues(v=self.state._initial_stack_pointer() + sb_offset) reg_atom = Register(RegisterOffset(reg_offset), size) # first check if it is ever defined try: value: MultiValues = self.state.registers.load(reg_offset, size=size) except SimMemoryMissingError as ex: # the full value does not exist, but we handle partial existence, too missing_defs = None if ex.missing_size != size: existing_values = [] i = 0 while i < size: try: value: MultiValues = self.state.registers.load(reg_offset + i, size=1) except SimMemoryMissingError as ex_: i += ex_.missing_size continue i += 1 existing_values.append(value) # extract existing definitions for existing_value in existing_values: for vs in existing_value.values(): for v in vs: if missing_defs is None: missing_defs = self.state.extract_defs(v) else: missing_defs = chain(missing_defs, self.state.extract_defs(v)) if missing_defs is not None: self.state.add_register_use_by_defs(missing_defs, expr=expr) top = self.state.top(size * self.state.arch.byte_width) # annotate it extloc = self._external_codeloc() top = self.state.annotate_with_def(top, Definition(reg_atom, extloc)) value = MultiValues(top) # write it back self.state.kill_and_add_definition(reg_atom, value, override_codeloc=extloc) # extract Definitions defs: Iterable[Definition] | None = None for vs in value.values(): for v in vs: defs = self.state.extract_defs(v) if defs is None else chain(defs, self.state.extract_defs(v)) if defs is None: # define it right away as an external dependency self.state.kill_and_add_definition(reg_atom, value, override_codeloc=self._external_codeloc()) else: self.state.add_register_use_by_defs(defs, expr=expr) return value def _handle_expr_Load(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: addrs = self._expr_bv(expr.addr) size = expr.size bits = expr.bits if expr.guard is not None: assert expr.alt is not None self._expr(expr.guard) self._expr(expr.alt) # convert addrs from MultiValues to a list of valid addresses if addrs.count() == 1: addrs_v = next(iter(addrs.values())) else: top = self.state.top(bits) # annotate it extloc = self._external_codeloc() dummy_atom = MemoryLocation(0, size, endness=expr.endness) def_ = Definition(dummy_atom, extloc) top = self.state.annotate_with_def(top, def_) # add use self.state.add_memory_use_by_def(def_, expr=expr) return MultiValues(top) result: MultiValues | None = None for addr in addrs_v: if not isinstance(addr, claripy.ast.Base): continue if addr.concrete: # a concrete address concrete_addr: int = addr.concrete_value try: vs: MultiValues = self.state.memory.load(concrete_addr, size=size, endness=expr.endness) defs = set(LiveDefinitions.extract_defs_from_mv(vs)) except SimMemoryMissingError: continue self.state.add_memory_use_by_defs(defs, expr=expr) result = result.merge(vs) if result is not None else vs elif self.state.is_stack_address(addr): stack_offset = self.state.get_stack_offset(addr) if stack_offset is not None: stack_addr = self.state.live_definitions.stack_offset_to_stack_addr(stack_offset) try: vs: MultiValues = self.state.stack.load(stack_addr, size=size, endness=expr.endness) defs = set(LiveDefinitions.extract_defs_from_mv(vs)) except SimMemoryMissingError: continue # XXX should be add_stack_use_by_defs? self.state.add_memory_use_by_defs(defs, expr=expr) result = result.merge(vs) if result is not None else vs else: # XXX does ail not support heap tracking? l.debug("Memory address %r undefined or unsupported at pc %#x.", addr, self.ins_addr) if result is None: top = self.state.top(bits) # TODO: Annotate top with a definition result = MultiValues(top) return result def _handle_expr_Convert(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: to_conv: MultiValues = self._expr(expr.operand) bits = expr.to_bits size = bits // self.arch.byte_width if ( to_conv.count() == 1 and 0 in to_conv and expr.from_type == ailment.Expr.Convert.TYPE_INT and expr.to_type == ailment.Expr.Convert.TYPE_INT ): values = to_conv[0] else: top = self.state.top(expr.to_bits) # annotate it dummy_atom = MemoryLocation(0, size, endness=self.arch.memory_endness) def_ = Definition(dummy_atom, self._external_codeloc()) top = self.state.annotate_with_def(top, def_) # add use self.state.add_memory_use_by_def(def_, expr=expr) return MultiValues(top) converted = set() for v in values: if expr.to_bits < expr.from_bits: conv = v[expr.to_bits - 1 : 0] elif expr.to_bits > expr.from_bits: conv = claripy.ZeroExt(expr.to_bits - expr.from_bits, v) else: conv = v converted.add(conv) return MultiValues(offset_to_values={0: converted}) def _handle_expr_Reinterpret(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: _: MultiValues = self._expr(expr.operand) bits = expr.to_bits # we currently do not support floating-point operations. therefore, we return TOP directly reinterpreted = self.state.top(bits) return MultiValues(reinterpreted) def _handle_unop_Default(self, expr): return self._top(expr.bits) _handle_unop_Reference = _handle_unop_Default _handle_unop_Clz = _handle_unop_Default _handle_unop_Ctz = _handle_unop_Default _handle_unop_Dereference = _handle_unop_Default _handle_unop_GetMSBs = _handle_unop_Default _handle_unop_unpack = _handle_unop_Default _handle_unop_Sqrt = _handle_unop_Default _handle_unop_RSqrtEst = _handle_unop_Default def _handle_expr_ITE(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: _: MultiValues = self._expr(expr.cond) iftrue: MultiValues = self._expr(expr.iftrue) _: MultiValues = self._expr(expr.iffalse) top = self.state.top(len(iftrue)) return MultiValues(top) def _handle_unop_Not(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: operand = self._expr_bv(expr.operand) bits = expr.bits operand_v = operand.one_value() if operand_v is not None and operand_v.concrete: return MultiValues(~operand_v) # pylint:disable=invalid-unary-operand-type return MultiValues(self.state.top(bits)) def _handle_unop_Neg(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: operand: MultiValues = self._expr(expr.operand) bits = expr.bits operand_v = operand.one_value() if operand_v is not None and operand_v.concrete: return MultiValues(-operand_v) # pylint:disable=invalid-unary-operand-type return MultiValues(self.state.top(bits)) def _handle_unop_BitwiseNeg(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: operand = self._expr_bv(expr.operand) bits = expr.bits operand_v = operand.one_value() if operand_v is not None and operand_v.concrete: return MultiValues(offset_to_values={0: {~operand_v}}) return MultiValues(offset_to_values={0: {self.state.top(bits)}}) def _handle_binop_Add(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: expr0, expr1 = self._expr_pair(expr.operands[0], expr.operands[1]) bits = expr.bits r: MultiValues[claripy.ast.BV | claripy.ast.FP] | None = None expr0_v = expr0.one_value() expr1_v = expr1.one_value() if expr0_v is not None and expr1_v is not None: # adding two single values together if (expr0_v.concrete or self.state.is_stack_address(expr0_v)) and expr1_v.concrete: r = MultiValues(expr0_v + expr1_v) # type: ignore elif expr0_v is None and expr1_v is not None: # adding a single value to a multivalue if ( expr0.count() == 1 and 0 in expr0 and all(v.concrete or self.state.is_stack_address(v) for v in expr0[0]) ): vs = {v + expr1_v for v in expr0[0]} # type: ignore r = MultiValues(offset_to_values={0: vs}) elif expr0_v is not None and expr1_v is None: # adding a single value to a multivalue if expr1.count() == 1 and 0 in expr1 and all(v.concrete for v in expr1[0]): vs = {v + expr0_v for v in expr1[0]} # type: ignore r = MultiValues(offset_to_values={0: vs}) else: r = MultiValues(self.state.top(bits)) if r is None: r = MultiValues(self.state.top(bits)) return r def _handle_binop_Sub(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: expr0, expr1 = self._expr_pair(expr.operands[0], expr.operands[1]) bits = expr.bits r: MultiValues[claripy.ast.BV | claripy.ast.FP] | None = None expr0_v = expr0.one_value() expr1_v = expr1.one_value() if expr0_v is not None and expr1_v is not None: if (expr0_v.concrete or self.state.is_stack_address(expr0_v)) and expr1_v.concrete: r = MultiValues(expr0_v - expr1_v) # type: ignore elif expr0_v is None and expr1_v is not None: # subtracting a single value from a multivalue if ( expr0.count() == 1 and 0 in expr0 and all(v.concrete or self.state.is_stack_address(v) for v in expr0[0]) ): vs = {v - expr1_v for v in expr0[0]} # type: ignore r = MultiValues(offset_to_values={0: vs}) elif expr0_v is not None and expr1_v is None: # subtracting a single value from a multivalue if expr1.count() == 1 and 0 in expr1 and all(v.concrete for v in expr1[0]): vs = {expr0_v - v for v in expr1[0]} # type: ignore r = MultiValues(offset_to_values={0: vs}) else: r = MultiValues(self.state.top(bits)) if r is None: r = MultiValues(self.state.top(bits)) return r def _handle_binop_Default(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: arg0, arg1 = expr.operands self._expr(arg0) self._expr(arg1) bits = expr.bits return MultiValues(self.state.top(bits)) _handle_binop_AddV = _handle_binop_Add _handle_binop_Div = _handle_binop_Default _handle_binop_MulV = _handle_binop_Default _handle_binop_MulHiV = _handle_binop_Default _handle_binop_Mod = _handle_binop_Default _handle_binop_AddF = _handle_binop_Default _handle_binop_DivF = _handle_binop_Default _handle_binop_DivV = _handle_binop_Default _handle_binop_MulF = _handle_binop_Default _handle_binop_SubF = _handle_binop_Default _handle_binop_SubV = _handle_binop_Default _handle_binop_InterleaveLOV = _handle_binop_Default _handle_binop_InterleaveHIV = _handle_binop_Default _handle_binop_CasCmpEQ = _handle_binop_Default _handle_binop_CasCmpNE = _handle_binop_Default _handle_binop_ExpCmpNE = _handle_binop_Default _handle_binop_SarNV = _handle_binop_Default _handle_binop_ShrNV = _handle_binop_Default _handle_binop_ShlNV = _handle_binop_Default _handle_binop_CmpEQV = _handle_binop_Default _handle_binop_CmpNEV = _handle_binop_Default _handle_binop_CmpGEV = _handle_binop_Default _handle_binop_CmpGTV = _handle_binop_Default _handle_binop_CmpLEV = _handle_binop_Default _handle_binop_CmpLTV = _handle_binop_Default _handle_binop_MinV = _handle_binop_Default _handle_binop_MaxV = _handle_binop_Default _handle_binop_QAddV = _handle_binop_Default _handle_binop_QNarrowBinV = _handle_binop_Default _handle_binop_PermV = _handle_binop_Default _handle_binop_Set = _handle_binop_Default def _handle_binop_Mul(self, expr): expr0 = self._expr(expr.operands[0]) expr1 = self._expr(expr.operands[1]) bits = expr.bits expr0_v = expr0.one_value() expr1_v = expr1.one_value() if expr0_v is not None and expr1_v is not None and expr0_v.concrete and expr1_v.concrete: r = MultiValues(offset_to_values={0: {expr0_v * expr1_v}}) # type: ignore else: r = MultiValues(offset_to_values={0: {self.state.top(bits)}}) return r def _handle_binop_Mull(self, expr): expr0 = self._expr(expr.operands[0]) expr1 = self._expr(expr.operands[1]) bits = expr.bits expr0_v = expr0.one_value() expr1_v = expr1.one_value() if expr0_v is not None and expr1_v is not None and expr0_v.concrete and expr1_v.concrete: xt = expr.bits // 2 if expr.signed: r = MultiValues( offset_to_values={0: {expr0_v.sign_extend(xt) * expr1_v.sign_extend(xt)}} # type: ignore ) else: r = MultiValues( offset_to_values={0: {expr0_v.zero_extend(xt) * expr1_v.zero_extend(xt)}} # type: ignore ) else: r = MultiValues(offset_to_values={0: {self.state.top(bits)}}) return r def _handle_binop_Shr(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: expr0 = self._expr_bv(expr.operands[0]) expr1 = self._expr_bv(expr.operands[1]) bits = expr.bits r: MultiValues[claripy.ast.BV | claripy.ast.FP] | None = None expr0_v = expr0.one_value() expr1_v = expr1.one_value() if expr0_v is not None and expr1_v is not None: if expr0_v.concrete and expr1_v.concrete: r = MultiValues(claripy.LShR(expr0_v, expr1_v.concrete_value)) elif expr0_v is None and expr1_v is not None: # each value in expr0 >> expr1_v if expr0.count() == 1 and 0 in expr0 and all(v.concrete for v in expr0[0]) and expr1_v.concrete: vs = { (claripy.LShR(v, expr1_v.concrete_value) if v.concrete else self.state.top(bits)) for v in expr0[0] } r = MultiValues(offset_to_values={0: vs}) elif expr0_v is not None and expr1_v is None: # expr0_v >> each value in expr1 if expr1.count() == 1 and 0 in expr1 and all(v.concrete for v in expr1[0]): vs = { (claripy.LShR(expr0_v, v.concrete_value) if v.concrete else self.state.top(bits)) for v in expr1[0] } r = MultiValues(offset_to_values={0: vs}) else: r = MultiValues(self.state.top(bits)) if r is None: r = MultiValues(self.state.top(bits)) return r def _handle_binop_Sar(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: expr0 = self._expr_bv(expr.operands[0]) expr1 = self._expr_bv(expr.operands[1]) bits = expr.bits r: MultiValues[claripy.ast.BV | claripy.ast.FP] | None = None expr0_v = expr0.one_value() expr1_v = expr1.one_value() if expr0_v is not None and expr1_v is not None: if expr0_v.concrete and expr1_v.concrete: r = MultiValues(expr0_v >> expr1_v.concrete_value) elif expr0_v is None and expr1_v is not None: # each value in expr0 >> expr1_v if expr0.count() == 1 and 0 in expr0 and all(v.concrete for v in expr0[0]) and expr1_v.concrete: vs = { (claripy.LShR(v, expr1_v.concrete_value) if v.concrete else self.state.top(bits)) for v in expr0[0] } r = MultiValues(offset_to_values={0: vs}) elif expr0_v is not None and expr1_v is None: # expr0_v >> each value in expr1 if expr1.count() == 1 and 0 in expr1 and all(v.concrete for v in expr1[0]): vs = { (claripy.LShR(expr0_v, v.concrete_value) if v.concrete else self.state.top(bits)) for v in expr1[0] } r = MultiValues(offset_to_values={0: vs}) else: r = MultiValues(self.state.top(bits)) if r is None: r = MultiValues(self.state.top(bits)) return r def _handle_binop_Shl(self, expr): expr0 = self._expr_bv(expr.operands[0]) expr1 = self._expr_bv(expr.operands[1]) bits = expr.bits r: MultiValues[claripy.ast.BV | claripy.ast.FP] | None = None expr0_v = expr0.one_value() expr1_v = expr1.one_value() if expr0_v is not None and expr1_v is not None: if expr0_v.concrete and expr1_v.concrete: r = MultiValues(expr0_v << expr1_v.concrete_value) elif expr0_v is None and expr1_v is not None: # each value in expr0 << expr1_v if expr0.count() == 1 and 0 in expr0 and all(v.concrete for v in expr0[0]) and expr1_v.concrete: vs = {((v << expr1_v.concrete_value) if v.concrete else self.state.top(bits)) for v in expr0[0]} r = MultiValues(offset_to_values={0: vs}) elif expr0_v is not None and expr1_v is None: # expr0_v >> each value in expr1 if expr1.count() == 1 and 0 in expr1 and all(v.concrete for v in expr1[0]): vs = {((expr0_v << v.concrete_value) if v.concrete else self.state.top(bits)) for v in expr1[0]} r = MultiValues(offset_to_values={0: vs}) else: r = MultiValues(self.state.top(bits)) if r is None: r = MultiValues(self.state.top(bits)) return r _handle_binop_Rol = _handle_binop_Default _handle_binop_Ror = _handle_binop_Default def _handle_binop_And(self, expr): expr0 = self._expr_bv(expr.operands[0]) expr1 = self._expr_bv(expr.operands[1]) bits = expr.bits r: MultiValues[claripy.ast.BV | claripy.ast.FP] | None = None expr0_v = expr0.one_value() expr1_v = expr1.one_value() if expr0_v is not None and expr1_v is not None: # special handling for stack alignment if self.state.is_stack_address(expr0_v): r = MultiValues(expr0_v) else: if expr0_v.concrete and expr1_v.concrete: r = MultiValues(expr0_v & expr1_v) elif expr0_v is None and expr1_v is not None: # expr1_v & each value in expr0 if expr0.count() == 1 and 0 in expr0 and all(v.concrete for v in expr0[0]): r = MultiValues(offset_to_values={0: {v & expr1_v for v in expr0[0]}}) elif expr0_v is not None and expr1_v is None: # expr0_v & each value in expr1 if expr1.count() == 1 and 0 in expr1 and all(v.concrete for v in expr1[0]): r = MultiValues(offset_to_values={0: {expr0_v & v for v in expr1[0]}}) else: r = MultiValues(self.state.top(bits)) if r is None: r = MultiValues(self.state.top(bits)) return r def _handle_binop_Or(self, expr): expr0: MultiValues = self._expr(expr.operands[0]) expr1: MultiValues = self._expr(expr.operands[1]) bits = expr.bits r: MultiValues[claripy.ast.BV | claripy.ast.FP] | None = None expr0_v = expr0.one_value() expr1_v = expr1.one_value() if expr0_v is not None and expr1_v is not None: if expr0_v.concrete and expr1_v.concrete: r = MultiValues(expr0_v | expr1_v) elif expr0_v is None and expr1_v is not None: # expr1_v | each value in expr0 if expr0.count() == 1 and 0 in expr0 and all(v.concrete for v in expr0[0]): vs = {v | expr1_v for v in expr0[0]} r = MultiValues(offset_to_values={0: vs}) elif expr0_v is not None and expr1_v is None: # expr0_v | each value in expr1 if expr1.count() == 1 and 0 in expr1 and all(v.concrete for v in expr1[0]): vs = {expr0_v | v for v in expr1[0]} r = MultiValues(offset_to_values={0: vs}) else: r = MultiValues(self.state.top(bits)) if r is None: r = MultiValues(self.state.top(bits)) return r def _handle_binop_LogicalAnd(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: expr0: MultiValues = self._expr(expr.operands[0]) expr1: MultiValues = self._expr(expr.operands[1]) bits = expr.bits expr0_v = expr0.one_value() expr1_v = expr1.one_value() # TODO: can maybe be smarter about this. if we can determine that expr0 is never falsey, we can just return it, # TODO: or if it's always falsey we can return expr1 (did I get this backwards?) if expr0_v is None or expr1_v is None: return MultiValues(self.state.top(bits)) return MultiValues(claripy.If(expr0_v == 0, expr0_v, expr1_v)) def _handle_binop_LogicalOr(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: expr0: MultiValues = self._expr(expr.operands[0]) expr1: MultiValues = self._expr(expr.operands[1]) bits = expr.bits expr0_v = expr0.one_value() expr1_v = expr1.one_value() if expr0_v is None or expr1_v is None: return MultiValues(self.state.top(bits)) return MultiValues(claripy.If(expr0_v != 0, expr0_v, expr1_v)) def _handle_binop_LogicalXor(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: expr0: MultiValues = self._expr(expr.operands[0]) expr1: MultiValues = self._expr(expr.operands[1]) bits = expr.bits expr0_v = expr0.one_value() expr1_v = expr1.one_value() if expr0_v is None or expr1_v is None: return MultiValues(self.state.top(bits)) return MultiValues(claripy.If(expr0_v != 0, expr1_v, expr0_v)) def _handle_binop_Xor(self, expr): expr0: MultiValues = self._expr(expr.operands[0]) expr1: MultiValues = self._expr(expr.operands[1]) bits = expr.bits r: MultiValues[claripy.ast.BV | claripy.ast.FP] | None = None expr0_v = expr0.one_value() expr1_v = expr1.one_value() if expr0_v is not None and expr1_v is not None: if expr0_v.concrete and expr1_v.concrete: r = MultiValues(expr0_v ^ expr1_v) elif expr0_v is None and expr1_v is not None: # expr1_v ^ each value in expr0 if expr0.count() == 1 and 0 in expr0 and all(v.concrete for v in expr0[0]): vs = {v ^ expr1_v for v in expr0[0]} r = MultiValues(offset_to_values={0: vs}) elif expr0_v is not None and expr1_v is None: # expr0_v ^ each value in expr1 if expr1.count() == 1 and 0 in expr1 and all(v.concrete for v in expr1[0]): vs = {expr0_v ^ v for v in expr1[0]} r = MultiValues(offset_to_values={0: vs}) else: r = MultiValues(self.state.top(bits)) if r is None: r = MultiValues(self.state.top(bits)) return r def _handle_binop_Carry(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: _ = self._expr(expr.operands[0]) _ = self._expr(expr.operands[1]) bits = expr.bits return MultiValues(self.state.top(bits)) def _handle_binop_SCarry(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: _ = self._expr(expr.operands[0]) _ = self._expr(expr.operands[1]) bits = expr.bits return MultiValues(self.state.top(bits)) def _handle_binop_SBorrow(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: _ = self._expr(expr.operands[0]) _ = self._expr(expr.operands[1]) bits = expr.bits return MultiValues(self.state.top(bits)) def _handle_binop_Concat(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: expr0 = self._expr_bv(expr.operands[0]) expr1 = self._expr_bv(expr.operands[1]) bits = expr.bits r: MultiValues[claripy.ast.BV | claripy.ast.FP] | None = None expr0_v = expr0.one_value() expr1_v = expr1.one_value() if expr0_v is not None and expr1_v is not None: if expr0_v.concrete and expr1_v.concrete: r = MultiValues(claripy.Concat(expr0_v, expr1_v)) elif expr0_v is None and expr1_v is not None: # concatenate expr1_v with each value in expr0 if expr0.count() == 1 and 0 in expr0 and all(v.concrete for v in expr0[0]): r = MultiValues(offset_to_values={0: {claripy.Concat(v, expr1_v) for v in expr0[0]}}) elif expr0_v is not None and expr1_v is None: # concatenate expr0_v with each value in expr1 if expr1.count() == 1 and 0 in expr1 and all(v.concrete for v in expr1[0]): r = MultiValues(offset_to_values={0: {claripy.Concat(expr0_v, v) for v in expr1[0]}}) else: r = MultiValues(self.state.top(bits)) if r is None: r = MultiValues(self.state.top(bits)) return r def _handle_binop_Cmp(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: op0 = self._expr(expr.operands[0]) op1 = self._expr(expr.operands[1]) if op0 is None: _ = expr.operands[0] if op1 is None: _ = expr.operands[1] top = self.state.top(expr.bits) return MultiValues(top) _handle_binop_CmpF = _handle_binop_Cmp _handle_binop_CmpEQ = _handle_binop_Cmp _handle_binop_CmpNE = _handle_binop_Cmp _handle_binop_CmpLE = _handle_binop_Cmp _handle_binop_CmpLEs = _handle_binop_Cmp _handle_binop_CmpLT = _handle_binop_Cmp _handle_binop_CmpLTs = _handle_binop_Cmp _handle_binop_CmpGE = _handle_binop_Cmp _handle_binop_CmpGEs = _handle_binop_Cmp _handle_binop_CmpGT = _handle_binop_Cmp _handle_binop_CmpGTs = _handle_binop_Cmp _handle_binop_CmpORD = _handle_binop_Cmp def _handle_binop_ExpCmpNE(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: self._expr(expr.operands[0]) self._expr(expr.operands[1]) return MultiValues(self.state.top(expr.bits)) def _handle_unop_Clz(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: self._expr(expr.operand) return MultiValues(self.state.top(expr.bits)) def _handle_expr_Const(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: self.state.mark_const(expr.value, expr.size) if isinstance(expr.value, float): sort = None if expr.bits == 64: sort = FSORT_DOUBLE elif expr.bits == 32: sort = FSORT_FLOAT return MultiValues(claripy.FPV(expr.value, sort)) return MultiValues(claripy.BVV(expr.value, expr.bits)) def _handle_expr_StackBaseOffset(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: stack_addr = self.state.stack_address(expr.offset) return MultiValues(stack_addr) def _ail_handle_VEXCCallExpression(self, expr: ailment.Expr.VEXCCallExpression) -> MultiValues: for operand in expr.operands: self._expr(operand) top = self.state.top(expr.bits) return MultiValues(top) def _handle_expr_Phi(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: return self._top(expr.bits) # TODO def _handle_expr_VEXCCallExpression(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: return self._top(expr.bits) # TODO def _handle_expr_VirtualVariable(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: return self._top(expr.bits) # TODO def _handle_expr_DirtyExpression(self, expr) -> MultiValues[claripy.ast.BV | claripy.ast.FP]: if isinstance(expr.dirty_expr, ailment.expression.VEXCCallExpression): for operand in expr.dirty_expr.operands: self._expr(operand) return MultiValues(self.state.top(expr.bits)) def _handle_expr_BasePointerOffset(self, expr): return self._top(expr.bits) def _handle_expr_MultiStatementExpression(self, expr): return self._top(expr.bits)