Files
frida-frida-python/src/frida/core.py
T
Ole André Vadla Ravnås 490fbf20b8 Update to the new core API
2018-05-05 03:25:14 +02:00

602 lines
19 KiB
Python

# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function
import _frida
import bisect
import fnmatch
import json
import numbers
import sys
import threading
import traceback
class DeviceManager(object):
def __init__(self, impl):
self._impl = impl
def __repr__(self):
return repr(self._impl)
def enumerate_devices(self):
return [Device(device) for device in self._impl.enumerate_devices()]
def add_remote_device(self, host):
return Device(self._impl.add_remote_device(host))
def remove_remote_device(self, host):
self._impl.remove_remote_device(host)
def get_device(self, device_id):
devices = self._impl.enumerate_devices()
if device_id is None:
return Device(devices[0])
for device in devices:
if device.id == device_id:
return Device(device)
raise _frida.InvalidArgumentError("unable to find device with id %s" % device_id)
def on(self, signal, callback):
self._impl.on(signal, callback)
def off(self, signal, callback):
self._impl.off(signal, callback)
class Device(object):
def __init__(self, device):
self.id = device.id
self.name = device.name
self.icon = device.icon
self.type = device.type
self._impl = device
def __repr__(self):
return repr(self._impl)
def get_frontmost_application(self):
return self._impl.get_frontmost_application()
def enumerate_applications(self):
return self._impl.enumerate_applications()
def enumerate_processes(self):
return self._impl.enumerate_processes()
def get_process(self, process_name):
process_name_lc = process_name.lower()
matching = [process for process in self._impl.enumerate_processes() if fnmatch.fnmatchcase(process.name.lower(), process_name_lc)]
if len(matching) == 1:
return matching[0]
elif len(matching) > 1:
raise _frida.ProcessNotFoundError("ambiguous name; it matches: %s" % ", ".join(["%s (pid: %d)" % (process.name, process.pid) for process in matching]))
else:
raise _frida.ProcessNotFoundError("unable to find process with name '%s'" % process_name)
def enable_spawn_gating(self):
return self._impl.enable_spawn_gating()
def disable_spawn_gating(self):
return self._impl.disable_spawn_gating()
def enumerate_pending_spawn(self):
return self._impl.enumerate_pending_spawn()
def enumerate_pending_children(self):
return self._impl.enumerate_pending_children()
def spawn(self, program, argv=None, envp=None, env=None, cwd=None, stdio=None, **kwargs):
if not isinstance(program, string_types):
argv = program
if len(argv) == 1:
argv = None
program = argv[0]
aux_options = kwargs
return self._impl.spawn(program, argv, envp, env, cwd, stdio, aux_options)
def input(self, target, data):
self._impl.input(self._pid_of(target), data)
def resume(self, target):
self._impl.resume(self._pid_of(target))
def kill(self, target):
self._impl.kill(self._pid_of(target))
def attach(self, target):
return Session(self._impl.attach(self._pid_of(target)))
def inject_library_file(self, target, path, entrypoint, data):
return self._impl.inject_library_file(self._pid_of(target), path, entrypoint, data)
def inject_library_blob(self, target, blob, entrypoint, data):
return self._impl.inject_library_blob(self._pid_of(target), blob, entrypoint, data)
def on(self, signal, callback):
self._impl.on(signal, callback)
def off(self, signal, callback):
self._impl.off(signal, callback)
def _pid_of(self, target):
if isinstance(target, numbers.Number):
return target
else:
return self.get_process(target).pid
class FunctionContainer(object):
def __init__(self):
self._functions = {}
"""
@param address is relative to container
"""
def ensure_function(self, address):
f = self._functions.get(address)
if f is not None:
return f
return self._do_ensure_function(address)
def _do_ensure_function(self, address):
raise NotImplementedError("not implemented")
class Session(FunctionContainer):
def __init__(self, impl):
super(Session, self).__init__()
self._impl = impl
self._modules = None
self._module_map = None
self._script = None
def detach(self):
self._impl.detach()
def enable_child_gating(self):
self._impl.enable_child_gating()
def disable_child_gating(self):
self._impl.disable_child_gating()
def enumerate_modules(self):
if self._modules is None:
raw_modules = self._get_api().enumerate_modules()
self._modules = [Module(data['name'], int(data['base'], 16), data['size'], data['path'], self) for data in raw_modules]
return self._modules
def prefetch_modules(self):
modules = self.enumerate_modules()
pending = [m for m in modules if m._exports is None]
batches = self._get_api().enumerate_exports([m.path for m in pending])
for i, raw_exports in enumerate(batches):
pending[i]._update_exports(raw_exports)
"""
@param protection example '--x'
"""
def enumerate_ranges(self, protection):
raw_ranges = self._get_api().enumerate_ranges(protection)
return [Range(int(data['base'], 16), data['size'], data['protection']) for data in raw_ranges]
def find_base_address(self, module_name):
raw_base_address = self._get_api().find_base_address(module_name)
return int(raw_base_address, 16)
def read_bytes(self, address, size):
return self._get_api().read_byte_array("0x%x" % address, size)
def write_bytes(self, address, data):
self._get_api().write_byte_array("0x%x" % address, [x for x in iterbytes(data)])
def read_utf8(self, address, length=-1):
return self._get_api().read_utf8("0x%x" % address, length)
def write_utf8(self, address, string):
self._get_api().write_utf8("0x%x" % address, string)
def create_script(self, *args, **kwargs):
return Script(self._impl.create_script(*args, **kwargs))
def create_script_from_bytes(self, *args, **kwargs):
return Script(self._impl.create_script_from_bytes(*args, **kwargs))
def compile_script(self, *args, **kwargs):
return self._impl.compile_script(*args, **kwargs)
def enable_debugger(self, *args, **kwargs):
self._impl.enable_debugger(*args, **kwargs)
def disable_debugger(self):
self._impl.disable_debugger()
def enable_jit(self):
self._impl.enable_jit()
def on(self, signal, callback):
self._impl.on(signal, callback)
def off(self, signal, callback):
self._impl.off(signal, callback)
def _get_api(self):
return self._get_script().exports
def _get_script(self):
if self._script is None:
self._script = self.create_script(name="session", source=self._create_session_script())
self._script.on('message', self._on_message)
self._script.load()
return self._script
def _on_message(self, message, data):
print("[session]", message, data)
def _create_session_script(self):
return """\
"use strict";
rpc.exports = {
enumerateModules: function () {
return Process.enumerateModulesSync();
},
enumerateExports: function (modulePaths) {
return modulePaths.map(function (modulePath) {
return Module.enumerateExportsSync(modulePath);
});
},
enumerateRanges: function (protection) {
return Process.enumerateRangesSync(protection);
},
findBaseAddress: function (moduleName) {
var address = Module.findBaseAddress(moduleName);
return (address !== null) ? address.toString() : "0";
},
readByteArray: function (address, size) {
return Memory.readByteArray(ptr(address), size);
},
writeByteArray: function (address, data) {
var base = ptr(address);
for (var i = 0; i !== data.length; i++) {
Memory.writeU8(base.add(i), data[i]);
}
},
readUtf8: function (address, length) {
return Memory.readUtf8String(ptr(address), length);
},
writeUtf8: function (address, string) {
Memory.writeUtf8String(ptr(address), string);
},
enumerateModuleExports: function (modulePath) {
return Module.enumerateExportsSync(modulePath).filter(function (e) {
return e.type === 'function';
});
},
enumerateModuleRanges: function (modulePath, protection) {
return Module.enumerateRangesSync(modulePath, protection);
}
};
"""
def _do_ensure_function(self, absolute_address):
if self._module_map is None:
self._module_map = ModuleMap(self.enumerate_modules())
m = self._module_map.lookup(absolute_address)
if m is not None:
f = m.ensure_function(absolute_address - m.base_address)
else:
f = Function("dsub_%x" % absolute_address, absolute_address)
self._functions[absolute_address] = f
return f
def __getattr__(self, attr):
if attr == 'session':
raise KeyError("Please update your code from `.session.create_script()` to `.create_script()`")
else:
return getattr(super(Session, self), attr)
class Script(object):
def __init__(self, impl):
self.exports = ScriptExports(self)
self._impl = impl
self._on_message_callbacks = []
self._log_handler = self._on_log
self._pending = {}
self._next_request_id = 1
self._cond = threading.Condition()
impl.on('destroyed', self._on_destroyed)
impl.on('message', self._on_message)
def __repr__(self):
return repr(self._impl)
def load(self):
self._impl.load()
def unload(self):
self._impl.unload()
def post(self, message, **kwargs):
raw_message = json.dumps(message)
self._impl.post(raw_message, **kwargs)
def on(self, signal, callback):
if signal == 'message':
self._on_message_callbacks.append(callback)
else:
self._impl.on(signal, callback)
def off(self, signal, callback):
if signal == 'message':
self._on_message_callbacks.remove(callback)
else:
self._impl.off(signal, callback)
def set_log_handler(self, handler):
if handler is not None:
self._log_handler = handler
else:
self._log_handler = self._on_log
def _rpc_request(self, *args):
result = [False, None, None]
def on_complete(value, error):
with self._cond:
result[0] = True
result[1] = value
result[2] = error
self._cond.notify_all()
with self._cond:
request_id = self._next_request_id
self._next_request_id += 1
self._pending[request_id] = on_complete
message = ['frida:rpc', request_id]
message.extend(args)
try:
self.post(message)
except Exception as e:
del self._pending[request_id]
raise
with self._cond:
while not result[0]:
self._cond.wait()
if result[2] is not None:
raise result[2]
return result[1]
def _on_rpc_message(self, request_id, operation, params, data):
if operation in ('ok', 'error'):
callback = self._pending.pop(request_id)
value = None
error = None
if operation == 'ok':
value = params[0] if data is None else data
else:
error = RPCException(*params[0:3])
callback(value, error)
def _on_destroyed(self):
while True:
next_pending = None
with self._cond:
pending_ids = list(self._pending.keys())
if len(pending_ids) > 0:
next_pending = self._pending.pop(pending_ids[0])
if next_pending is None:
break
next_pending(None, _frida.InvalidOperationError('script is destroyed'))
def _on_message(self, raw_message, data):
message = json.loads(raw_message)
mtype = message['type']
payload = message.get('payload', None)
if mtype == 'log':
level = message['level']
text = payload
self._log_handler(level, text)
elif mtype == 'send' and isinstance(payload, list) and payload[0] == 'frida:rpc':
request_id = payload[1]
operation = payload[2]
params = payload[3:]
self._on_rpc_message(request_id, operation, params, data)
else:
for callback in self._on_message_callbacks[:]:
try:
callback(message, data)
except:
traceback.print_exc()
def _on_log(self, level, text):
if level == 'info':
print(text, file=sys.stdout)
else:
print(text, file=sys.stderr)
class RPCException(Exception):
def __str__(self):
return self.args[2] if len(self.args) >= 3 else self.args[0]
class ScriptExports(object):
def __init__(self, script):
self._script = script
def __getattr__(self, name):
script = self._script
js_name = _to_camel_case(name)
def method(*args):
return script._rpc_request('call', js_name, args)
return method
class Module(FunctionContainer):
def __init__(self, name, base_address, size, path, session):
super(Module, self).__init__()
self.name = name
self.base_address = base_address
self.size = size
self.path = path
self._exports = None
self._session = session
def __repr__(self):
return "Module(name=\"%s\", base_address=0x%x, size=%d, path=\"%s\")" % (self.name, self.base_address, self.size, self.path)
def __hash__(self):
return self.base_address.__hash__()
def __cmp__(self, other):
return self.base_address.__cmp__(other.base_address)
def __eq__(self, other):
return self.base_address == other.base_address
def __ne__(self, other):
return self.base_address != other.base_address
def enumerate_exports(self):
if self._exports is None:
raw_exports = self._session._get_api().enumerate_module_exports(self.path)
self._update_exports(raw_exports)
return self._exports
"""
@param protection example '--x'
"""
def enumerate_ranges(self, protection):
raw_ranges = self._session._get_script().exports.enumerate_module_ranges(self.path, protection)
return [Range(int(data['base'], 16), data['size'], data['protection']) for data in raw_ranges]
def _update_exports(self, exports):
self._exports = []
for export in exports:
relative_address = int(export["address"], 16) - self.base_address
mf = ModuleFunction(self, export["name"], relative_address, True)
self._exports.append(mf)
self._functions[relative_address] = mf
def _do_ensure_function(self, relative_address):
self.enumerate_exports()
mf = self._functions.get(relative_address)
if mf is None:
mf = ModuleFunction(self, "sub_%x" % relative_address, relative_address, False)
self._functions[relative_address] = mf
return mf
class Function(object):
def __init__(self, name, absolute_address):
self.name = name
self.absolute_address = absolute_address
def __str__(self):
return self.name
def __repr__(self):
return "Function(name=\"%s\", absolute_address=0x%x)" % (self.name, self.absolute_address)
def __hash__(self):
return self.absolute_address.__hash__()
def __cmp__(self, other):
return self.absolute_address.__cmp__(other.absolute_address)
def __eq__(self, other):
return self.absolute_address == other.absolute_address
def __ne__(self, other):
return self.absolute_address != other.absolute_address
class ModuleFunction(Function):
def __init__(self, module, name, relative_address, exported):
super(ModuleFunction, self).__init__(name, module.base_address + relative_address)
self.module = module
self.relative_address = relative_address
self.exported = exported
def __repr__(self):
return "ModuleFunction(module=\"%s\", name=\"%s\", relative_address=0x%x)" % (self.module.name, self.name, self.relative_address)
class ObjCMethod(Function):
def __init__(self, mtype, cls, method, address):
self.mtype = mtype
self.cls = cls
self.method = method
self.address = address
super(ObjCMethod, self).__init__(self.display_name(), address)
def display_name(self):
return '{mtype}[{cls} {method}]'.format(mtype=self.mtype, cls=self.cls, method=self.method)
def __repr__(self):
return "ObjCMethod(mtype=\"%s\", cls=\"%s\", method=\"%s\", address=0x%x)" % (self.mtype, self.cls, self.method, self.address)
class Range(object):
def __init__(self, base_address, size, protection):
self.base_address = base_address
self.size = size
self.protection = protection
def __repr__(self):
return "Range(base_address=0x%x, size=%s, protection='%s')" % (self.base_address, self.size, self.protection)
class Error(Exception):
pass
class AddressMap(object):
def __init__(self, items, get_address, get_size):
self._items = sorted(items, key=get_address)
self._indices = [ get_address(item) for item in self._items ]
self._get_address = get_address
self._get_size = get_size
def lookup(self, address):
index = bisect.bisect(self._indices, address)
if index == 0:
return None
item = self._items[index - 1]
if address >= self._get_address(item) + self._get_size(item):
return None
return item
class ModuleMap(AddressMap):
def __init__(self, modules):
super(ModuleMap, self).__init__(modules, lambda m: m.base_address, lambda m: m.size)
class FunctionMap(AddressMap):
def __init__(self, functions, get_address=lambda f: f.absolute_address):
super(FunctionMap, self).__init__(functions, get_address, lambda f: 1)
def _to_camel_case(name):
result = ""
uppercase_next = False
for c in name:
if c == '_':
uppercase_next = True
elif uppercase_next:
result += c.upper()
uppercase_next = False
else:
result += c.lower()
return result
if sys.version_info[0] >= 3:
string_types = str,
iterbytes = lambda x: iter(x)
else:
string_types = basestring,
def iterbytes(data):
return (ord(char) for char in data)