Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions angr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@
from .distributed import Server
from .knowledge_base import KnowledgeBase
from .procedures.definitions import load_external_definitions
from .emulator import Emulator, EmulatorStopReason

# for compatibility reasons
from . import sim_manager as manager
Expand Down Expand Up @@ -259,6 +260,8 @@
"AngrVaultError",
"Blade",
"Block",
"Emulator",
"EmulatorStopReason",
"ExplorationTechnique",
"KnowledgeBase",
"PTChunk",
Expand Down
143 changes: 143 additions & 0 deletions angr/emulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
from __future__ import annotations

import logging
from enum import Enum

from angr.engines.concrete import ConcreteEngine, HeavyConcreteState
from angr.errors import AngrError


log = logging.getLogger(name=__name__)


class EmulatorException(AngrError):
"""Base class for exceptions raised by the Emulator."""


class EngineException(EmulatorException):
"""Exception raised when the emulator encounters an unhandlable error in the engine."""


class StateDivergedException(EmulatorException):
"""Exception raised when an engine returns multiple successors."""


class EmulatorStopReason(Enum):
"""
Enum representing the reason for stopping the emulator.
"""

INSTRUCTION_LIMIT = "instruction_limit"
BREAKPOINT = "breakpoint"
NO_SUCCESSORS = "no_successors"
MEMORY_ERROR = "memory_error"
FAILURE = "failure"
EXIT = "exit"


class Emulator:
"""
Emulator is a utility that adapts an angr `ConcreteEngine` to a more
user-friendly interface for concrete execution. It only supports concrete
execution and requires a ConcreteEngine.

Saftey: This class is not thread-safe. It should only be used in a
single-threaded context. It can be safely shared between multiple threads,
provided that only one thread is using it at a time.
"""

_engine: ConcreteEngine
_state: HeavyConcreteState

def __init__(self, engine: ConcreteEngine, init_state: HeavyConcreteState):
"""
:param engine: The `ConcreteEngine` to use for emulation.
:param init_state: The initial state to use for emulation.
"""
self._engine = engine
self._state = init_state

@property
def state(self) -> HeavyConcreteState:
"""
The current state of the emulator.
"""
return self._state

@property
def breakpoints(self) -> set[int]:
"""
The set of currently set breakpoints.
"""
return self._engine.get_breakpoints()

def add_breakpoint(self, addr: int) -> None:
"""
Add a breakpoint at the given address.

:param addr: The address to set the breakpoint at.
"""
self._engine.add_breakpoint(addr)

def remove_breakpoint(self, addr: int) -> None:
"""
Remove a breakpoint at the given address, if present.

:param addr: The address to remove the breakpoint from.
"""
self._engine.remove_breakpoint(addr)

def run(self, num_inst: int | None = None) -> EmulatorStopReason:
"""
Execute the emulator.
"""
completed_engine_execs = 0
num_inst_executed: int = 0
while self._state.history.jumpkind != "Ijk_Exit":
# Check if there is a breakpoint at the current address
if completed_engine_execs > 0 and self._state.addr in self._engine.get_breakpoints():
return EmulatorStopReason.BREAKPOINT

# Check if we've already executed the requested number of instructions
if num_inst is not None and num_inst_executed >= num_inst:
return EmulatorStopReason.INSTRUCTION_LIMIT

# Calculate remaining instructions for this engine execution
remaining_inst: int | None = None
if num_inst is not None:
remaining_inst = num_inst - num_inst_executed

# Run the engine to get successors
try:
successors = self._engine.process(self._state, num_inst=remaining_inst)
except EngineException as e:
raise EngineException(f"Engine encountered an error: {e}") from e

# Handle cases with an unexpected number of successors
if len(successors.successors) == 0:
return EmulatorStopReason.NO_SUCCESSORS
if len(successors.successors) > 1:
log.warning("Concrete engine returned multiple successors")

# Set the state before raising further exceptions
self._state = successors.successors[0]

# Track the number of instructions executed using the state's history
if self._state.history.recent_instruction_count > 0:
num_inst_executed += self._state.history.recent_instruction_count

if successors.successors[0].history.jumpkind == "Ijk_SigSEGV":
return EmulatorStopReason.MEMORY_ERROR

completed_engine_execs += 1

return EmulatorStopReason.EXIT


__all__ = (
"Emulator",
"EmulatorException",
"EmulatorStopReason",
"EngineException",
"StateDivergedException",
)
66 changes: 66 additions & 0 deletions angr/engines/concrete.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from __future__ import annotations

import logging
import typing
from abc import abstractmethod, ABCMeta
from typing_extensions import override

import claripy
from angr.engines.successors import SimSuccessors, SuccessorsEngine
from angr.sim_state import SimState

log = logging.getLogger(__name__)


HeavyConcreteState = SimState[int, int]


class ConcreteEngine(SuccessorsEngine, metaclass=ABCMeta):
"""
ConcreteEngine extends SuccessorsEngine and adds APIs for managing breakpoints.
"""

@abstractmethod
def get_breakpoints(self) -> set[int]:
"""Return the set of currently set breakpoints."""

@abstractmethod
def add_breakpoint(self, addr: int) -> None:
"""Add a breakpoint at the given address."""

@abstractmethod
def remove_breakpoint(self, addr: int) -> None:
"""Remove a breakpoint at the given address, if present."""

@abstractmethod
def process_concrete(self, state: HeavyConcreteState, num_inst: int | None = None) -> HeavyConcreteState:
"""
Process the concrete state and return a HeavyState object.

:param state: The concrete state to process.
:return: A HeavyState object representing the processed state.
"""

@override
def process_successors(
self, successors: SimSuccessors, *, num_inst: int | None = None, **kwargs: dict[str, typing.Any]
):
if len(kwargs) > 0:
log.warning("ConcreteEngine.process_successors received unknown kwargs: %s", kwargs)

# TODO: Properly error here when the state is not a HeavyConcreteState
# Alternatively, we could make SimSuccessors generic over the state type too
concrete_state = typing.cast(HeavyConcreteState, self.state)

concrete_successor = self.process_concrete(concrete_state, num_inst=num_inst)
successors.add_successor(
concrete_successor,
concrete_successor.ip,
claripy.true(),
concrete_successor.history.jumpkind,
add_guard=False,
)
successors.processed = True


__all__ = ["ConcreteEngine", "HeavyConcreteState"]
Loading
Loading