# coding=utf-8
# Copyright (c) 2015-2024 Vector 35 Inc
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
import ctypes
from typing import List, Generator, Optional, Union, Set, Dict, Tuple
from dataclasses import dataclass
import binaryninja
from . import _binaryninjacore as core
from . import databuffer
from . import decorators
from .enums import RegisterValueType, VariableSourceType, DeadStoreElimination, FunctionGraphType, BuiltinType
FunctionOrILFunction = Union["binaryninja.function.Function", "binaryninja.lowlevelil.LowLevelILFunction",
"binaryninja.mediumlevelil.MediumLevelILFunction",
"binaryninja.highlevelil.HighLevelILFunction"]
[docs]
@dataclass(frozen=True)
class LookupTableEntry:
from_values: List[int]
to_value: int
type: RegisterValueType = RegisterValueType.LookupTableValue
def __repr__(self):
return f"[{', '.join([f'{i:#x}' for i in self.from_values])}] -> {self.to_value:#x}"
[docs]
@dataclass(frozen=True)
class RegisterValue:
value: int
offset: int
type: RegisterValueType = RegisterValueType.UndeterminedValue
confidence: int = core.max_confidence
size: int = 0
def _to_core_struct(self) -> core.BNRegisterValue:
result = core.BNRegisterValue()
result.state = self.type
result.value = self.value
result.offset = self.offset
result.size = self.size
return result
def _to_core_struct_with_confidence(self):
result = core.BNRegisterValueWithConfidence()
result.value = self._to_core_struct()
result.confidence = self.confidence
return result
def __bool__(self):
return self.value != 0
def __int__(self):
return self.value
def __eq__(self, other):
if isinstance(other, int):
return int(self) == other
elif isinstance(other, bool):
return bool(self) == other
elif isinstance(other, self.__class__):
return (self.type, self.offset, self.type,
self.confidence) == (other.type, other.offset, other.type, other.confidence)
assert False, f"no comparison for types {repr(self)} and {repr(other)}"
[docs]
@classmethod
def from_BNRegisterValue(
cls, reg_value: Union[core.BNRegisterValue, core.BNRegisterValueWithConfidence],
arch: Optional['binaryninja.architecture.Architecture'] = None
) -> 'RegisterValue':
confidence = core.max_confidence
if isinstance(reg_value, core.BNRegisterValueWithConfidence):
confidence = reg_value.confidence
reg_value = reg_value.value
if reg_value.state == RegisterValueType.EntryValue:
reg = None
if arch is not None:
reg = arch.get_reg_name(binaryninja.architecture.RegisterIndex(reg_value.value))
return EntryRegisterValue(reg_value.value, reg=reg, confidence=confidence)
elif reg_value.state == RegisterValueType.ConstantValue:
return ConstantRegisterValue(reg_value.value, confidence=confidence)
elif reg_value.state == RegisterValueType.ConstantPointerValue:
return ConstantPointerRegisterValue(reg_value.value, confidence=confidence)
elif reg_value.state == RegisterValueType.StackFrameOffset:
return StackFrameOffsetRegisterValue(reg_value.value, confidence=confidence)
elif reg_value.state == RegisterValueType.ImportedAddressValue:
return ImportedAddressRegisterValue(reg_value.value, confidence=confidence)
elif reg_value.state == RegisterValueType.UndeterminedValue:
return Undetermined()
elif reg_value.state == RegisterValueType.ReturnAddressValue:
return ReturnAddressRegisterValue(reg_value.value, confidence=confidence)
elif reg_value.state == RegisterValueType.ExternalPointerValue:
return ExternalPointerRegisterValue(reg_value.value, reg_value.offset, confidence=confidence)
elif reg_value.state & RegisterValueType.ConstantDataValue == RegisterValueType.ConstantDataValue:
return ConstantDataRegisterValue(reg_value.value, 0, RegisterValueType(reg_value.state), confidence=confidence, size=reg_value.size)
assert False, f"RegisterValueType {reg_value.state} not handled"
[docs]
@classmethod
def to_BNRegisterValue(cls, reg_value: 'RegisterValue') -> core.BNRegisterValue:
return reg_value._to_core_struct()
[docs]
@dataclass(frozen=True, eq=False)
class Undetermined(RegisterValue):
value: int = 0
offset: int = 0
type: RegisterValueType = RegisterValueType.UndeterminedValue
def __repr__(self):
return "<undetermined>"
[docs]
@dataclass(frozen=True, eq=False)
class ConstantRegisterValue(RegisterValue):
offset: int = 0
type: RegisterValueType = RegisterValueType.ConstantValue
def __repr__(self):
return f"<const {self.value:#x}>"
[docs]
@dataclass(frozen=True, eq=False)
class ConstantPointerRegisterValue(RegisterValue):
offset: int = 0
type: RegisterValueType = RegisterValueType.ConstantPointerValue
def __repr__(self):
return f"<const ptr {self.value:#x}>"
[docs]
@dataclass(frozen=True, eq=False)
class ImportedAddressRegisterValue(RegisterValue):
offset: int = 0
type: RegisterValueType = RegisterValueType.ImportedAddressValue
def __repr__(self):
return f"<imported address from entry {self.value:#x}>"
[docs]
@dataclass(frozen=True, eq=False)
class ReturnAddressRegisterValue(RegisterValue):
offset: int = 0
type: RegisterValueType = RegisterValueType.ReturnAddressValue
def __repr__(self):
return "<return address>"
[docs]
@dataclass(frozen=True, eq=False)
class EntryRegisterValue(RegisterValue):
value: int = 0
offset: int = 0
type: RegisterValueType = RegisterValueType.EntryValue
reg: Optional['binaryninja.architecture.RegisterName'] = None
def __repr__(self):
if self.reg is not None:
return f"<entry {self.reg}>"
return f"<entry {self.value}>"
[docs]
@dataclass(frozen=True, eq=False)
class StackFrameOffsetRegisterValue(RegisterValue):
offset: int = 0
type: RegisterValueType = RegisterValueType.StackFrameOffset
def __repr__(self):
return f"<stack frame offset {self.value:#x}>"
[docs]
@dataclass(frozen=True, eq=False)
class ExternalPointerRegisterValue(RegisterValue):
type: RegisterValueType = RegisterValueType.ExternalPointerValue
def __repr__(self):
return f"<external {self.value:#x} + offset {self.offset:#x}>"
[docs]
@dataclass(frozen=True, eq=False)
class ConstantDataRegisterValue(RegisterValue):
def __repr__(self):
if self.type == RegisterValueType.ConstantDataZeroExtendValue:
return f"<const data {{zx.{self.size}({self.value:#x})}}>"
if self.type == RegisterValueType.ConstantDataSignExtendValue:
return f"<const data {{sx.{self.size}({self.value:#x})}}>"
if self.type == RegisterValueType.ConstantDataAggregateValue:
return f"<const data {{aggregate.{self.size}}} @ {self.value:#x}>"
return f"<const data {{invalid}} {self.type} {self.value:#x}>"
[docs]
@dataclass(frozen=True, eq=False)
class ConstantData(RegisterValue):
function: '_function.Function' = None
def __repr__(self):
if self.type == RegisterValueType.ConstantDataZeroExtendValue:
return f"<{self.__class__.__name__}: {{zx.{self.size}({self.value:#x})}}>"
if self.type == RegisterValueType.ConstantDataSignExtendValue:
return f"<{self.__class__.__name__}: {{sx.{self.size}({self.value:#x})}}>"
if self.type == RegisterValueType.ConstantDataAggregateValue:
return f"<{self.__class__.__name__}: {{aggregate.{self.size}}} @ {self.value:#x}>"
return f"<{self.__class__.__name__}: {{invalid}} {self.type} {self.value:#x}>"
@property
def data(self) -> databuffer.DataBuffer:
if self.function is None:
raise ValueError(f"ConstantData requires a Function instance: {self.size}")
return self.function.get_constant_data(self.type, self.value, self.size)
@property
def data_and_builtin(self) -> Tuple[databuffer.DataBuffer, BuiltinType]:
if self.function is None:
raise ValueError(f"ConstantData requires a Function instance: {self.size}")
return self.function.get_constant_data_and_builtin(self.type, self.value, self.size)
[docs]
@dataclass(frozen=True)
class ValueRange:
start: int
end: int
step: int
def __repr__(self):
if self.step == 1:
return f"<range: {self.start:#x} to {self.end:#x}>"
return f"<range: {self.start:#x} to {self.end:#x}, step {self.step:#x}>"
def __contains__(self, other):
if not isinstance(other, int):
return NotImplemented
return other in range(self.start, self.end, self.step)
[docs]
@decorators.passive
class PossibleValueSet:
"""
`class PossibleValueSet` PossibleValueSet is used to define possible values
that a variable can take. It contains methods to instantiate different
value sets such as Constant, Signed/Unsigned Ranges, etc.
"""
def __init__(self, arch=None, value=None):
if value is None:
self._type = RegisterValueType.UndeterminedValue
return
self._type = RegisterValueType(value.state)
if value.state == RegisterValueType.EntryValue:
if arch is None:
self._reg = value.value
else:
self._reg = arch.get_reg_name(value.value)
elif value.state == RegisterValueType.ConstantValue:
self._value = value.value
elif value.state == RegisterValueType.ConstantPointerValue:
self._value = value.value
elif value.state == RegisterValueType.StackFrameOffset:
self._offset = value.value
elif value.state & RegisterValueType.ConstantDataValue == RegisterValueType.ConstantDataValue:
self._value = value.value
self._size = value.size
elif value.state == RegisterValueType.SignedRangeValue:
self._offset = value.value
self._ranges = []
for i in range(0, value.count):
start = value.ranges[i].start
end = value.ranges[i].end
step = value.ranges[i].step
if start & (1 << 63):
start |= ~((1 << 63) - 1)
if end & (1 << 63):
end |= ~((1 << 63) - 1)
self._ranges.append(ValueRange(start, end, step))
elif value.state == RegisterValueType.UnsignedRangeValue:
self._offset = value.value
self._ranges = []
for i in range(0, value.count):
start = value.ranges[i].start
end = value.ranges[i].end
step = value.ranges[i].step
self._ranges.append(ValueRange(start, end, step))
elif value.state == RegisterValueType.LookupTableValue:
self._table = []
self._mapping = {}
for i in range(0, value.count):
from_list = []
for j in range(0, value.table[i].fromCount):
from_list.append(value.table[i].fromValues[j])
self._mapping[value.table[i].fromValues[j]] = value.table[i].toValue
self._table.append(LookupTableEntry(from_list, value.table[i].toValue))
elif (value.state == RegisterValueType.InSetOfValues) or (value.state == RegisterValueType.NotInSetOfValues):
self._values = set()
for i in range(0, value.count):
self._values.add(value.valueSet[i])
self._count = value.count
def __repr__(self):
if self._type == RegisterValueType.EntryValue:
return f"<entry {self.reg}>"
if self._type == RegisterValueType.ConstantValue:
return f"<const {self.value:#x}>"
if self._type == RegisterValueType.ConstantPointerValue:
return f"<const ptr {self.value:#x}>"
if self._type == RegisterValueType.StackFrameOffset:
return f"<stack frame offset {self._offset:#x}>"
if self._type == RegisterValueType.ConstantDataZeroExtendValue:
return f"<const data {{zx.{self._size}({self.value:#x})}}>"
if self._type == RegisterValueType.ConstantDataSignExtendValue:
return f"<const data {{sx.{self._size}({self.value:#x})}}>"
if self._type == RegisterValueType.ConstantDataAggregateValue:
return f"<const data {{aggregate.{self._size}}} @ {self.value:#x}>"
if self._type == RegisterValueType.SignedRangeValue:
return f"<signed ranges: {repr(self.ranges)}>"
if self._type == RegisterValueType.UnsignedRangeValue:
return f"<unsigned ranges: {repr(self.ranges)}>"
if self._type == RegisterValueType.LookupTableValue:
return f"<table: {', '.join([repr(i) for i in self.table])}>"
if self._type == RegisterValueType.InSetOfValues:
return f"<in set([{', '.join(hex(i) for i in sorted(self.values))}])>"
if self._type == RegisterValueType.NotInSetOfValues:
return f"<not in set([{', '.join(hex(i) for i in sorted(self.values))}])>"
if self._type == RegisterValueType.ReturnAddressValue:
return "<return address>"
return "<undetermined>"
def __contains__(self, other):
if self.type in [RegisterValueType.ConstantValue, RegisterValueType.ConstantPointerValue
] and isinstance(other, int):
return self.value == other
if self.type in [RegisterValueType.ConstantValue, RegisterValueType.ConstantPointerValue
] and hasattr(other, "value"):
return self.value == other.value
if not isinstance(other, int):
return NotImplemented
#Initial implementation only checks numbers, no set logic
if self.type == RegisterValueType.StackFrameOffset:
return NotImplemented
if self.type in [RegisterValueType.SignedRangeValue, RegisterValueType.UnsignedRangeValue]:
for rng in self.ranges:
if other in rng:
return True
return False
if self.type == RegisterValueType.InSetOfValues:
return other in self.values
if self.type == RegisterValueType.NotInSetOfValues:
return not other in self.values
return NotImplemented
def __eq__(self, other):
if self.type in [RegisterValueType.ConstantValue, RegisterValueType.ConstantPointerValue
] and isinstance(other, int):
return self.value == other
if not isinstance(other, self.__class__):
return NotImplemented
if self.type in [RegisterValueType.ConstantValue, RegisterValueType.ConstantPointerValue]:
return self.value == other.value
elif self.type == RegisterValueType.StackFrameOffset:
return self.offset == other.offset
elif self.type & RegisterValueType.ConstantDataValue == RegisterValueType.ConstantDataValue:
return self.value == other.value and self._size == other._size
elif self.type in [RegisterValueType.SignedRangeValue, RegisterValueType.UnsignedRangeValue]:
return self.ranges == other.ranges
elif self.type in [RegisterValueType.InSetOfValues, RegisterValueType.NotInSetOfValues]:
return self.values == other.values
elif self.type == RegisterValueType.UndeterminedValue and hasattr(other, 'type'):
return self.type == other.type
else:
return self == other
def __ne__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return not (self == other)
def _to_core_struct(self) -> core.BNPossibleValueSet:
result = core.BNPossibleValueSet()
result.state = RegisterValueType(self.type)
if self.type == RegisterValueType.UndeterminedValue:
return result
elif self.type == RegisterValueType.ConstantValue:
result.value = self.value
elif self.type == RegisterValueType.ConstantPointerValue:
result.value = self.value
elif self.type == RegisterValueType.StackFrameOffset:
result.offset = self.value
elif self.type & RegisterValueType.ConstantDataValue == RegisterValueType.ConstantDataValue:
result.value = self.value
result.size = self.size
elif self.type == RegisterValueType.SignedRangeValue:
result.offset = self.value
result.ranges = (core.BNValueRange * self.count)()
for i in range(0, self.count):
start = self.ranges[i].start
end = self.ranges[i].end
if start & (1 << 63):
start |= ~((1 << 63) - 1)
if end & (1 << 63):
end |= ~((1 << 63) - 1)
value_range = core.BNValueRange()
value_range.start = start
value_range.end = end
value_range.step = self.ranges[i].step
result.ranges[i] = value_range
result.count = self.count
elif self.type == RegisterValueType.UnsignedRangeValue:
result.offset = self.value
result.ranges = (core.BNValueRange * self.count)()
for i in range(0, self.count):
value_range = core.BNValueRange()
value_range.start = self.ranges[i].start
value_range.end = self.ranges[i].end
value_range.step = self.ranges[i].step
result.ranges[i] = value_range
result.count = self.count
elif self.type == RegisterValueType.LookupTableValue:
result.table = []
result.mapping = {}
for i in range(self.count):
from_list = []
for j in range(0, len(self.table[i].from_values)):
from_list.append(self.table[i].from_values[j])
result.mapping[self.table[i].from_values[j]] = result.table[i].to_value
result.table.append(LookupTableEntry(from_list, result.table[i].to_value))
result.count = self.count
elif (self.type == RegisterValueType.InSetOfValues) or (self.type == RegisterValueType.NotInSetOfValues):
values = (ctypes.c_longlong * self.count)()
i = 0
for value in self.values:
values[i] = value
i += 1
int_ptr = ctypes.POINTER(ctypes.c_longlong)
result.valueSet = ctypes.cast(values, int_ptr)
result.count = self.count
return result
@property
def type(self) -> RegisterValueType:
return self._type
@property
def reg(self) -> 'binaryninja.architecture.RegisterName':
return self._reg
@property
def value(self) -> int:
return self._value
@property
def offset(self) -> int:
return self._offset
@property
def size(self) -> int:
return self._size
@property
def ranges(self) -> List[ValueRange]:
return self._ranges
@property
def table(self) -> List[LookupTableEntry]:
return self._table
@property
def mapping(self) -> Dict[int, int]:
return self._mapping
@property
def values(self) -> Set[int]:
return self._values
@property
def count(self) -> int:
return self._count
[docs]
@staticmethod
def undetermined() -> 'PossibleValueSet':
"""
Create a PossibleValueSet object of type UndeterminedValue.
:return: PossibleValueSet object of type UndeterminedValue
:rtype: PossibleValueSet
"""
return PossibleValueSet()
[docs]
@staticmethod
def constant(value: int) -> 'PossibleValueSet':
"""
Create a constant valued PossibleValueSet object.
:param int value: Integer value of the constant
:rtype: PossibleValueSet
"""
result = PossibleValueSet()
result._type = RegisterValueType.ConstantValue
result._value = value
return result
[docs]
@staticmethod
def constant_ptr(value: int) -> 'PossibleValueSet':
"""
Create constant pointer valued PossibleValueSet object.
:param int value: Integer value of the constant pointer
:rtype: PossibleValueSet
"""
result = PossibleValueSet()
result._type = RegisterValueType.ConstantPointerValue
result._value = value
return result
[docs]
@staticmethod
def stack_frame_offset(offset: int) -> 'PossibleValueSet':
"""
Create a PossibleValueSet object for a stack frame offset.
:param int offset: Integer value of the offset
:rtype: PossibleValueSet
"""
result = PossibleValueSet()
result._type = RegisterValueType.StackFrameOffset
result._offset = offset
return result
[docs]
@staticmethod
def signed_range_value(ranges: List[ValueRange]) -> 'PossibleValueSet':
"""
Create a PossibleValueSet object for a signed range of values.
:param list(ValueRange) ranges: List of ValueRanges
:rtype: PossibleValueSet
:Example:
>>> v_1 = ValueRange(-5, -1, 1)
>>> v_2 = ValueRange(7, 10, 1)
>>> val = PossibleValueSet.signed_range_value([v_1, v_2])
<signed ranges: [<range: -0x5 to -0x1>, <range: 0x7 to 0xa>]>
"""
result = PossibleValueSet()
result._value = 0
result._type = RegisterValueType.SignedRangeValue
result._ranges = ranges
result._count = len(ranges)
return result
[docs]
@staticmethod
def unsigned_range_value(ranges: List[ValueRange]) -> 'PossibleValueSet':
"""
Create a PossibleValueSet object for a unsigned signed range of values.
:param list(ValueRange) ranges: List of ValueRanges
:rtype: PossibleValueSet
:Example:
>>> v_1 = ValueRange(0, 5, 1)
>>> v_2 = ValueRange(7, 10, 1)
>>> val = PossibleValueSet.unsigned_range_value([v_1, v_2])
<unsigned ranges: [<range: 0x0 to 0x5>, <range: 0x7 to 0xa>]>
"""
result = PossibleValueSet()
result._value = 0
result._type = RegisterValueType.UnsignedRangeValue
result._ranges = ranges
result._count = len(ranges)
return result
[docs]
@staticmethod
def in_set_of_values(values: Union[List[int], Set[int]]) -> 'PossibleValueSet':
"""
Create a PossibleValueSet object for a value in a set of values.
:param list(int) values: List of integer values
:rtype: PossibleValueSet
"""
result = PossibleValueSet()
result._type = RegisterValueType.InSetOfValues
result._values = set(values)
result._count = len(values)
return result
[docs]
@staticmethod
def not_in_set_of_values(values) -> 'PossibleValueSet':
"""
Create a PossibleValueSet object for a value NOT in a set of values.
:param list(int) values: List of integer values
:rtype: PossibleValueSet
"""
result = PossibleValueSet()
result._type = RegisterValueType.NotInSetOfValues
result._values = set(values)
result._count = len(values)
return result
[docs]
@staticmethod
def lookup_table_value(lookup_table, mapping) -> 'PossibleValueSet':
"""
Create a PossibleValueSet object for a value which is a member of a
lookup table.
:param list(LookupTableEntry) lookup_table: List of table entries
:param dict of (int, int) mapping: Mapping used for resolution
:rtype: PossibleValueSet
"""
result = PossibleValueSet()
result._type = RegisterValueType.LookupTableValue
result._table = lookup_table
result._mapping = mapping
return result
[docs]
@dataclass(frozen=True)
class StackVariableReference:
_source_operand: Optional[int]
type: 'binaryninja.types.Type'
name: str
var: 'Variable'
referenced_offset: int
size: int
def __repr__(self):
if self.source_operand is None:
if self.referenced_offset != self.var.storage:
return f"<ref to {self.name}{self.referenced_offset - self.var.storage:+#x}>"
return f"<ref to {self.name}>"
if self.referenced_offset != self.var.storage:
return f"<operand {self.source_operand} ref to {self.var.storage}{self.var.storage:+#x}>"
return f"<operand {self.source_operand} ref to {self.name}>"
@property
def source_operand(self):
if self._source_operand == 0xffffffff:
return None
return self._source_operand
[docs]
@dataclass(frozen=True, order=True)
class CoreVariable:
"""
``class CoreVariable`` is the base class for other variable types,
such as :py:meth:`VariableNameAndType` and :py:meth:`Variable`
:cvar index: Internal identifier
:cvar storage: If this variable is a stack variable
(`source_type == VariableSourceType.StackVariableSourceType`),
then the storage location is the offset onto the stack that contains
the first byte of this variable. Otherwise it's used as an internal identifier.
"""
_source_type: int
index: int
storage: int
@property
def identifier(self) -> int:
"""A UID for a variable within a function."""
return core.BNToVariableIdentifier(self.to_BNVariable())
@property
def source_type(self) -> VariableSourceType:
"""Whether this variable was created based off of an underlying register, stack location, or flag."""
return VariableSourceType(self._source_type)
[docs]
def to_BNVariable(self):
v = core.BNVariable()
v.type = self._source_type
v.index = self.index
v.storage = self.storage
return v
[docs]
@classmethod
def from_BNVariable(cls, var: core.BNVariable):
return cls(var.type, var.index, var.storage)
[docs]
@classmethod
def from_identifier(cls, identifier):
var = core.BNFromVariableIdentifier(identifier)
return cls(var.type, var.index, var.storage)
[docs]
@dataclass(frozen=True, order=True)
class VariableNameAndType(CoreVariable):
"""
``class VariableNameAndType`` is a lightweight wrapper around a
variable and its name, useful for shuttling between APIs that require
them both. While :py:meth:`Variable` has :py:meth:`Variable.name` and
:py:meth:`Variable.type` fields, those require additional core calls
each time you fetch them.
:cvar name: The variable's name
:cvar type: The variable's type
"""
name: str
type: 'binaryninja.types.Type'
[docs]
@classmethod
def from_identifier(cls, identifier, name, type):
var = core.BNFromVariableIdentifier(identifier)
return cls(name, type, var.type, var.index, var.storage)
[docs]
@classmethod
def from_core_variable(cls, var, name, type):
return cls(name, type, var.type, var.index, var.storage)
[docs]
class Variable(CoreVariable):
"""
``class Variable`` represents variables in Binary Ninja. Variables are resolved
in medium level IL, so variables objects are only valid for MLIL and above.
"""
def __init__(self, func: FunctionOrILFunction, source_type: VariableSourceType, index: int, storage: int):
super(Variable, self).__init__(int(source_type), index, storage)
if isinstance(func, binaryninja.function.Function):
self._function = func
self._il_function = None
else:
self._function = func.source_function
self._il_function = func
[docs]
@classmethod
def from_variable_name_and_type(cls, func: FunctionOrILFunction, var: VariableNameAndType):
return cls(func, VariableSourceType(var.type), var.index, var.storage)
[docs]
@classmethod
def from_core_variable(cls, func: FunctionOrILFunction, var: CoreVariable):
return cls(func, var.source_type, var.index, var.storage)
[docs]
@classmethod
def from_BNVariable(cls, func: FunctionOrILFunction, var: core.BNVariable):
return cls(func, var.type, var.index, var.storage)
[docs]
@classmethod
def from_identifier(cls, func: FunctionOrILFunction, identifier: int):
var = core.BNFromVariableIdentifier(identifier)
return cls(func, VariableSourceType(var.type), var.index, var.storage)
def __repr__(self):
if self.type is not None:
return f"<var {self.type.get_string_before_name()} {self.name}{self.type.get_string_after_name()}>"
else:
return f"<var {self.name}>"
def __str__(self):
return self.name
def __eq__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return super().__eq__(other) and (self._function == other._function)
def __ne__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return not (self == other)
def __lt__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return super().__lt__(other) and self._function < other._function
def __gt__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return super().__gt__(other) and self._function > other._function
def __le__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return super().__le__(other) and self._function <= other._function
def __ge__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return super().__ge__(other) and self._function >= other._function
def __hash__(self):
return hash((self._function, super().__hash__()))
@property
def core_variable(self) -> CoreVariable:
"""Retrieve the underlying :py:meth:`CoreVariable` class"""
return CoreVariable(self._source_type, self.index, self.storage)
@property
def var_name_and_type(self) -> VariableNameAndType:
"""Convert to :py:meth:`VariableNameAndType` """
return VariableNameAndType.from_core_variable(self, self.name, self.type)
@property
def name(self) -> str:
"""Name of the variable, Settings this property is slow because it ensures that analysis has been updated. If you are renaming many variables, use :py:meth:`set_name_async`, then call :py:meth:`update_analysis` when complete."""
return core.BNGetVariableNameOrDefault(self._function.handle, self.to_BNVariable())
@name.setter
def name(self, name: Optional[str]) -> None:
self.set_name_async(name)
self._function.view.update_analysis_and_wait()
@property
def last_seen_name(self) -> str:
"""Name of the variable, or the name most recently assigned if the variable has since been removed (read-only). """
return core.BNGetLastSeenVariableNameOrDefault(self._function.handle, self.to_BNVariable())
@property
def type(self) -> Optional['binaryninja.types.Type']:
var_type_conf = core.BNGetVariableType(self._function.handle, self.to_BNVariable())
if var_type_conf.type:
return binaryninja.types.Type.create(var_type_conf.type, self._function.platform, var_type_conf.confidence)
return None
@type.setter
def type(self, new_type: 'binaryninja.types.Type') -> None:
self.set_type_async(new_type)
self._function.view.update_analysis_and_wait()
@property
def ssa_versions(self) -> Generator[int, None, None]:
"""Returns the SSA versions associated with this variable. Doesn't return anything for aliased variables."""
if self._il_function is None:
raise NotImplementedError("No IL function associated with variable")
version_count = ctypes.c_ulonglong()
if self._il_function.il_form in [
FunctionGraphType.MediumLevelILFunctionGraph, FunctionGraphType.MediumLevelILSSAFormFunctionGraph
]:
versions = core.BNGetMediumLevelILVariableSSAVersions(
self._il_function.handle, self.to_BNVariable(), version_count
)
elif self._il_function.il_form in [
FunctionGraphType.HighLevelILFunctionGraph, FunctionGraphType.HighLevelILSSAFormFunctionGraph
]:
versions = core.BNGetHighLevelILVariableSSAVersions(
self._il_function.handle, self.to_BNVariable(), version_count
)
else:
raise NotImplementedError("Unsupported IL form")
if versions is None:
raise NotImplementedError("No SSA versions; is this an aliased variable?")
try:
for version_i in range(version_count.value):
yield versions[version_i]
finally:
core.BNFreeILInstructionList(versions)
@property
def dead_store_elimination(self) -> DeadStoreElimination:
"""returns the dead store elimination setting for this variable"""
return DeadStoreElimination(
core.BNGetFunctionVariableDeadStoreElimination(self._function.handle, self.to_BNVariable())
)
@dead_store_elimination.setter
def dead_store_elimination(self, value):
core.BNSetFunctionVariableDeadStoreElimination(self._function.handle, self.to_BNVariable(), value)
@property
def is_parameter_variable(self) -> bool:
"""returns whether this variable is a function parameter"""
return self in self._function.parameter_vars
@property
def offset_to_next_variable(self) -> Optional[int]:
"""returns number of bytes to the next variable on the stack"""
if self.source_type != VariableSourceType.StackVariableSourceType:
return None
for i, var in enumerate(self._function.stack_layout):
if var == self:
if i+1 < len(self._function.stack_layout):
return abs(self.storage - self._function.stack_layout[i+1].storage)
else:
return abs(self.storage)
return None
@property
def function(self) -> 'binaryninja.function.Function':
"""returns the source Function object which this variable belongs to"""
return self._function
@property
def il_function(self) -> 'function.ILFunctionType':
"""returns the IL Function object which this variable belongs to"""
return self.var._il_function
[docs]
def set_name_async(self, name: Optional[str]) -> None:
"""
``set_name_async`` provides a way to asynchronously set the name of a variable. This method should be used
when speed is of concern.
"""
if name is None:
name = ""
self._function.create_user_var(self, self.type, name)
[docs]
def set_type_async(self, new_type: 'binaryninja.types.Type') -> None:
"""
``set_type_async`` provides a way to asynchronously set the type of a variable. This method should be used
when speed is of concern.
"""
self._function.create_user_var(self, new_type, self.name)
[docs]
def set_name_and_type_async(self, name: Optional[str], new_type: 'binaryninja.types.Type') -> None:
"""
``set_name_and_type_async`` provides a way to asynchronously set both the name and type of a variable. This method should be used
when speed is of concern.
"""
self._function.create_user_var(self, new_type, name)
[docs]
@dataclass(frozen=True)
class ConstantReference:
value: int
size: int
pointer: bool
intermediate: bool
def __repr__(self):
if self.pointer:
return "<constant pointer %#x>" % self.value
if self.size == 0:
return "<constant %#x>" % self.value
return "<constant %#x size %d>" % (self.value, self.size)
[docs]
@dataclass(frozen=True)
class IndirectBranchInfo:
source_arch: 'binaryninja.architecture.Architecture'
source_addr: int
dest_arch: 'binaryninja.architecture.Architecture'
dest_addr: int
auto_defined: bool
def __repr__(self):
return f"<branch {self.source_arch.name}:{self.source_addr:#x} -> {self.dest_arch.name}:{self.dest_addr:#x}>"
[docs]
@decorators.passive
class ParameterVariables:
def __init__(
self, var_list: List[Variable], confidence: int = core.max_confidence,
func: Optional['binaryninja.function.Function'] = None
):
self._vars = var_list
self._confidence = confidence
self._func = func
def __repr__(self):
return f"<ParameterVariables: {str(self._vars)}>"
def __len__(self):
return len(self._vars)
def __iter__(self) -> Generator['Variable', None, None]:
for var in self._vars:
yield var
def __eq__(self, other) -> bool:
return (self._vars, self._confidence, self._func) == (other._vars, other._confidence, other._func)
def __getitem__(self, idx) -> 'Variable':
return self._vars[idx]
def __setitem__(self, idx: int, value: 'Variable'):
self._vars[idx] = value
if self._func is not None:
self._func.parameter_vars = self
[docs]
def with_confidence(self, confidence: int) -> 'ParameterVariables':
return ParameterVariables(list(self._vars), confidence, self._func)
@property
def vars(self) -> List['Variable']:
return self._vars
@property
def confidence(self) -> int:
return self._confidence
@property
def function(self) -> Optional['binaryninja.function.Function']:
return self._func
[docs]
@dataclass(frozen=True, order=True)
class AddressRange:
start: int # Inclusive starting address
end: int # Exclusive ending address
def __repr__(self):
return f"<{self.start:#x}-{self.end:#x}>"
def __contains__(self, i: int):
return self.start <= i < self.end