mirror of
https://github.com/frida/frida-python
synced 2026-06-08 14:16:17 +00:00
Improve Tracer so handlers can be defined on the file-system
Also fix Py3k issues.
This commit is contained in:
+277
-41
@@ -1,3 +1,5 @@
|
||||
from frida.core import ModuleFunction
|
||||
import os
|
||||
import fnmatch
|
||||
import sys
|
||||
|
||||
@@ -79,24 +81,46 @@ class TracerProfile(object):
|
||||
r.append(export)
|
||||
return set(r)
|
||||
|
||||
|
||||
class Tracer(object):
|
||||
def __init__(self, profile):
|
||||
def __init__(self, reactor, repository, profile):
|
||||
self._reactor = reactor
|
||||
self._repository = repository
|
||||
self._profile = profile
|
||||
self._script = None
|
||||
|
||||
def start_trace(self, process, sink):
|
||||
def on_message(message, data):
|
||||
sink.on_update([ message['payload'] ])
|
||||
def start_trace(self, process, ui):
|
||||
def on_create(*args):
|
||||
ui.on_trace_handler_create(*args)
|
||||
self._repository.on_create(on_create)
|
||||
|
||||
def on_load(*args):
|
||||
ui.on_trace_handler_load(*args)
|
||||
self._repository.on_load(on_load)
|
||||
|
||||
def on_message(message, data):
|
||||
self._reactor.schedule(lambda: self._process_message(message, data, ui))
|
||||
|
||||
ui.on_trace_progress('resolve')
|
||||
working_set = self._profile.resolve(process)
|
||||
source = self._create_trace_script()
|
||||
ui.on_trace_progress('upload')
|
||||
self._script = process.session.create_script(source)
|
||||
self._script.on("message", on_message)
|
||||
self._script.load()
|
||||
for chunk in [working_set[i:i+1000] for i in range(0, len(working_set), 1000)]:
|
||||
targets = [{ 'absolute_address': hex(export.absolute_address), 'name': export.name } for export in chunk]
|
||||
self._script.post_message(targets)
|
||||
targets = [{
|
||||
'name': export.name,
|
||||
'absolute_address': hex(export.absolute_address),
|
||||
'handler': self._repository.ensure_handler(export)
|
||||
} for export in chunk]
|
||||
self._script.post_message({
|
||||
'to': "/targets",
|
||||
'name': '+add',
|
||||
'payload': {
|
||||
'items': targets
|
||||
}
|
||||
})
|
||||
ui.on_trace_progress('ready')
|
||||
|
||||
return working_set
|
||||
|
||||
@@ -113,6 +137,9 @@ class Tracer(object):
|
||||
var started = new Date();
|
||||
var pending = [];
|
||||
var timer = null;
|
||||
function log(message) {
|
||||
send([new Date().getTime() - started.getTime(), message]);
|
||||
}
|
||||
function processNext() {
|
||||
timer = null;
|
||||
|
||||
@@ -127,42 +154,185 @@ function scheduleNext() {
|
||||
timer = setTimeout(processNext, 0);
|
||||
}
|
||||
};
|
||||
function onStanza(targets) {
|
||||
targets.forEach(function (target) {
|
||||
pending.push(function () {
|
||||
Interceptor.attach(ptr(target.absolute_address), {
|
||||
onEnter: function onEnter(args) {
|
||||
send([new Date().getTime() - started.getTime(), target.name]);
|
||||
}
|
||||
function onStanza(stanza) {
|
||||
if (stanza.to === "/targets") {
|
||||
if (stanza.name === '+add') {
|
||||
var targets = stanza.payload.items;
|
||||
targets.forEach(function (target) {
|
||||
pending.push(function () {
|
||||
eval("var handler = " + target.handler);
|
||||
var state = {};
|
||||
Interceptor.attach(ptr(target.absolute_address), {
|
||||
onEnter: function onEnter(args) {
|
||||
handler.onEnter(log, args, state);
|
||||
},
|
||||
onLeave: function onLeave(retval) {
|
||||
handler.onLeave(log, retval, state);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
scheduleNext();
|
||||
scheduleNext();
|
||||
}
|
||||
}
|
||||
|
||||
recv(onStanza);
|
||||
};
|
||||
recv(onStanza);
|
||||
"""
|
||||
|
||||
def end_trace(self):
|
||||
def _process_message(self, message, data, ui):
|
||||
if message['type'] == 'send':
|
||||
ui.on_trace_events([ message['payload'] ])
|
||||
else:
|
||||
print(message)
|
||||
|
||||
class Repository(object):
|
||||
def __init__(self):
|
||||
self._on_create_callback = None
|
||||
self._on_load_callback = None
|
||||
|
||||
def ensure_handler(self, function):
|
||||
raise NotImplementedError("not implemented")
|
||||
|
||||
def on_create(self, callback):
|
||||
self._on_create_callback = callback
|
||||
|
||||
def on_load(self, callback):
|
||||
self._on_load_callback = callback
|
||||
|
||||
def _notify_create(self, function, handler, source):
|
||||
if self._on_create_callback is not None:
|
||||
self._on_create_callback(function, handler, source)
|
||||
|
||||
def _notify_load(self, function, handler, source):
|
||||
if self._on_load_callback is not None:
|
||||
self._on_load_callback(function, handler, source)
|
||||
|
||||
def _create_stub_handler(self, function):
|
||||
return """\
|
||||
/*
|
||||
* Auto-generated by Frida — please modify to match the signature of %(name)s.
|
||||
* This stub is somewhat dumb. Future verions of Frida could auto-generate
|
||||
* based on OS API references, manpages, etc. (Pull-requests appreciated!)
|
||||
*
|
||||
* For full API reference, see: https://github.com/frida/frida-gum/wiki/Reference:-Script
|
||||
*/
|
||||
|
||||
{
|
||||
/**
|
||||
* Called synchronously when about to call %(name)s.
|
||||
*
|
||||
* @this {object} - Object allowing you to store state for use in onLeave.
|
||||
* @param {function} log - Call this function with a string to be presented to the user.
|
||||
* @param {array} args - Function arguments represented as an array of NativePointer objects.
|
||||
* For example use Memory.readUtf8String(args[0]) if the first argument is a pointer to a C string encoded as UTF-8.
|
||||
* It is also possible to modify arguments by assigning a NativePointer object to an element of this array.
|
||||
* @param {object} state - Object allowing you to keep state across function calls.
|
||||
* Only one JavaScript function will execute at a time, so do not worry about race-conditions.
|
||||
* However, do not use this to store function arguments across onEnter/onLeave, but instead
|
||||
* use "this" which is an object for keeping state local to an invocation.
|
||||
*/
|
||||
onEnter: function onEnter(log, args, state) {
|
||||
log("%(name)s()");
|
||||
},
|
||||
|
||||
/**
|
||||
* Called synchronously when about to return from %(name)s.
|
||||
*
|
||||
* See onEnter for details.
|
||||
*
|
||||
* @this {object} - Object allowing you to access state stored in onEnter.
|
||||
* @param {function} log - Call this function with a string to be presented to the user.
|
||||
* @param {NativePointer} retval - Return value represented as a NativePointer object.
|
||||
* @param {object} state - Object allowing you to keep state across function calls.
|
||||
*/
|
||||
onLeave: function onLeave(log, retval, state) {
|
||||
}
|
||||
}
|
||||
""" % { 'name': function.name }
|
||||
|
||||
class MemoryRepository(Repository):
|
||||
def __init__(self):
|
||||
super(MemoryRepository, self).__init__()
|
||||
self._handlers = {}
|
||||
|
||||
def ensure_handler(self, function):
|
||||
handler = self._handlers.get(function)
|
||||
if handler is None:
|
||||
handler = self._create_stub_handler(function)
|
||||
self._handlers[function] = handler
|
||||
self._notify_create(function, handler, "memory")
|
||||
else:
|
||||
self._notify_load(function, handler, "memory")
|
||||
return handler
|
||||
|
||||
class FileRepository(Repository):
|
||||
def __init__(self):
|
||||
super(FileRepository, self).__init__()
|
||||
self._handlers = {}
|
||||
self._repo_dir = os.path.join(os.getcwd(), "__frida_handlers__")
|
||||
|
||||
def ensure_handler(self, function):
|
||||
handler = self._handlers.get(function)
|
||||
if handler is not None:
|
||||
return handler
|
||||
|
||||
handler_files_to_try = []
|
||||
|
||||
if isinstance(function, ModuleFunction):
|
||||
module_dir = os.path.join(self._repo_dir, function.module.name)
|
||||
module_handler_file = os.path.join(module_dir, function.name + ".js")
|
||||
handler_files_to_try.append(module_handler_file)
|
||||
|
||||
any_module_handler_file = os.path.join(self._repo_dir, function.name + ".js")
|
||||
handler_files_to_try.append(any_module_handler_file)
|
||||
|
||||
for handler_file in handler_files_to_try:
|
||||
if os.path.isfile(handler_file):
|
||||
with open(handler_file, 'r') as f:
|
||||
handler = f.read()
|
||||
self._notify_load(function, handler, handler_file)
|
||||
break
|
||||
|
||||
if handler is None:
|
||||
handler = self._create_stub_handler(function)
|
||||
handler_file = handler_files_to_try[0]
|
||||
handler_dir = os.path.dirname(handler_file)
|
||||
if not os.path.isdir(handler_dir):
|
||||
os.makedirs(handler_dir)
|
||||
with open(handler_file, 'w') as f:
|
||||
f.write(handler)
|
||||
self._notify_create(function, handler, handler_file)
|
||||
|
||||
self._handlers[function] = handler
|
||||
|
||||
return handler
|
||||
|
||||
class UI(object):
|
||||
def on_trace_progress(self, operation):
|
||||
pass
|
||||
|
||||
class IOSink(object):
|
||||
def __init__(self, stream):
|
||||
self._stream = stream
|
||||
def on_trace_events(self, events):
|
||||
pass
|
||||
|
||||
def on_update(self, invocation_events):
|
||||
for timestamp, function_name in invocation_events:
|
||||
self._stream.write("%6d ms\t%s\n" % (timestamp, function_name))
|
||||
def on_trace_handler_create(self, function, handler, source):
|
||||
pass
|
||||
|
||||
STDOUT_SINK = IOSink(sys.stdout)
|
||||
STDERR_SINK = IOSink(sys.stderr)
|
||||
def on_trace_handler_load(self, function, handler, source):
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
import colorama
|
||||
from colorama import Fore, Back, Style
|
||||
import frida
|
||||
from frida.core import Reactor
|
||||
from optparse import OptionParser
|
||||
import sys
|
||||
|
||||
colorama.init(autoreset=True)
|
||||
|
||||
tp = TracerProfileBuilder()
|
||||
def process_builder_arg(option, opt_str, value, parser, method, **kwargs):
|
||||
@@ -181,26 +351,92 @@ def main():
|
||||
(options, args) = parser.parse_args()
|
||||
if len(args) != 1:
|
||||
parser.error("process name or id must be specified")
|
||||
|
||||
t = Tracer(tp.build())
|
||||
try:
|
||||
target = int(args[0])
|
||||
except:
|
||||
target = args[0]
|
||||
try:
|
||||
p = frida.attach(target)
|
||||
except Exception as e:
|
||||
print >> sys.stderr, "Failed to attach: %s" % e
|
||||
sys.exit(1)
|
||||
targets = t.start_trace(p, STDOUT_SINK)
|
||||
print("Started tracing %d functions" % len(targets))
|
||||
print("Press ENTER to stop")
|
||||
raw_input()
|
||||
print("Stopping...")
|
||||
t.stop()
|
||||
p.detach()
|
||||
profile = tp.build()
|
||||
|
||||
class Application(UI):
|
||||
def __init__(self, target, profile):
|
||||
self._target = target
|
||||
self._process = None
|
||||
self._tracer = None
|
||||
self._profile = profile
|
||||
self._status_updated = False
|
||||
self._exit_status = 0
|
||||
self._reactor = Reactor(await_enter)
|
||||
self._reactor.schedule(self._start)
|
||||
|
||||
def run(self):
|
||||
self._reactor.run()
|
||||
self._stop()
|
||||
return self._exit_status
|
||||
|
||||
def _start(self):
|
||||
try:
|
||||
self._update_status("Attaching...")
|
||||
self._process = frida.attach(self._target)
|
||||
except Exception as e:
|
||||
self._update_status("Failed to attach: %s" % e)
|
||||
self._exit_status = 1
|
||||
self._reactor.schedule(self._stop)
|
||||
return
|
||||
self._tracer = Tracer(self._reactor, FileRepository(), self._profile)
|
||||
targets = self._tracer.start_trace(self._process, self)
|
||||
if len(targets) == 1:
|
||||
plural = ""
|
||||
else:
|
||||
plural = "s"
|
||||
self._update_status("Started tracing %d function%s. Press ENTER to stop." % (len(targets), plural))
|
||||
|
||||
def _stop(self):
|
||||
if self._tracer is not None:
|
||||
print("Stopping...")
|
||||
self._tracer.stop()
|
||||
self._tracer = None
|
||||
if self._process is not None:
|
||||
self._process.detach()
|
||||
self._process = None
|
||||
self._reactor.stop()
|
||||
|
||||
def on_trace_progress(self, operation):
|
||||
if operation == 'resolve':
|
||||
self._update_status("Resolving functions...")
|
||||
elif operation == 'upload':
|
||||
self._update_status("Uploading data...")
|
||||
elif operation == 'ready':
|
||||
self._update_status("Ready!")
|
||||
|
||||
def on_trace_events(self, events):
|
||||
self._status_updated = False
|
||||
for timestamp, message in events:
|
||||
print("%6d ms\t%s" % (timestamp, message))
|
||||
|
||||
def on_trace_handler_create(self, function, handler, source):
|
||||
print("%s: Auto-generated handler at \"%s\"" % (function, source))
|
||||
|
||||
def on_trace_handler_load(self, function, handler, source):
|
||||
print("%s: Loaded handler at \"%s\"" % (function, source))
|
||||
|
||||
def _update_status(self, message):
|
||||
if self._status_updated:
|
||||
cursor_position = "\033[A"
|
||||
else:
|
||||
cursor_position = ""
|
||||
print("%-80s" % (cursor_position + Style.BRIGHT + message,))
|
||||
self._status_updated = True
|
||||
|
||||
def await_enter():
|
||||
if sys.version_info[0] >= 3:
|
||||
input()
|
||||
else:
|
||||
raw_input()
|
||||
|
||||
app = Application(target, profile)
|
||||
status = app.run()
|
||||
frida.shutdown()
|
||||
sys.exit(0)
|
||||
sys.exit(status)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
+13
-5
@@ -1,7 +1,9 @@
|
||||
import frida
|
||||
from frida.tracer import Tracer, TracerProfileBuilder, STDOUT_SINK
|
||||
from frida.core import Reactor
|
||||
from frida.tracer import Tracer, TracerProfileBuilder, MemoryRepository, UI
|
||||
import platform
|
||||
import subprocess
|
||||
import threading
|
||||
try:
|
||||
import unittest2 as unittest
|
||||
except:
|
||||
@@ -24,10 +26,16 @@ class TestTracer(unittest.TestCase):
|
||||
cls.target.terminate()
|
||||
|
||||
def test_basics(self):
|
||||
tp = TracerProfileBuilder().include("open*")
|
||||
t = Tracer(tp.build())
|
||||
targets = t.start_trace(self.process, STDOUT_SINK)
|
||||
t.stop()
|
||||
never = threading.Event()
|
||||
reactor = Reactor(never.wait)
|
||||
def start():
|
||||
tp = TracerProfileBuilder().include("open*")
|
||||
t = Tracer(reactor, MemoryRepository(), tp.build())
|
||||
targets = t.start_trace(self.process, UI())
|
||||
t.stop()
|
||||
reactor.stop()
|
||||
reactor.schedule(start)
|
||||
reactor.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user