From 4de31ce8122ae83ae413f70c2c541bfae4984fd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Fri, 25 Sep 2015 19:32:23 +0200 Subject: [PATCH] Avoid logging getting overwritten by the status message changing --- src/frida/application.py | 38 ++++++++++++++++++++++++++++---------- src/frida/discoverer.py | 16 ++++++++-------- src/frida/lsd.py | 6 +++--- src/frida/ps.py | 14 +++++++------- src/frida/repl.py | 37 ++++++++++++++++++++----------------- src/frida/tracer.py | 24 +++++++++++++----------- 6 files changed, 79 insertions(+), 56 deletions(-) diff --git a/src/frida/application.py b/src/frida/application.py index 4e282ab..a013589 100644 --- a/src/frida/application.py +++ b/src/frida/application.py @@ -12,7 +12,7 @@ import threading import time import colorama -from colorama import Style +from colorama import Fore, Style import frida @@ -55,6 +55,11 @@ def await_enter(reactor): except KeyboardInterrupt: print('') +class ConsoleState: + EMPTY = 1 + STATUS = 2 + TEXT = 3 + class ConsoleApplication(object): def __init__(self, run_until_return=await_enter, on_stop=None): colorama.init() @@ -99,7 +104,7 @@ class ConsoleApplication(object): self._resumed = False self._reactor = Reactor(run_until_return, on_stop) self._exit_status = None - self._status_updated = False + self._console_state = ConsoleState.EMPTY if self._device_id is not None and self._device_type is not None: parser.error("-D cannot be used with -U and -R") @@ -206,9 +211,7 @@ class ConsoleApplication(object): self._session = self._device.attach(attach_target) if self._enable_debugger: self._session.enable_debugger() - self._update_status("") - print("Debugger listening on port 5858\n") - self._update_status("Attaching...") + self._print("Debugger listening on port 5858\n") self._session.on('detached', self._schedule_on_session_detached) except Exception as e: if spawning: @@ -222,25 +225,40 @@ class ConsoleApplication(object): def _show_message_if_no_device(self): if self._device is None: - print("Waiting for USB device to appear...") + self._print("Waiting for USB device to appear...") def _on_device_lost(self): if self._exit_status is not None: return - print("Device disconnected.") + self._print("Device disconnected.") self._exit(1) def _on_session_detached(self): - print("Target process terminated.") + self._print("Target process terminated.") self._exit(1) + def _clear_status(self): + if self._console_state == ConsoleState.STATUS: + print("\033[A" + (80 * " ")) + def _update_status(self, message): - if self._status_updated: + if self._console_state == ConsoleState.STATUS: cursor_position = "\033[A" else: cursor_position = "" print("%-80s" % (cursor_position + Style.BRIGHT + message + Style.RESET_ALL,)) - self._status_updated = True + self._console_state = ConsoleState.STATUS + + def _print(self, *args, **kwargs): + print(*args, **kwargs) + self._console_state = ConsoleState.TEXT + + def _log(self, level, text): + if level == 'info': + self._print(text) + else: + color = Fore.RED if level == 'error' else Fore.YELLOW + self._print(color + Style.BRIGHT + text + Style.RESET_ALL) def find_device(type): for device in frida.enumerate_devices(): diff --git a/src/frida/discoverer.py b/src/frida/discoverer.py index c304365..39385ea 100644 --- a/src/frida/discoverer.py +++ b/src/frida/discoverer.py @@ -170,7 +170,7 @@ def main(): self._discoverer.start(self._session, self) def _stop(self): - print("Stopping...") + self._print("Stopping...") self._discoverer.dispose() self._discoverer = None @@ -180,17 +180,17 @@ def main(): def on_sample_result(self, module_functions, dynamic_functions): for module, functions in module_functions.items(): - print(module.name) - print("\t%-10s\t%s" % ("Calls", "Function")) + self._print(module.name) + self._print("\t%-10s\t%s" % ("Calls", "Function")) for function, count in sorted(functions, key=lambda item: item[1], reverse=True): - print("\t%-10d\t%s" % (count, function)) - print("") + self._print("\t%-10d\t%s" % (count, function)) + self._print("") if len(dynamic_functions) > 0: - print("Dynamic functions:") - print("\t%-10s\t%s" % ("Calls", "Function")) + self._print("Dynamic functions:") + self._print("\t%-10s\t%s" % ("Calls", "Function")) for function, count in sorted(dynamic_functions, key=lambda item: item[1], reverse=True): - print("\t%-10d\t%s" % (count, function)) + self._print("\t%-10d\t%s" % (count, function)) self._results_received.set() diff --git a/src/frida/lsd.py b/src/frida/lsd.py index 04a37a7..d4b1e70 100644 --- a/src/frida/lsd.py +++ b/src/frida/lsd.py @@ -22,13 +22,13 @@ def main(): header_format = "%-" + str(id_column_width) + "s " + \ "%-" + str(type_column_width) + "s " + \ "%-" + str(name_column_width) + "s" - print(header_format % ("Id", "Type", "Name")) - print("%s %s %s" % (id_column_width * "-", type_column_width * "-", name_column_width * "-")) + self._print(header_format % ("Id", "Type", "Name")) + self._print("%s %s %s" % (id_column_width * "-", type_column_width * "-", name_column_width * "-")) line_format = "%-" + str(id_column_width) + "s " + \ "%-" + str(type_column_width) + "s " + \ "%-" + str(name_column_width) + "s" for device in sorted(devices, key=cmp_to_key(compare_devices)): - print(line_format % (device.id, device.type, device.name)) + self._print(line_format % (device.id, device.type, device.name)) self._exit(0) def compare_devices(a, b): diff --git a/src/frida/ps.py b/src/frida/ps.py index 6107544..2ef2302 100644 --- a/src/frida/ps.py +++ b/src/frida/ps.py @@ -41,16 +41,16 @@ def main(): header_format = "%" + str(pid_column_width) + "s " + \ "%-" + str(name_column_width) + "s " + \ "%-" + str(identifier_column_width) + "s" - print(header_format % ("PID", "Name", "Identifier")) - print("%s %s %s" % (pid_column_width * "-", name_column_width * "-", identifier_column_width * "-")) + self._print(header_format % ("PID", "Name", "Identifier")) + self._print("%s %s %s" % (pid_column_width * "-", name_column_width * "-", identifier_column_width * "-")) line_format = "%" + str(pid_column_width) + "s " + \ "%-" + str(name_column_width) + "s " + \ "%-" + str(identifier_column_width) + "s" for app in sorted(applications, key=cmp_to_key(compare_applications)): if app.pid == 0: - print(line_format % ("-", app.name, app.identifier)) + self._print(line_format % ("-", app.name, app.identifier)) else: - print(line_format % (app.pid, app.name, app.identifier)) + self._print(line_format % (app.pid, app.name, app.identifier)) else: try: processes = self._device.enumerate_processes() @@ -61,11 +61,11 @@ def main(): pid_column_width = max(map(lambda p: len("%d" % p.pid), processes)) name_column_width = max(map(lambda p: len(p.name), processes)) header_format = "%" + str(pid_column_width) + "s %s" - print(header_format % ("PID", "Name")) - print("%s %s" % (pid_column_width * "-", name_column_width * "-")) + self._print(header_format % ("PID", "Name")) + self._print("%s %s" % (pid_column_width * "-", name_column_width * "-")) line_format = "%" + str(pid_column_width) + "d %s" for process in sorted(processes, key=cmp_to_key(compare_processes)): - print(line_format % (process.pid, process.name)) + self._print(line_format % (process.pid, process.name)) self._exit(0) def compare_applications(a, b): diff --git a/src/frida/repl.py b/src/frida/repl.py index 9a6947b..b2ae6cf 100644 --- a/src/frida/repl.py +++ b/src/frida/repl.py @@ -56,7 +56,7 @@ def main(): if self._spawned_argv is not None: self._update_status("Spawned `{command}`. Use %resume to let the main thread start executing!".format(command=" ".join(self._spawned_argv))) else: - sys.stdout.write("\033[A") + self._clear_status() self._ready.set() def _on_stop(self): @@ -74,6 +74,7 @@ def main(): def _load_script(self): self._seqno += 1 script = self._session.create_script(name="repl%d" % self._seqno, source=self._create_repl_script()) + script.set_log_handler(self._log) self._unload_script() self._script = script def on_message(message, data): @@ -125,7 +126,7 @@ def main(): eventloop.close() except EOFError: # An extra newline after EOF to exit the REPL cleanly - print("\nThank you for using Frida!") + self._print("\nThank you for using Frida!") return except KeyboardInterrupt: line = "" @@ -140,17 +141,16 @@ def main(): self._print_help(expression) except JavaScriptError as e: error = e.error - sys.stdout.write(Fore.RED + Style.BRIGHT + error['name'] + Style.RESET_ALL + ": " + error['message'] + "\n") - sys.stdout.flush() + self._print(Fore.RED + Style.BRIGHT + error['name'] + Style.RESET_ALL + ": " + error['message']) except frida.InvalidOperationError: return elif expression.startswith("%"): self._do_magic(expression[1:].rstrip()) elif expression in ("exit", "quit", "q"): - print("Thank you for using Frida!") + self._print("Thank you for using Frida!") return elif expression == "help": - print("Help: #TODO :)") + self._print("Help: #TODO :)") else: self._eval_and_print(expression) @@ -168,11 +168,10 @@ def main(): output = Fore.RED + Style.BRIGHT + error['name'] + Style.RESET_ALL + ": " + error['message'] except frida.InvalidOperationError: return - sys.stdout.write(output + "\n") - sys.stdout.flush() + self._print(output) def _print_startup_message(self): - print(""" _____ + self._print(""" _____ (_____) | | Frida {version} - A world-class dynamic instrumentation framework | | @@ -219,7 +218,7 @@ def main(): help_text += "Text: %s\n" % self._evaluate("%s.toString()" % obj_to_identify)[1] help_text += "Docstring: #TODO :)" - print(help_text) + self._print(help_text) # Negative means at least abs(val) - 1 _magic_command_args = { @@ -238,8 +237,8 @@ def main(): required_args = self._magic_command_args.get(command) if required_args == None: - print("Unknown command: {}".format(command)) - print("Valid commands: {}".format(", ".join(self._magic_command_args.keys()))) + self._print("Unknown command: {}".format(command)) + self._print("Valid commands: {}".format(", ".join(self._magic_command_args.keys()))) return atleast_args = False @@ -249,7 +248,7 @@ def main(): if (not atleast_args and len(args) != required_args) or \ (atleast_args and len(args) < required_args): - print("{cmd} command expects {atleast}{n} argument{s}".format( + self._print("{cmd} command expects {atleast}{n} argument{s}".format( cmd=command, atleast='atleast ' if atleast_args else '', n=required_args, s='' if required_args == 1 else ' ')) return @@ -289,7 +288,7 @@ def main(): if result[0] is None: return True else: - print("Failed to load script: {error}".format(error=result[0])) + self._print("Failed to load script: {error}".format(error=result[0])) return False def _create_prompt(self): @@ -331,13 +330,17 @@ def main(): raise JavaScriptError(stanza['payload']) def _process_message(self, message, data): - if message['type'] == 'send': + message_type = message['type'] + if message_type == 'send': stanza = message['payload'] with self._response_cond: self._response_data = (stanza, data) self._response_cond.notify() + elif message_type == 'error': + text = message.get('stack', message['description']) + self._log('error', text) else: - print("message:", message, "data:", data) + self._print("message:", message, "data:", data) def _create_repl_script(self): user_script = "" @@ -485,7 +488,7 @@ def main(): continue yield Completion(key, -len(before_dot)) except Exception as e: - print(e) + self._print(e) def _get_keys(self, code): return sorted( diff --git a/src/frida/tracer.py b/src/frida/tracer.py index f3a50e9..6be59ad 100644 --- a/src/frida/tracer.py +++ b/src/frida/tracer.py @@ -67,8 +67,9 @@ class TracerProfile(object): def __init__(self, spec): self._spec = spec - def resolve(self, session): + def resolve(self, session, log_handler=None): script = session.create_script(name="profile-resolver", source=self._create_resolver_script()) + script.set_log_handler(log_handler) result = [None, None] completed = threading.Event() def on_message(message, data): @@ -922,11 +923,12 @@ function regExpEscape(s) { """ class Tracer(object): - def __init__(self, reactor, repository, profile): + def __init__(self, reactor, repository, profile, log_handler=None): self._reactor = reactor self._repository = repository self._profile = profile self._script = None + self._log_handler = log_handler def start_trace(self, session, ui): def on_create(*args): @@ -955,9 +957,10 @@ class Tracer(object): self._reactor.schedule(lambda: self._process_message(message, data, ui)) ui.on_trace_progress('resolve') - working_set = self._profile.resolve(session) + working_set = self._profile.resolve(session, log_handler=self._log_handler) ui.on_trace_progress('instrument') self._script = session.create_script(name="tracer", source=self._create_trace_script()) + self._script.set_log_handler(self._log_handler) 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)]: @@ -1399,11 +1402,11 @@ def main(): return True def _start(self): - self._tracer = Tracer(self._reactor, FileRepository(), self._profile) + self._tracer = Tracer(self._reactor, FileRepository(), self._profile, log_handler=self._log) self._targets = self._tracer.start_trace(self._session, self) def _stop(self): - print("Stopping...") + self._print("Stopping...") self._tracer.stop() self._tracer = None @@ -1428,24 +1431,23 @@ def main(): self._resume() def on_trace_error(self, error): - print(Fore.RED + Style.BRIGHT + "Error" + Style.RESET_ALL + ": " + error['message']) + self._print(Fore.RED + Style.BRIGHT + "Error" + Style.RESET_ALL + ": " + error['message']) def on_trace_events(self, events): - self._status_updated = False no_attributes = Style.RESET_ALL for timestamp, thread_id, depth, target_address, message in events: indent = depth * " | " attributes = self._get_attributes(thread_id) if thread_id != self._last_event_tid: - print("%s /* TID 0x%x */%s" % (attributes, thread_id, Style.RESET_ALL)) + self._print("%s /* TID 0x%x */%s" % (attributes, thread_id, Style.RESET_ALL)) self._last_event_tid = thread_id - print("%6d ms %s%s%s%s" % (timestamp, attributes, indent, message, no_attributes)) + self._print("%6d ms %s%s%s%s" % (timestamp, attributes, indent, message, no_attributes)) def on_trace_handler_create(self, function, handler, source): - print("%s: Auto-generated handler at \"%s\"" % (function, source)) + self._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)) + self._print("%s: Loaded handler at \"%s\"" % (function, source)) def _get_attributes(self, thread_id): attributes = self._attributes_by_thread_id.get(thread_id, None)