From 5c14a62561b94f453dd41ea92634ac4151a4e3a6 Mon Sep 17 00:00:00 2001 From: Peter LaFosse Date: Fri, 27 Aug 2021 12:45:45 -0400 Subject: Refactor DataVariable, BinaryReader, BinaryWriter, and StructuredDataView DataVariable is now 'live' - Can now directly get/set the name/symbol and type - Can now read values by automatically creating a TypedDataReader - Can now write the value of DataVariables (only using bytes objects for now) BinaryReader/BinaryWriter now have optional addresses passed to each of the read/write methods - Can now be instantiated from a helper on BinaryView 'br = bv.reader' StructuredDataView is deprecated in favor of TypedDataReader but left in for backward compatibility --- python/binaryview.py | 422 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 261 insertions(+), 161 deletions(-) (limited to 'python') diff --git a/python/binaryview.py b/python/binaryview.py index 6f27316a..b2266563 100644 --- a/python/binaryview.py +++ b/python/binaryview.py @@ -6844,11 +6844,12 @@ class BinaryReader: """ return core.BNIsEndOfFile(self._handle) - def read(self, length:int, address:int=None) -> Optional[bytes]: + def read(self, length:int, address:Optional[int]=None) -> Optional[bytes]: """ ``read`` returns ``length`` bytes read from the current offset, adding ``length`` to offset. :param int length: number of bytes to read. + :param int address: offset to set the internal offset before reading :return: ``length`` bytes from current offset :rtype: str, or None on failure :Example: @@ -6865,10 +6866,11 @@ class BinaryReader: return None return dest.raw - def read8(self, address:int=None) -> Optional[int]: + def read8(self, address:Optional[int]=None) -> Optional[int]: """ ``read8`` returns a one byte integer from offset incrementing the offset. + :param int address: offset to set the internal offset before reading :return: byte at offset. :rtype: int, or None on failure :Example: @@ -6886,10 +6888,11 @@ class BinaryReader: return None return result.value - def read16(self, address:int=None) -> Optional[int]: + def read16(self, address:Optional[int]=None) -> Optional[int]: """ ``read16`` returns a two byte integer from offset incrementing the offset by two, using specified endianness. + :param int address: offset to set the internal offset before reading :return: a two byte integer at offset. :rtype: int, or None on failure :Example: @@ -6907,10 +6910,11 @@ class BinaryReader: return None return result.value - def read32(self, address:int=None) -> Optional[int]: + def read32(self, address:Optional[int]=None) -> Optional[int]: """ ``read32`` returns a four byte integer from offset incrementing the offset by four, using specified endianness. + :param int address: offset to set the internal offset before reading :return: a four byte integer at offset. :rtype: int, or None on failure :Example: @@ -6928,10 +6932,11 @@ class BinaryReader: return None return result.value - def read64(self, address:int=None) -> Optional[int]: + def read64(self, address:Optional[int]=None) -> Optional[int]: """ ``read64`` returns an eight byte integer from offset incrementing the offset by eight, using specified endianness. + :param int address: offset to set the internal offset before reading :return: an eight byte integer at offset. :rtype: int, or None on failure :Example: @@ -6949,10 +6954,11 @@ class BinaryReader: return None return result.value - def read16le(self, address:int=None) -> Optional[int]: + def read16le(self, address:Optional[int]=None) -> Optional[int]: """ ``read16le`` returns a two byte little endian integer from offset incrementing the offset by two. + :param int address: offset to set the internal offset before reading :return: a two byte integer at offset. :rtype: int, or None on failure :Example: @@ -6970,10 +6976,11 @@ class BinaryReader: return None return struct.unpack(" Optional[int]: + def read32le(self, address:Optional[int]=None) -> Optional[int]: """ ``read32le`` returns a four byte little endian integer from offset incrementing the offset by four. + :param int address: offset to set the internal offset before reading :return: a four byte integer at offset. :rtype: int, or None on failure :Example: @@ -6991,10 +6998,11 @@ class BinaryReader: return None return struct.unpack(" Optional[int]: + def read64le(self, address:Optional[int]=None) -> Optional[int]: """ ``read64le`` returns an eight byte little endian integer from offset incrementing the offset by eight. + :param int address: offset to set the internal offset before reading :return: a eight byte integer at offset. :rtype: int, or None on failure :Example: @@ -7012,10 +7020,11 @@ class BinaryReader: return None return struct.unpack(" Optional[int]: + def read16be(self, address:Optional[int]=None) -> Optional[int]: """ ``read16be`` returns a two byte big endian integer from offset incrementing the offset by two. + :param int address: offset to set the internal offset before reading :return: a two byte integer at offset. :rtype: int, or None on failure :Example: @@ -7033,10 +7042,11 @@ class BinaryReader: return None return struct.unpack(">H", result)[0] - def read32be(self, address:int=None) -> Optional[int]: + def read32be(self, address:Optional[int]=None) -> Optional[int]: """ ``read32be`` returns a four byte big endian integer from offset incrementing the offset by four. + :param int address: offset to set the internal offset before reading :return: a four byte integer at offset. :rtype: int, or None on failure :Example: @@ -7044,7 +7054,6 @@ class BinaryReader: >>> br.seek(0x100000000) >>> hex(br.read32be()) '0xcffaedfe' - >>> """ if address is not None: self.seek(address) @@ -7054,10 +7063,11 @@ class BinaryReader: return None return struct.unpack(">I", result)[0] - def read64be(self, address:int=None) -> Optional[int]: + def read64be(self, address:Optional[int]=None) -> Optional[int]: """ ``read64be`` returns an eight byte big endian integer from offset incrementing the offset by eight. + :param int address: offset to set the internal offset before reading :return: a eight byte integer at offset. :rtype: int, or None on failure :Example: @@ -7197,6 +7207,7 @@ class BinaryWriter: ``write`` writes ``len(value)`` bytes to the internal offset, without regard to endianness. :param str value: bytes to be written at current offset + :param int address: offset to set the internal offset before writing :return: boolean True on success, False on failure. :rtype: bool :Example: @@ -7220,6 +7231,7 @@ class BinaryWriter: ``write8`` lowest order byte from the integer ``value`` to the current offset. :param str value: bytes to be written at current offset + :param int address: offset to set the internal offset before writing :return: boolean :rtype: bool :Example: @@ -7239,6 +7251,7 @@ class BinaryWriter: ``write16`` writes the lowest order two bytes from the integer ``value`` to the current offset, using internal endianness. :param int value: integer value to write. + :param int address: offset to set the internal offset before writing :return: boolean True on success, False on failure. :rtype: bool """ @@ -7251,6 +7264,7 @@ class BinaryWriter: ``write32`` writes the lowest order four bytes from the integer ``value`` to the current offset, using internal endianness. :param int value: integer value to write. + :param int address: offset to set the internal offset before writing :return: boolean True on success, False on failure. :rtype: bool """ @@ -7263,6 +7277,7 @@ class BinaryWriter: ``write64`` writes the lowest order eight bytes from the integer ``value`` to the current offset, using internal endianness. :param int value: integer value to write. + :param int address: offset to set the internal offset before writing :return: boolean True on success, False on failure. :rtype: bool """ @@ -7275,6 +7290,7 @@ class BinaryWriter: ``write16le`` writes the lowest order two bytes from the little endian integer ``value`` to the current offset. :param int value: integer value to write. + :param int address: offset to set the internal offset before writing :return: boolean True on success, False on failure. :rtype: bool """ @@ -7287,6 +7303,7 @@ class BinaryWriter: ``write32le`` writes the lowest order four bytes from the little endian integer ``value`` to the current offset. :param int value: integer value to write. + :param int address: offset to set the internal offset before writing :return: boolean True on success, False on failure. :rtype: bool """ @@ -7299,6 +7316,7 @@ class BinaryWriter: ``write64le`` writes the lowest order eight bytes from the little endian integer ``value`` to the current offset. :param int value: integer value to write. + :param int address: offset to set the internal offset before writing :return: boolean True on success, False on failure. :rtype: bool """ @@ -7311,6 +7329,7 @@ class BinaryWriter: ``write16be`` writes the lowest order two bytes from the big endian integer ``value`` to the current offset. :param int value: integer value to write. + :param int address: offset to set the internal offset before writing :return: boolean True on success, False on failure. :rtype: bool """ @@ -7323,6 +7342,7 @@ class BinaryWriter: ``write32be`` writes the lowest order four bytes from the big endian integer ``value`` to the current offset. :param int value: integer value to write. + :param int address: offset to set the internal offset before writing :return: boolean True on success, False on failure. :rtype: bool """ @@ -7335,6 +7355,7 @@ class BinaryWriter: ``write64be`` writes the lowest order eight bytes from the big endian integer ``value`` to the current offset. :param int value: integer value to write. + :param int address: offset to set the internal offset before writing :return: boolean True on success, False on failure. :rtype: bool """ @@ -7377,167 +7398,291 @@ class BinaryWriter: core.BNSeekBinaryWriterRelative(self._handle, offset) -@dataclass -class StructuredDataValue: - type:'_types.Type' - value:bytes - endian:Endianness +class StructuredDataValue(object): + def __init__(self, type, address, value, endian): + """ + DEPRECATED use: TypedDataReader instead. + """ + self._type = type + self._address = address + self._value = value + self._endian = endian def __str__(self): - decode_str = "{}B".format(self.type.width) - return ' '.join(["{:02x}".format(x) for x in struct.unpack(decode_str, self.value)]) + decode_str = "{}B".format(self._type.width) + return ' '.join(["{:02x}".format(x) for x in struct.unpack(decode_str, self._value)]) def __repr__(self): - return f"" - - def __len__(self): - return len(self.value) + return "".format(str(self._type), str(self)) def __int__(self): - if isinstance(self.type, _types.PointerType): - return self.int_from_bytes(self.value, self.type.width, False, self.endian) - elif isinstance(self.type, (_types.IntegerType, _types.EnumerationType)): - return self.int_from_bytes(self.value, self.type.width, bool(self.type.signed), self.endian) - raise Exception("Attempting to coerce non integral type to an integer") - - @staticmethod - def int_from_bytes(data:bytes, width:int, sign:bool, endian:Optional[Endianness]=None) -> int: - if width == 1: - code = "B" - elif width == 2: - code = "H" - elif width == 4: - code = "I" - elif width == 8: - code = "Q" + if self._type.width == 1: + if self._endian == Endianness.LittleEndian: + code = "" - if sign: - code = code.lower() - return struct.unpack(f"{_endian}{code}", data)[0] + return struct.unpack(code, self._value)[0] - def __float__(self): - if not isinstance(self.type, _types.FloatType): - raise Exception("Attempting to coerce non float type to a float") - endian = "<" if self.endian == Endianness.LittleEndian else ">" - if self.type.width == 2: - code = "e" - elif self.type.width == 4: - code = "f" - elif self.type.width == 8: - code = "d" - else: - raise Exception("Could not convert to float with width {}".format(self.type.width)) - return struct.unpack(f"{endian}{code}", self.value)[0] + @property + def type(self): + return self._type @property - def int(self) -> int: - return int(self) + def width(self): + return self._type.width @property - def float(self) -> float: - return float(self) + def address(self): + return self._address -# class StructuredDataView: -# @staticmethod -# def create(view:'BinaryView', structure_name:str, address:int, endian:Optional[Endianness]=None): -# s = view.get_type_by_name(structure_name) -# if s is None: -# raise Exception(f"Could not find structure with name: {structure_name}") + @property + def value(self): + return self._value -# if s.type_class == TypeClass.NamedTypeReferenceClass: -# s = view.get_type_by_id(s.named_type_reference.id) -# if s.type_class != TypeClass.StructureTypeClass: -# raise Exception(f"{self.structure_name} is not a StructureTypeClass, got: {s.type_class}") + @property + def int(self): + return int(self) -# self._structure = s.structure + @property + def str(self): + return str(self) -# return make_dataclass(f"{structure_name}_{address}_{endian}", fields) -@dataclass -class StructuredDataView: +class StructuredDataView(object): """ - ``class StructuredDataView`` is a convenience class for reading structured binary data. + DEPRECATED use: TypedDataReader instead. + ``class StructuredDataView`` is a convenience class for reading structured binary data. StructuredDataView can be instantiated as follows: - >>> from binaryninja import * >>> bv = BinaryViewType.get_view_of_file("/bin/ls") >>> structure = "Elf64_Header" >>> address = bv.start >>> elf = StructuredDataView(bv, structure, address) >>> - Once instantiated, members can be accessed: - >>> print("{:x}".format(elf.machine)) 003e >>> + """ + _structure = None + _structure_name = None + _address = 0 + _bv = None - """ - view:BinaryView - structure:_types.StructureType - address:int - endian:Optional[Endianness] = None - - def __post__init__(self): - if self.endian is None: - if self.view.arch is None: - raise Exception("Can not instantiate StructuredDataView without specifying and Endianness") - self.endian = self.view.arch.endianness - + def __init__(self, bv, structure_name, address): + self._bv = bv + self._structure_name = structure_name + self._address = address self._members = OrderedDict() - for member in self.structure.members: - self._members[member.name] = member + self._endian = bv.arch.endianness + + self._lookup_structure() + self._define_members() def __repr__(self): - return f"" + return "".format(self._structure_name, + self._structure.width, self._address) def __len__(self): - return len(self.structure) + return self._structure.width def __getattr__(self, key): - m = self.structure[key] + m = self._members.get(key, None) if m is None: return self.__getattribute__(key) return self[key] def __getitem__(self, key): - m = self.structure[key] + m = self._members.get(key, None) if m is None: return m - value = self.view.read(self.address + m.offset, m.type.width) - return StructuredDataValue(m.type, value, self.endian) # type: ignore + ty = m.type + offset = m.offset + width = ty.width + + value = self._bv.read(self._address + offset, width) + return StructuredDataValue(ty, self._address + offset, value, self._endian) def __str__(self): - rv = f"{self.structure} {self.adddress:#x} {{\n" + rv = "struct {name} 0x{addr:x} {{\n".format(name=self._structure_name, addr=self._address) for k in self._members: m = self._members[k] ty = m.type offset = m.offset - formatted_offset = f"{offset:=+x}" - formatted_type = f"{str(ty):s} {k:s}" + formatted_offset = "{:=+x}".format(offset) + formatted_type = "{:s} {:s}".format(str(ty), k) value = self[k] - assert value is not None - if len(value) in (1, 2, 4, 8): - formatted_value = str.zfill("{:x}".format(value.int), len(value) * 2) + if value.width in (1, 2, 4, 8): + formatted_value = str.zfill("{:x}".format(value.int), value.width * 2) else: formatted_value = str(value) - rv += f"\t{formatted_offset:>6s} {formatted_type:40s} = {formatted_value:30s}\n" + rv += "\t{:>6s} {:40s} = {:30s}\n".format(formatted_offset, formatted_type, formatted_value) rv += "}\n" return rv + def _lookup_structure(self): + s = self._bv.get_type_by_name(self._structure_name) + if s is None: + raise Exception("Could not find structure with name: {}".format(self._structure_name)) + + if s.type_class != TypeClass.StructureTypeClass: + raise Exception("{} is not a StructureTypeClass, got: {}".format(self._structure_name, s._type_class)) + + self._structure = s.structure + + def _define_members(self): + for m in self._structure.members: + self._members[m.name] = m + + +@dataclass +class TypedDataReader: + type:'_types.Type' + address:int + view:'BinaryView' + endian:Endianness + + def __post_init__(self): + assert isinstance(self.type, _types.Type), "Attempting to create TypedDataReader with TypeBuilder" + + def __bytes__(self): + return self.view.read(self.address, len(self.type)) + + def __repr__(self): + return f"" + + def __len__(self): + return len(self.type) + + def __int__(self): + _type = self.type + if isinstance(_type, _types.NamedTypeReferenceType): + _type = _type.target(self.view) + if isinstance(_type, _types.PointerType): + return self.int_from_bytes(bytes(self), _type.width, False, self.endian) + elif isinstance(_type, (_types.IntegerType, _types.EnumerationType)): + return self.int_from_bytes(bytes(self), _type.width, bool(_type.signed), self.endian) + raise Exception(f"Attempting to coerce non integral type: {type(_type)} to an integer") + + def __getitem__(self, key:Union[str, int]) -> 'TypedDataReader': + _type = self.type + if isinstance(_type, _types.NamedTypeReferenceType): + _type = _type.target(self.view) + if isinstance(_type, _types.ArrayType) and isinstance(key, int): + assert key < _type.count, f"Index {key} out of bounds array has {_type.count} elements" + return TypedDataReader(_type.element_type, key * len(_type.element_type), self.view, self.endian) + assert isinstance(_type, _types.StructureType), "Can't get member of non-structure" + assert isinstance(key, str), "Must use string to get member of structure" + m = _type[key] + assert m is not None, f"Member {key} doesn't exist in structure" + return TypedDataReader(m.type.immutable_copy(), m.offset, self.view, self.endian) + + @staticmethod + def int_from_bytes(data:bytes, width:int, sign:bool, endian:Optional[Endianness]=None) -> int: + if width == 1: + code = "B" + elif width == 2: + code = "H" + elif width == 4: + code = "I" + elif width == 8: + code = "Q" + else: + raise Exception("Could not convert to integer with width {}".format(width)) + + _endian = "<" if endian == Endianness.LittleEndian else ">" + if sign: + code = code.lower() + return struct.unpack(f"{_endian}{code}", data)[0] + + def __float__(self): + if not isinstance(self.type, _types.FloatType): + raise Exception("Attempting to coerce non float type to a float") + endian = "<" if self.endian == Endianness.LittleEndian else ">" + if self.type.width == 2: + code = "e" + elif self.type.width == 4: + code = "f" + elif self.type.width == 8: + code = "d" + else: + raise Exception("Could not convert to float with width {}".format(self.type.width)) + return struct.unpack(f"{endian}{code}", bytes(self))[0] + + @property + def value(self) -> Any: + return self._value_helper(self.type, self.view.read(self.address, len(self.type))) + + @value.setter + def value(self, data:bytes) -> None: + assert isinstance(data, bytes), "Setting value to type other than bytes not currently supported" + self.view.write(self.address, data) + + def _value_helper(self, _type:'_types.Type', data:bytes) -> Any: + assert isinstance(_type, _types.Type), f"Attempting to get value of TypeBuilder of type {type(_type)}" + if isinstance(_type, _types.NamedTypeReferenceType): + target = _type.target(self.view) + assert target is not None + _type = target + + if isinstance(_type, (_types.VoidType, _types.FunctionType)): #, _types.VarArgsType, _types.ValueType)): + return None + elif isinstance(_type, _types.BoolType): + return bool(self) + elif isinstance(_type, (_types.IntegerType, _types.PointerType)): + return int(self) + elif isinstance(_type, _types.FloatType): + return float(self) + elif isinstance(_type, _types.WideCharType): + return data.decode("utf-16") + elif isinstance(_type, _types.StructureType): + result = {} + for member in _type.members: + result[member.name] = TypedDataReader(member.type, self.address + member.offset, self.view, self.endian).value + return result + elif isinstance(_type, _types.EnumerationType): + value = int(self) + for member in _type.members: + if int(member) == value: + return member + return value + elif isinstance(_type, _types.ArrayType): + result = [] + if _type.element_type is None: + raise ValueError("Can not get value for Array type with no element type") + for offset in range(0, len(_type), _type.element_type.width): + result.append(TypedDataReader(_type.element_type, self.address + offset, self.view, self.endian).value) + return result + else: + assert False, f"Unhandled `Type` {type(_type)}" + @dataclass(frozen=True) class CoreDataVariable: @@ -7545,6 +7690,8 @@ class CoreDataVariable: type:'_types.Type' auto_discovered:bool + def __len__(self): + return len(self.type) @dataclass class DataVariable: @@ -7552,12 +7699,7 @@ class DataVariable: view:'BinaryView' def __post_init__(self): - if isinstance(self.core_data_var.type, _types.NamedTypeReferenceType): - referenced_type = self.core_data_var.type.target(self.view) - assert isinstance(referenced_type, _types.StructureType) - self._sdv = StructuredDataView(self.view, referenced_type, self.address) - elif isinstance(self.core_data_var.type, _types.StructureType): - self._sdv = StructuredDataView(self.view, self.core_data_var.type, self.address) + self._sdv = TypedDataReader(self.core_data_var.type, self.core_data_var.address, self.view, self.view.endianness) @classmethod def from_core_struct(cls, var:core.BNDataVariable, view:'BinaryView') -> 'DataVariable': @@ -7590,60 +7732,18 @@ class DataVariable: def address(self) -> int: return self.core_data_var.address - def _value_helper(self, t:'_types.Type', data:bytes): - sdv = StructuredDataValue(t, data, self.view.endianness) - if isinstance(t, (_types.VoidType, _types.FunctionType)): #, _types.VarArgsType, _types.ValueType)): - return None - elif isinstance(t, _types.BoolType): - return bool(sdv) - elif isinstance(t, (_types.IntegerType, _types.PointerType)): - return int(sdv) - elif isinstance(t, _types.FloatType): - return float(sdv) - elif isinstance(t, _types.WideCharType): - return data.decode("utf-8") - elif isinstance(t, _types.StructureType): - result = {} - for member in t.members: - member_data = data[member.offset: member.offset+member.type.width] - result[member.name] = self._value_helper(member.type, member_data) - return result - elif isinstance(t, _types.EnumerationType): - value = int(sdv) - for member in t.members: - if int(member) == value: - return member - return value - elif isinstance(t, _types.ArrayType): - result = [] - if t.element_type is None: - raise ValueError("Can not get value for Array type with no element type") - for i in range(t.count): - offset = i * t.element_type.width - element_data = data[offset: offset + t.element_type.width] - result.append(self._value_helper(t.element_type, element_data)) - return result - elif isinstance(t, _types.NamedTypeReferenceType): - target = self.view.get_type_by_id(t.type_id) - assert target is not None - return self._value_helper(target, data) - assert False, f"Unhandled `Type` {type(t)}" - @property def value(self) -> Any: - data = self.view.read(self.address, self.type.width) - if len(data) != self.type.width: - raise Exception(f"Failed to read bytes at address {self.address} of width {self.type.width}") - return self._value_helper(self.type, data) + return self._sdv.value + + @value.setter + def value(self, data:bytes) -> None: + self._sdv.value = data def __getitem__(self, item:str): - assert self._sdv is not None, "Can't get item for non-structure types" + # assert self._sdv is not None, "Can't get item for non-structure types" return self._sdv[item] - @value.setter - def value(self, value:bytes) -> None: - self.view.write(self.address, value) - @property def type(self) -> '_types.Type': return self.core_data_var.type -- cgit v1.3.1