From 93a6dbbe7e9d954c0bddb9c648c003dbf772ccfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Fri, 17 Jul 2015 01:16:22 +0200 Subject: [PATCH] Improve tracer error-handling and port embedded JS to ES6 --- src/frida/discoverer.py | 28 +++++------ src/frida/repl.py | 14 +++--- src/frida/tracer.py | 108 +++++++++++++++++++++++++--------------- 3 files changed, 89 insertions(+), 61 deletions(-) diff --git a/src/frida/discoverer.py b/src/frida/discoverer.py index 1965235..c304365 100644 --- a/src/frida/discoverer.py +++ b/src/frida/discoverer.py @@ -34,32 +34,32 @@ class Discoverer(object): }) def _create_discover_script(self): - return """\ + return """"use strict";\ + function Sampler() { - var threadIds = []; - var result = {}; + let threadIds = []; + let result = {}; function onStanza(stanza) { if (stanza.to === "/sampler") { - if (stanza.name === '+stop') { + if (stanza.name === '+stop') stop(); - } } recv(onStanza); } - this.start = function () { + this.start = () => { threadIds = []; Process.enumerateThreads({ - onMatch: function (thread) { + onMatch(thread) { threadIds.push(thread.id); }, - onComplete: function () { - threadIds.forEach(function (threadId) { + onComplete() { + threadIds.forEach(threadId => { Stalker.follow(threadId, { events: { call: true }, - onCallSummary: function (summary) { + onCallSummary(summary) { for (var address in summary) { if (summary.hasOwnProperty(address)) { var count = result[address] || 0; @@ -79,10 +79,10 @@ function Sampler() { }); } }); - } + }; function stop() { - threadIds.forEach(function (threadId) { + threadIds.forEach(threadId => { Stalker.unfollow(threadId); }); threadIds = []; @@ -100,8 +100,8 @@ function Sampler() { recv(onStanza); }; -sampler = new Sampler(); -setTimeout(function () { sampler.start(); }, 0); +const sampler = new Sampler(); +setTimeout(() => { sampler.start(); }, 0); """ def _process_message(self, message, data, session, ui): diff --git a/src/frida/repl.py b/src/frida/repl.py index 93a927a..8f10429 100644 --- a/src/frida/repl.py +++ b/src/frida/repl.py @@ -265,10 +265,10 @@ def main(): self._reload() elif command == 'time': self._eval_and_print(''' - (function() {{ - var _startTime = Date.now(); - var _result = eval({expression}); - var _endTime = Date.now(); + (() => {{ + const _startTime = Date.now(); + const _result = eval({expression}); + const _endTime = Date.now(); console.log('Time: ' + (_endTime - _startTime).toLocaleString() + ' ms.'); return _result; }})();'''.format(expression=json.dumps(' '.join(args)))) @@ -346,7 +346,7 @@ def main(): return user_script + """\ -(function () { +(() => { "use strict"; function onEvaluate(expression) { @@ -386,7 +386,7 @@ def main(): return false; if (v instanceof Array) // Object returned by Memory.readByteArray() isn't an Array return false; - for (var i = 0; i !== v.length; i++) { + for (let i = 0; i !== v.length; i++) { if (typeof v[i] !== 'number') return false; } @@ -453,7 +453,7 @@ def main(): try: if encountered_dot: - for key in self._get_keys("""(function(o) { + for key in self._get_keys("""(o => { "use strict"; let k = []; try { diff --git a/src/frida/tracer.py b/src/frida/tracer.py index 315b7c9..64a5cfd 100644 --- a/src/frida/tracer.py +++ b/src/frida/tracer.py @@ -112,7 +112,7 @@ class TracerProfile(object): def _create_resolver_script(self): return r""""use strict"; -recv(function (spec) { +recv(spec => { try { send({ name: '+result', @@ -127,7 +127,7 @@ recv(function (spec) { }); function resolve(spec) { - const workingSet = spec.reduce(function (workingSet, item) { + const workingSet = spec.reduce((workingSet, item) => { const operation = item[0]; const scope = item[1]; const param = item[2]; @@ -322,7 +322,7 @@ function getObjCState() { method_getImplementation: 1, sel_getName: 1 }; - Object.keys(apiSpec).forEach(function (name) { + Object.keys(apiSpec).forEach(name => { const funPtr = Module.findExportByName("libobjc.A.dylib", name); const argCount = apiSpec[name]; const argTypes = []; @@ -395,7 +395,7 @@ function allFunctionExports(module) { const moduleId = allModules().indexOf(module); module._cachedFunctionExports = Module.enumerateExportsSync(module.path) .filter(isFunctionExport) - .map(function (e) { + .map(e => { e.module = moduleId; return e; }); @@ -443,7 +443,7 @@ const twoStarNoDot = '(?:(?!(?:\\\/|^)\\.).)*?'; const reSpecials = charSet('().*{}+?[]^$\\!'); function charSet(s) { - return s.split("").reduce(function (set, c) { + return s.split("").reduce((set, c) => { set[c] = true; return set; }, {}); @@ -455,10 +455,10 @@ function ext(a, b) { a = a || {}; b = b || {}; const t = {}; - Object.keys(b).forEach(function (k) { + Object.keys(b).forEach(k => { t[k] = b[k]; }); - Object.keys(a).forEach(function (k) { + Object.keys(a).forEach(k => { t[k] = a[k]; }); return t; @@ -480,7 +480,7 @@ function Minimatch(pattern, options) { this.make(); } -Minimatch.prototype.make = function () { +Minimatch.prototype.make = () => { if (this._made) return; @@ -500,22 +500,22 @@ Minimatch.prototype.make = function () { let set = this.globSet = [this.pattern]; - set = this.globParts = set.map(function (s) { + set = this.globParts = set.map(s => { return s.split(slashSplit); }); - set = set.map(function (s, si, set) { + set = set.map((s, si, set) => { return s.map(this.parse, this); }, this); - set = set.filter(function (s) { + set = set.filter(s => { return s.indexOf(false) === -1; }); this.set = set; }; -Minimatch.prototype.parseNegate = function () { +Minimatch.prototype.parseNegate = () => { const pattern = this.pattern; let negate = false; const options = this.options; @@ -535,7 +535,7 @@ Minimatch.prototype.parseNegate = function () { }; const SUBPARSE = {}; -Minimatch.prototype.parse = function (pattern, isSub) { +Minimatch.prototype.parse = (pattern, isSub) => { const options = this.options; if (!options.noglobstar && pattern === '**') @@ -726,7 +726,7 @@ Minimatch.prototype.parse = function (pattern, isSub) { for (let pl = patternListStack.pop(); pl; pl = patternListStack.pop()) { let tail = re.slice(pl.reStart + 3); - tail = tail.replace(/((?:\\{2})*)(\\?)\|/g, function (_, $1, $2) { + tail = tail.replace(/((?:\\{2})*)(\\?)\|/g, (_, $1, $2) => { if (!$2) $2 = '\\'; @@ -771,7 +771,7 @@ Minimatch.prototype.parse = function (pattern, isSub) { return regExp; }; -Minimatch.prototype.makeRe = function () { +Minimatch.prototype.makeRe = () => { if (this.regexp || this.regexp === false) return this.regexp; @@ -786,8 +786,8 @@ Minimatch.prototype.makeRe = function () { const twoStar = options.noglobstar ? star : options.dot ? twoStarDot : twoStarNoDot; const flags = options.nocase ? 'i' : ''; - let re = set.map(function (pattern) { - return pattern.map(function (p) { + let re = set.map(pattern => { + return pattern.map(p => { return (p === GLOBSTAR) ? twoStar : (typeof p === 'string') ? regExpEscape(p) : p._src; }).join('\\\/'); }).join('|'); @@ -805,7 +805,7 @@ Minimatch.prototype.makeRe = function () { return this.regexp; }; -Minimatch.prototype.match = function (f, partial) { +Minimatch.prototype.match = (f, partial) => { if (this.comment) return false; if (this.empty) @@ -844,7 +844,7 @@ Minimatch.prototype.match = function (f, partial) { return this.negate; }; -Minimatch.prototype.matchOne = function (file, pattern, partial) { +Minimatch.prototype.matchOne = (file, pattern, partial) => { const options = this.options; let fi, pi, fl, pl; @@ -943,6 +943,7 @@ class Tracer(object): 'name': '+update', 'payload': { 'items': [{ + 'name': function.name, 'absolute_address': hex(function.absolute_address), 'handler': handler }] @@ -961,6 +962,7 @@ class Tracer(object): self._script.load() for chunk in [working_set[i:i+1000] for i in range(0, len(working_set), 1000)]: targets = [{ + 'name': function.name, 'absolute_address': hex(function.absolute_address), 'handler': self._repository.ensure_handler(function) } for function in chunk] @@ -989,12 +991,13 @@ class Tracer(object): self._script = None def _create_trace_script(self): - return """\ -var started = new Date(); -var pending = []; -var timer = null; -var handlers = {}; -var state = {}; + return """"use strict"; + +const started = Date.now(); +const pending = []; +let timer = null; +const handlers = {}; +const state = {}; function onStanza(stanza) { if (stanza.to === "/targets") { if (stanza.name === '+add') { @@ -1009,29 +1012,31 @@ function onStanza(stanza) { recv(onStanza); } function add(targets) { - targets.forEach(function (target) { - var targetAddress = target.absolute_address; - eval("var handler = " + target.handler); + targets.forEach(target => { + const handler = parseHandler(target); + if (handler === null) + return; + const targetAddress = target.absolute_address; target = null; - var h = [handler]; + const h = [handler]; handlers[targetAddress] = h; function log(message) { send({ from: "/events", name: '+add', payload: { - items: [[new Date().getTime() - started.getTime(), targetAddress, message]] + items: [[Date.now() - started, targetAddress, message]] } }); } - pending.push(function attachToTarget() { + pending.push(() => { Interceptor.attach(ptr(targetAddress), { - onEnter: function onEnter(args) { + onEnter(args) { h[0].onEnter.call(this, log, args, state); }, - onLeave: function onLeave(retval) { + onLeave(retval) { h[0].onLeave.call(this, log, retval, state); } }); @@ -1041,13 +1046,26 @@ function add(targets) { scheduleNext(); } function update(targets) { - targets.forEach(function (target) { - eval("var handler = " + target.handler); - handlers[target.absolute_address][0] = handler; + targets.forEach(target => { + handlers[target.absolute_address][0] = parseHandler(target); }); } +function parseHandler(target) { + try { + return (1, eval)("(" + target.handler + ")"); + } catch (e) { + send({ + from: "/targets", + name: '+error', + payload: { + message: "Invalid handler for '" + target.name + "': " + e.message + } + }); + return null; + } +} function start() { - pending.push(function acknowledgeStart() { + pending.push(() => { send({ from: "/targets", name: '+started', @@ -1065,7 +1083,7 @@ function processNext() { timer = null; if (pending.length > 0) { - var work = pending.shift(); + const work = pending.shift(); work(); scheduleNext(); } @@ -1090,6 +1108,9 @@ recv(onStanza); elif stanza['from'] == "/targets" and stanza['name'] == '+started': ui.on_trace_progress('ready') handled = True + elif stanza['from'] == "/targets" and stanza['name'] == '+error': + ui.on_trace_error(stanza['payload']) + handled = True if not handled: print(message) @@ -1192,7 +1213,7 @@ class Repository(object): * 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) { + onEnter(log, args, state) { log(%(log_str)s); }, @@ -1206,7 +1227,7 @@ class Repository(object): * @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) { + onLeave(log, retval, state) { } } """ % {"display_name": display_name, "log_str": log_str} @@ -1297,6 +1318,9 @@ class UI(object): def on_trace_progress(self, operation): pass + def on_trace_error(self, error): + pass + def on_trace_events(self, events): pass @@ -1308,6 +1332,7 @@ class UI(object): def main(): + from colorama import Fore, Style from frida.application import ConsoleApplication, input_with_timeout class TracerApplication(ConsoleApplication, UI): @@ -1372,6 +1397,9 @@ def main(): self._update_status("Started tracing %d function%s. Press Ctrl+C to stop." % (len(self._targets), plural)) self._resume() + def on_trace_error(self, error): + print(Fore.RED + Style.BRIGHT + "Error" + Style.RESET_ALL + ": " + error['message']) + def on_trace_events(self, events): self._status_updated = False for timestamp, target_address, message in events: