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