from typing import TYPE_CHECKING
import logging
from cle.backends.elf.variable import Variable
from cle.backends.elf.variable_type import VariableType, PointerType, ArrayType, StructType, TypedefType
from angr.sim_state import SimState
from angr.sim_type import ALL_TYPES, SimTypeReg
from .plugin import SimStatePlugin
l = logging.getLogger(name=__name__)
if TYPE_CHECKING:
from angr.state_plugins.view import SimMemView
from angr.sim_type import SimType
[docs]
class SimDebugVariable:
"""
A SimDebugVariable will get dynamically created when queriyng for variable in a state with the
SimDebugVariablePlugin. It features a link to the state, an address and a type.
"""
[docs]
def __init__(self, state: SimState, addr, var_type: VariableType):
self.state = state
self.addr = addr
self.type = var_type
[docs]
@staticmethod
def from_cle_variable(state: SimState, cle_variable: Variable, dwarf_cfa) -> "SimDebugVariable":
addr = cle_variable.rebased_addr_from_cfa(dwarf_cfa)
var_type = cle_variable.type
return SimDebugVariable(state, addr, var_type)
@property
def mem_untyped(self) -> "SimMemView":
if self.addr is None:
raise Exception("Cannot view a variable without an address")
return self.state.mem[self.addr]
@property
def mem(self) -> "SimMemView":
if isinstance(self.type, TypedefType):
unpacked = SimDebugVariable(self.state, self.addr, self.type.type)
return unpacked.mem
if self.type is None or self.type.byte_size is None:
return self.mem_untyped
arch = self.state.arch
size = self.type.byte_size * arch.byte_width
name = self.type.name
if name in ALL_TYPES:
sim_type = ALL_TYPES[name].with_arch(arch)
assert size == sim_type.size
else:
# FIXME A lot more types are supported by angr that are not in ALL_TYPES (structs, arrays, pointers)
# Use a fallback type
sim_type = SimTypeReg(size, label=name)
return self.mem_untyped.with_type(sim_type)
# methods and properties equivalent to SimMemView
@property
def string(self) -> "SimMemView":
first_char = self.deref
# first char should have some char type (could be checked here)
return first_char.mem_untyped.string
[docs]
def with_type(self, sim_type: "SimType") -> "SimMemView":
return self.mem_untyped.with_type(sim_type)
@property
def resolvable(self):
return self.mem.resolvable
@property
def resolved(self):
return self.mem.resolved
@property
def concrete(self):
return self.mem.concrete
[docs]
def store(self, value):
return self.mem.store(value)
def __getitem__(self, i):
if isinstance(i, int):
return self.array(i)
elif isinstance(i, str):
return self.member(i)
raise KeyError
@property
def deref(self) -> "SimDebugVariable":
# dereferincing is equivalent to getting the first array element
return self.array(0)
[docs]
def array(self, i) -> "SimDebugVariable":
if isinstance(self.type, TypedefType):
unpacked = SimDebugVariable(self.state, self.addr, self.type.type)
return unpacked.array(i)
elif isinstance(self.type, ArrayType):
# an array already addresses its first element
addr = self.addr
el_type = self.type.element_type
elif isinstance(self.type, PointerType):
if self.addr is None:
addr = None
else:
addr = self.state.memory.load(self.addr, self.state.arch.bytes, endness=self.state.arch.memory_endness)
el_type = self.type.referenced_type
else:
raise Exception(f"{self.type} object cannot be dereferenced")
if i == 0:
new_addr = addr
elif addr is None or el_type is None or el_type.byte_size is None:
new_addr = None
else:
new_addr = addr + i * el_type.byte_size
return SimDebugVariable(self.state, new_addr, el_type)
[docs]
def member(self, member_name: str) -> "SimDebugVariable":
if isinstance(self.type, TypedefType):
unpacked = SimDebugVariable(self.state, self.addr, self.type.type)
return unpacked.member(member_name)
elif isinstance(self.type, StructType):
member = self.type[member_name]
if self.addr is None:
addr = None
else:
addr = self.addr + member.addr_offset
return SimDebugVariable(self.state, addr, member.type)
raise Exception(f"{self.type} object has no members")
[docs]
class SimDebugVariablePlugin(SimStatePlugin):
"""
This is the plugin you'll use to interact with (global/local) program variables.
These variables have a name and a visibility scope which depends on the pc address of the state.
With this plugin, you can access/modify the value of such variable or find its memory address.
For creating program varibles, or for importing them from cle, see the knowledge plugin debug_variables.
Run ``p.kb.dvars.load_from_dwarf()`` before using this plugin.
Example:
>>> p = angr.Project("various_variables", load_debug_info=True)
>>> p.kb.dvars.load_from_dwarf()
>>> state = # navigate to the state you want
>>> state.dvars.get_variable("pointer2").deref.mem
<int (32 bits) <BV32 0x1> at 0x404020>
"""
[docs]
def get_variable(self, var_name: str) -> SimDebugVariable:
"""
Returns the visible variable (if any) with name ``var_name`` based on the current ``state.ip``.
"""
kb = self.state.project.kb
cle_var = kb.dvars[var_name][self.state.ip]
if cle_var:
return SimDebugVariable.from_cle_variable(self.state, cle_var, self.dwarf_cfa)
return None
def __getitem__(self, var_name: str) -> SimDebugVariable:
return self.get_variable(var_name)
# DWARF cfa
@property
def dwarf_cfa(self):
"""
Returns the current cfa computation. Set this property to the correct value if needed.
"""
try:
return self._dwarf_cfa
except AttributeError:
return self.dwarf_cfa_approx
@dwarf_cfa.setter
def dwarf_cfa(self, new_val):
self._dwarf_cfa = new_val
@property
def dwarf_cfa_approx(self):
# FIXME This is only an approximation!
if self.state.arch.name == "AMD64":
return self.state.regs.rbp + 16
elif self.state.arch.name == "X86":
return self.state.regs.ebp + 8
return 0
SimState.register_default("dvars", SimDebugVariablePlugin)