[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/Vector35/binaryninja-api/dev/python/examples/rust_string.py [Back]  [Original]

"""
Example Rust ``&str`` string recognizer and data renderer.

Rust represents a string slice (``&str``) as a two word "fat pointer": a pointer
to the UTF-8 bytes followed by the length of the slice. The type for it looks like::

	struct &str
	{
		char* string;     // offset 0: pointer to the UTF-8 bytes
		uint64_t length;  // offset 8: number of bytes
	};

This plugin allows Binary Ninja to recover the underlying text in two situations:

* **Structure initializers.** When the optimizer folds the field assignments of
  a ``&str`` value into a single ``HLIL_STRUCT_INIT`` expression, the recognizer
  reads ``length`` bytes from ``string`` and renders the literal in place.

* **Constant pointers to ``&str`` data variables.** When code takes the address
  of a ``&str`` data variable, the recognizer reads the fat pointer out of that
  data variable and renders the string it points at.

The recognized strings use the ``rs`` prefix, so they render as ``rs"..."``. A
matching data renderer renders ``&str`` data variables the same way in linear view.
"""

from typing import Dict, List, Optional

from binaryninja import BinaryView, Type
from binaryninja.datarender import DataRenderer, TypeContext
from binaryninja.enums import (
	DerivedStringLocationType, InstructionTextTokenType, TypeClass)
from binaryninja.function import DisassemblyTextLine, InstructionTextToken
from binaryninja.highlevelil import HighLevelILFunction, HighLevelILInstruction
from binaryninja.stringrecognizer import CustomStringType, StringRecognizer
from binaryninja.types import NamedTypeReferenceType
from binaryninja.binaryview import DerivedString, DerivedStringLocation

# Exact name of the Rust string slice type we recognize.
str_type_name = "&str"

# Register a custom string type so the core knows how to render the strings we
# recover. The prefix turns "..." into rs"...".
rust_str_type = CustomStringType.register(str_type_name, string_prefix="rs")


def _type_name(type: Optional[Type]) -> Optional[str]:
	"""Return the registered/reference name of a type, or None if it has none.

	A `&str` value arrives either as a named type reference (`type.name`) or,
	once resolved, as the underlying structure carrying a registered name."""
	if type is None:
		return None
	if isinstance(type, NamedTypeReferenceType):
		return str(type.name)
	registered = type.registered_name
	if registered is not None:
		return str(registered.name)
	return None


def _is_str_type(type: Optional[Type]) -> bool:
	"""True if `type` is exactly the `&str` type."""
	return _type_name(type) == str_type_name


def _is_pointer_to_str(type: Optional[Type]) -> bool:
	"""True if `type` is a pointer to the `&str` type."""
	return type is not None and type.type_class == TypeClass.PointerTypeClass and _is_str_type(type.target)


def _derived_string_from_slice(bv: BinaryView, pointer: int, length: int) -> Optional[DerivedString]:
	"""Read `length` UTF-8 bytes at `pointer` and wrap them in a DerivedString.

	The returned string is data-backed location pointing at the bytes so
	that the rendered literal cross-references the underlying string data."""
	if length < 0:
		return None
	data = bv.read(pointer, length)
	if data is None or len(data) != length:
		return None
	location = DerivedStringLocation(DerivedStringLocationType.DataBackedStringLocation, pointer, length)
	return DerivedString(data, location, rust_str_type)


def _read_str_data_var(bv: BinaryView, addr: int) -> Optional[DerivedString]:
	"""Reads the `&str` fat pointer stored at `addr` and renders the string it points to."""
	addr_size = bv.address_size
	pointer = bv.read_pointer(addr)
	raw_length = bv.read(addr + addr_size, addr_size)
	if raw_length is None or len(raw_length) != addr_size:
		return None
	length = int.from_bytes(raw_length, "little")
	return _derived_string_from_slice(bv, pointer, length)


class RustStrRecognizer(StringRecognizer):
	"""Recognizes Rust `&str` slices in HLIL expressions."""
	recognizer_name = "Rust &str"

	def is_valid_for_type(self, func: HighLevelILFunction, type: Type) -> bool:
		# Run for `&str` structure initializers and for constant pointers to a
		# `&str` data variable; skip every other expression type.
		return _is_str_type(type) or _is_pointer_to_str(type)

	def recognize_struct_init(
		self, instr: HighLevelILInstruction, type: Type, vals: Dict[int, int]
	) -> Optional[DerivedString]:
		# `vals` maps each constant field offset to its value: offset 0 is the
		# pointer to the bytes, offset at address size is the length of the slice.
		addr_size = instr.function.view.address_size
		if 0 not in vals or addr_size not in vals:
			return None
		pointer = vals[0]
		length = vals[addr_size]
		return _derived_string_from_slice(instr.function.view, pointer, length)

	def recognize_constant_pointer(
		self, instr: HighLevelILInstruction, type: Type, val: int
	) -> Optional[DerivedString]:
		# Only resolve when a `&str` data variable actually lives at the pointer.
		bv = instr.function.view
		data_var = bv.get_data_var_at(val)
		if data_var is None or not _is_str_type(data_var.type):
			return None
		return _read_str_data_var(bv, val)


class RustStrDataRenderer(DataRenderer):
	"""Renders `&str` data variables as `rs"..."` in linear view."""

	def perform_is_valid_for_data(
		self, ctxt, view: BinaryView, addr: int, type: Type, context: List[TypeContext]
	) -> bool:
		return _is_str_type(type) and _read_str_data_var(view, addr) is not None

	def perform_get_lines_for_data(
		self, ctxt, view: BinaryView, addr: int, type: Type, prefix: List[InstructionTextToken],
		width: int, context: List[TypeContext]
	) -> List[DisassemblyTextLine]:
		derived = _read_str_data_var(view, addr)
		tokens = list(prefix)
		if derived is None:
			# We verified this in `perform_is_valid_for_data`, but handle the case of failing to
			# fetch the string in case the data variable has changed since the check.
			tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, str(type)))
			return [DisassemblyTextLine(tokens, addr)]
		# `&str` is UTF-8 by definition; escape control characters and quotes for display.
		text = bytes(derived.value).decode("utf-8", "replace")
		escaped = text.encode("unicode_escape").decode("ascii").replace('"', '\\"')
		# `prefix` already carries the `  = ` tokens, just append the literal.
		tokens.append(InstructionTextToken(InstructionTextTokenType.BraceToken, f'rs"'))
		tokens.append(InstructionTextToken(InstructionTextTokenType.StringToken, escaped))
		tokens.append(InstructionTextToken(InstructionTextTokenType.BraceToken, '"'))
		return [DisassemblyTextLine(tokens, addr)]

	def __del__(self):
		pass


RustStrRecognizer().register()
RustStrDataRenderer().register_type_specific()

Web Proxy Viewer  |  New URL  |  Original Page