Source code for revenge.types


import logging
logger = logging.getLogger(__name__)

import weakref
import collections
from .exceptions import *

[docs]def require_process(func): def func_wrap(self, *args, **kwargs): if self._process is None: raise RevengeProcessRequiredError("Calling {} requires ._process be set first.".format(func.__name__)) return func(self, *args, **kwargs) return func_wrap
# Keeping str types as properties in case they change what they call things later
[docs]class BasicBasic(object): @property def _process(self): """Process property is only required for certain actions.""" try: return self.__process except AttributeError: return None @_process.setter def _process(self, process): if not isinstance(process, Process): logger.error("_process must be an instance of Process. Got type {}".format(type(process))) return self.__process = process @property def memory(self): """Instantiate this type to an active memory location for getting and setting. Examples: .. code-block:: python3 struct = types.Struct() struct.add_member('my_int', types.Int) struct.add_member('my_pointer', types.Pointer) struct.memory = 0x12345 # OR struct.memory = 'a.out:symb' # OR struct.memory = process.memory[<whatever>] """ try: return self.__memory except AttributeError: return None @memory.setter def memory(self, memory): if isinstance(memory, (int, str)): # Passthrough and let memory object deal with it if self._process is None: logger.error("Setting Memory via int or str requires ._process be set.") return self.__memory = self._process.memory[memory] elif isinstance(memory, MemoryBytes): self.__memory = memory self._process = memory._process else: logger.error("Unhandled memory property setter of type {}".format(type(memory)))
[docs]class Basic(BasicBasic): def __add__(self, other): if type(self) is type(other) or type(other) is int: return self.__class__(int.__add__(self, other)) else: logger.warning("Adding incompatible types {} and {}. Un-casting back to int.".format(type(self), type(other))) return int(self) + int(other) def __sub__(self, other): if type(self) is type(other) or type(other) is int: return self.__class__(int.__sub__(self, other)) else: logger.warning("Subtracting incompatible types {} and {}. Un-casting back to int.".format(type(self), type(other))) return int(self) - int(other) @property def js(self): """String that can be fed into js.""" return str(self)
[docs]class FloatBasic: def __add__(self, other): if type(self) is type(other) or type(other) is int: return self.__class__(float.__add__(self, other)) else: logger.warning("Adding incompatible types {} and {}. Un-casting back to float.".format(type(self), type(other))) return float(self) + float(other) def __sub__(self, other): if type(self) is type(other) or type(other) is int: return self.__class__(float.__sub__(self, other)) else: logger.warning("Subtracting incompatible types {} and {}. Un-casting back to float.".format(type(self), type(other))) return float(self) - float(other)
# TODO: Add actual size checks and stuff here ( # In [57]: np.int8(0b10000000) # Out[57]: -128 # That's failing rn
[docs]class Int8(Basic, int): type = "int8" ctype = "char" sizeof = 1
[docs]class UInt8(Basic, int): type = "uint8" ctype = "unsigned char" sizeof = 1
[docs]class Int16(Basic, int): type = "int16" ctype = "short" sizeof = 2
[docs]class UInt16(Basic, int): type = "uint16" ctype = "unsigned short" sizeof = 2
[docs]class Int32(Basic, int): type = "int32" ctype = "int" sizeof = 4
[docs]class UInt32(Basic, int): type = "uint32" ctype = "unsigned int" sizeof = 4
[docs]class Int64(Basic, int): type = "int64" ctype = "long" sizeof = 8 @property def js(self): return "int64('{}')".format(hex(self))
[docs]class UInt64(Basic, int): type = "uint64" ctype = "unsigned long" sizeof = 8 @property def js(self): return "uint64('{}')".format(hex(self))
[docs]class Char(Int8): type = "char"
[docs]class UChar(UInt8): type = "uchar"
[docs]class Short(Int16): type = "short"
[docs]class UShort(UInt16): type = "ushort"
[docs]class Int(Int32): type = "int"
[docs]class UInt(UInt32): type = "uint"
[docs]class Long(Int64): type = "long"
[docs]class ULong(UInt64): type = "ulong"
[docs]class Float(FloatBasic, float): type = "float" ctype = "float" sizeof = 4 @property def js(self): return str(self)
[docs]class Double(Float): type = "double" ctype = "double" sizeof = 8
[docs]class Pointer(UInt64): type = "pointer" ctype = "void *" # Handle recursing down into sub object to get ctype # i.e.: char * @property @require_process def sizeof(self): return int(self._process.bits/8) @property def js(self): return """ptr("{}")""".format(hex(self))
[docs]class Padding(BasicBasic): """Defines the spacing between struct entries. Example: .. code-block:: python3 struct = types.Struct() struct['one'] = types.Int8 struct['pad1'] = types.Padding(3) struct['two'] = types.Int16 """ def __init__(self, size): self.sizeof = size
# # These don't directly have a return type value, they will just be pointers.. #
[docs]class StringUTF8(BasicBasic, str): type = 'utf8' ctype = "char *" sizeof = Pointer.sizeof @property def js(self): logger.error("Shouldn't be asking for js on this object...") return str(self)
[docs]class StringUTF16(BasicBasic, str): type = 'utf16' sizeof = Pointer.sizeof @property def js(self): logger.error("Shouldn't be asking for js on this object...") return str(self)
[docs]class Struct(Pointer): """Defines a C structure. Examples: .. code-block:: python3 # Create a struct my_struct = types.Struct() my_struct.add_member('member_1', types.Int) my_struct.add_member('pad1', types.Padding(1)) my_struct.add_member('member_2', types.Pointer) # Alternatively, add them IN ORDER via dict setter my_struct = types.Struct() my_struct['member_1'] = types.Int my_struct['member_2'] = types.Pointer # Use cast to bind your struct to a location my_struct = process.memory[0x12345].cast(my_struct) # Or set memory property directly my_struct.memory = process.memory[0x12345] # Read out the values my_struct['member_1'] my_struct['member_2'] # Write in some new values (this will auto-cast based on struct def) my_struct['member_1'] = 12 # Allocate a struct and use it in a function call my_struct = process.memory.alloc_struct(my_struct) process.memory[<some function>](types.Pointer(my_struct)) """
[docs] def add_member(self, name, value=None): """Adds given member to the end of this current structure. Args: name (str): Name of the Struct member value (revenge.types.all_types): Type and/or value for member. Examples: .. code-block:: python3 s = revenge.types.Struct() s.add_member('my_int', revenge.types.Int(12)) # Or, just the definition s = revenge.types.Struct() s.add_member('my_int', revenge.types.Int) """ if not type(name) is str: logger.error("Member name must be of type str.") return if not isinstance(value, all_types) and not value in all_types: logger.error("Entry added must be one of the revenge.types.* classes.") return if name in self.members: logger.warning("Member name already exists! This will overwrite the old member with the new value!") self.members[name] = value
@require_process def _get_member_offset(self, member_name): """int: Figure out how far in (in bytes) from the struct a given member is.""" if member_name not in self.members: logger.error("This member doesn't exist.") return offset = 0 for name, member in self.members.items(): if name == member_name: return offset if type(member) is type: member = member() member._process = self._process offset += member.sizeof @property @require_process def sizeof(self): """Equivalent of calling 'sizeof(this_struct)'.""" sum = 0 for name, value in self.members.items(): # Temporarily make an object so we can figure out size if type(value) is type: value = value() value._process = self._process sum += value.sizeof return sum @property def members(self): try: return self.__members except AttributeError: self.__members = collections.OrderedDict() return self.__members @members.setter def members(self, members): if isinstance(members, tuple): members = list(members) if not isinstance(members, list): logger.error("Setting members property requires a list.") return self.__members = members @property def name(self): try: return self.__name except AttributeError: return None @name.setter def name(self, name): if not isinstance(name, str): logger.error("Name must be instance of str.") return self.__name = name def __getitem__(self, member_name): if not isinstance(member_name, str): logger.error("Only member names are currently supported.") return if member_name not in self.members: logger.error("Member {} doesn't appear to exist in this struct.".format(member_name)) return member = self.members[member_name] if self.memory is not None: # Assume we want to read the actual value member_offset = self._get_member_offset(member_name) return self.memory._process.memory[self.memory.address + member_offset].cast(member) return member def __setitem__(self, member_name, value): # If not bound, this is the same as [re]adding a member if self.memory is None: return self.add_member(member_name, value) if not isinstance(member_name, str): logger.error("Only member names are currently supported.") return if member_name not in self.members: logger.error("Member {} doesn't appear to exist in this struct.".format(member_name)) return member = self.members[member_name] if type(member) is not type: member_type = type(member) else: member_type = member member_offset = self._get_member_offset(member_name) # Auto type-casting it self.memory._process.memory[self.memory.address + member_offset] = member_type(value) def __repr__(self): attrs = ['Struct'] if self.name is not None: attrs.append(self.name) attrs += ['members:' + str(len(self.members))] return '<' + ' '.join(attrs) + '>' def __str__(self): # No. This is not meant to be actual working struct code! s = "struct {} {{\n".format(self.name if self.name is not None else "") for name, value in self.members.items(): if isinstance(value, Padding): continue s += " " + name + " = " s += str(self[name]) s += ";\n" s += "}" return s def __int__(self): try: return int(self.memory.address) except AttributeError: return 0 def __hex__(self): return hex(int(self)) def __index__(self): return int(self)
[docs]class Telescope(BasicBasic): # TODO: Telescope needs to be abstracted by engine _flyweight_cache = weakref.WeakValueDictionary() def __init__(self, process, address=None, data=None): """Create a telescoped variable object. Args: process: Base process object address (int, optional): Address to telescope data (dict, optional): Dictionary object of information to populate from. see telescope.js. """ # Doing setup in __new__ pass def __new__(klass, process, address=None, data=None): # Implementing auto flyweight pattern for telescope magic telescope = super(Telescope, klass).__new__(klass) telescope._process = process telescope.address = address if data is not None: telescope._parse_dict(data) # If this object already exists, just use that one h = hash(telescope) if h in telescope._flyweight_cache: return telescope._flyweight_cache[h] # Save this object off telescope._flyweight_cache[h] = telescope return telescope def _telescope(self): d = self._process.engine.run_script_generic(r"""send(telescope({}));""".format( int(self.address)), raw=True, include_js="telescope.js", unload=True, runtime="v8")[0][0] self._parse_dict(d) def _parse_dict(self, d): """Parses a dict generated by js telescope call.""" if "telescope" not in d or d["telescope"] is not True: raise RevengeInvalidArgumentType("dictionary does not appear to be a telescope dictionary.") # Type must be set first! self.type = d['type'] self.thing = d['thing'] self.next = d['next'] self.memory_range = d['mem_range'] def __repr__(self): return "<Telescope " + self.description + ">" def __int__(self): if self.type != "int": raise RevengeInvalidArgumentType("Asking for int of Telescope value that is type {}".format(self.type)) return self.thing def __index__(self): return self.__int__() def __hex__(self): return hex(int(self)) def __and__(self, other): return int(self) & other def __rshift__(self, shift): return int(self) >> shift def __hash__(self): return hash((self.address, self.type, self.thing, self.next, self.memory_range)) @property def thing(self): """Whatever this part of the telescope is.""" try: return self.__thing except AttributeError: return None @thing.setter def thing(self, thing): # Immutable after set due to flyweight if self.thing is not None: raise RevengeImmutableError("thing argument is immutable.") # Sometimes we get ints returned as string hex rep if self.type == "int": thing = common.auto_int(thing) elif self.type == "instruction": thing = AssemblyInstruction.from_frida_dict(self._process, thing) self.__thing = thing @property def next(self): """Next step in the telescope.""" return self.__next @next.setter def next(self, next): if next is None: self.__next = None else: self.__next = Telescope(self._process, data=next) @property def address(self): """int: Address of this variable.""" return self.__address @address.setter def address(self, address): if address is None: self.__address = None return if isinstance(address, Symbol): address = address.address if not isinstance(address, int): raise RevengeInvalidArgumentType( "address must be of int type. Got {}".format(type(address))) self.__address = address self._parse_dict( self._process.engine.run_script_generic( r"""send(telescope({}));""".format(self.address), raw=True, include_js="telescope.js", unload=True, runtime="v8")[0][0]) @property def memory_range(self): """Information about the memory range this is in.""" return self.__memory_range @memory_range.setter def memory_range(self, memory_range): if memory_range is None: self.__memory_range = None else: self.__memory_range = MemoryRange._from_frida_find_json(self._process.engine, memory_range) @property def description(self): """str: String representational description of this telescope.""" attrs = [] next_thing = self while next_thing is not None: if next_thing.type == "int": attrs.append(hex(next_thing.thing)) else: attrs.append(str(next_thing.thing)) next_thing = next_thing.next return ' -> '.join(attrs)
frida_types = (Pointer, Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Char, UChar, Short, UShort, Int, UInt, Long, ULong, Float, Double) all_types = frida_types + (Struct, Padding, StringUTF8, StringUTF16) from .memory import MemoryBytes from .engines.frida.memory import MemoryRange from .process import Process from .symbols import Symbol from .cpu.assembly import AssemblyInstruction from . import common