Initial commit

CLI tools split out from frida-python.
This commit is contained in:
Ole André Vadla Ravnås committed 2018-07-11 01:20:57 +02:00
commit 778862021f
28 files changed
+3058

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
*.pyc
/*.egg-info/
/build/
/dist/
+50
View File
@@ -0,0 +1,50 @@
wxWindows Library Licence, Version 3.1
======================================
Copyright (c) 1998-2005 Julian Smart, Robert Roebling et al
Everyone is permitted to copy and distribute verbatim copies
of this licence document, but changing it is not allowed.
WXWINDOWS LIBRARY LICENCE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public Licence as published by
the Free Software Foundation; either version 2 of the Licence, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
Licence for more details.
You should have received a copy of the GNU Library General Public Licence
along with this software, usually in a file named COPYING.LIB. If not,
write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
Floor, Boston, MA 02110-1301 USA.
EXCEPTION NOTICE
1. As a special exception, the copyright holders of this library give
permission for additional uses of the text contained in this release of the
library as licenced under the wxWindows Library Licence, applying either
version 3.1 of the Licence, or (at your option) any later version of the
Licence as published by the copyright holders of version 3.1 of the Licence
document.
2. The exception is that you may use, copy, link, modify and distribute
under your own terms, binary object code versions of works based on the
Library.
3. If you copy code from files distributed under the terms of the GNU
General Public Licence or the GNU Library General Public Licence into a
copy of this library, as this licence permits, the exception does not apply
to the code that you add in this way. To avoid misleading anyone as to the
status of such modified files, you must delete this exception notice from
such code and/or adjust the licensing conditions notice accordingly.
4. If you write modifications of your own for this library, it is your
choice whether to permit this exception to apply to your modifications. If
you do not wish that, you must delete the exception notice from such code
and/or adjust the licensing conditions notice accordingly.
+3
View File
@@ -0,0 +1,3 @@
# Frida CLI tools
CLI tools for [Frida](http://www.frida.re).
+1
View File
@@ -0,0 +1 @@
# -*- coding: utf-8 -*-
+442
View File
@@ -0,0 +1,442 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function
import collections
import errno
from optparse import OptionParser
import os
import platform
import select
import signal
import sys
import threading
from time import time
if platform.system() == 'Windows':
import msvcrt
import colorama
from colorama import Fore, Style
import frida
def input_with_timeout(timeout):
if platform.system() == 'Windows':
start_time = time()
s = ''
while True:
while msvcrt.kbhit():
c = msvcrt.getche()
if ord(c) == 13: # enter_key
break
elif ord(c) >= 32: #space_char
s += c.decode('utf-8')
if time() - start_time > timeout:
return None
return s
else:
while True:
try:
rlist, _, _ = select.select([sys.stdin], [], [], timeout)
break
except (OSError, select.error) as e:
if e.args[0] != errno.EINTR:
raise e
if rlist:
return sys.stdin.readline()
else:
return None
def await_enter(reactor):
try:
while input_with_timeout(0.5) == None:
if not reactor.is_running():
break
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()
parser = OptionParser(usage=self._usage(), version=frida.__version__)
if self._needs_device():
parser.add_option("-D", "--device", help="connect to device with the given ID",
metavar="ID", type='string', action='store', dest="device_id", default=None)
parser.add_option("-U", "--usb", help="connect to USB device",
action='store_const', const='usb', dest="device_type", default=None)
parser.add_option("-R", "--remote", help="connect to remote frida-server",
action='store_const', const='remote', dest="device_type", default=None)
parser.add_option("-H", "--host", help="connect to remote frida-server on HOST",
metavar="HOST", type='string', action='store', dest="host", default=None)
if self._needs_target():
def store_target(option, opt_str, target_value, parser, target_type, *args, **kwargs):
if target_type == 'file':
target_value = [target_value]
setattr(parser.values, 'target', (target_type, target_value))
parser.add_option("-f", "--file", help="spawn FILE", metavar="FILE",
type='string', action='callback', callback=store_target, callback_args=('file',))
parser.add_option("-n", "--attach-name", help="attach to NAME", metavar="NAME",
type='string', action='callback', callback=store_target, callback_args=('name',))
parser.add_option("-p", "--attach-pid", help="attach to PID", metavar="PID",
type='int', action='callback', callback=store_target, callback_args=('pid',))
parser.add_option("--debug", help="enable the Node.js compatible script debugger",
action='store_true', dest="enable_debugger", default=False)
parser.add_option("--enable-jit", help="enable JIT",
action='store_true', dest="enable_jit", default=False)
self._add_options(parser)
(options, args) = parser.parse_args()
if sys.version_info[0] < 3:
input_encoding = sys.stdin.encoding or 'UTF-8'
args = [arg.decode(input_encoding) for arg in args]
if self._needs_device():
self._device_id = options.device_id
self._device_type = options.device_type
self._host = options.host
else:
self._device_id = None
self._device_type = None
self._host = None
self._device = None
self._schedule_on_output = lambda pid, fd, data: self._reactor.schedule(lambda: self._on_output(pid, fd, data))
self._schedule_on_device_lost = lambda: self._reactor.schedule(self._on_device_lost)
self._spawned_pid = None
self._spawned_argv = None
self._session = None
if self._needs_target():
self._enable_debugger = options.enable_debugger
self._enable_jit = options.enable_jit
else:
self._enable_debugger = False
self._enable_jit = False
self._schedule_on_session_detached = lambda reason: self._reactor.schedule(lambda: self._on_session_detached(reason))
self._started = False
self._resumed = False
self._reactor = Reactor(run_until_return, on_stop)
self._exit_status = None
self._console_state = ConsoleState.EMPTY
self._have_terminal = sys.stdin.isatty() and sys.stdout.isatty() and not os.environ.get("TERM", '') == "dumb"
self._quiet = False
if sum(map(lambda v: int(v is not None), (self._device_id, self._device_type, self._host))) > 1:
parser.error("Only one of -D, -U, -R, and -H may be specified")
if self._needs_target():
target = getattr(options, 'target', None)
if target is None:
if len(args) < 1:
parser.error("target file, process name or pid must be specified")
target = infer_target(args[0])
args.pop(0)
target = expand_target(target)
if target[0] == 'file':
argv = target[1]
argv.extend(args)
args = []
self._target = target
else:
self._target = None
self._initialize(parser, options, args)
def run(self):
mgr = frida.get_device_manager()
on_devices_changed = lambda: self._reactor.schedule(self._try_start)
mgr.on('changed', on_devices_changed)
self._reactor.schedule(self._try_start)
self._reactor.schedule(self._show_message_if_no_device, delay=1)
old_sigterm_handler = signal.signal(signal.SIGTERM, lambda n, f: self._exit(0))
self._reactor.run()
signal.signal(signal.SIGTERM, old_sigterm_handler)
if self._started:
self._stop()
if self._session is not None:
self._session.off('detached', self._schedule_on_session_detached)
self._session.detach()
self._session = None
if self._spawned_pid is not None:
try:
self._device.kill(self._spawned_pid)
except:
pass
if self._device is not None:
self._device.off('output', self._schedule_on_output)
self._device.off('lost', self._schedule_on_device_lost)
mgr.off('changed', on_devices_changed)
frida.shutdown()
sys.exit(self._exit_status)
def _add_options(self, parser):
pass
def _initialize(self, parser, options, args):
pass
def _needs_device(self):
return True
def _needs_target(self):
return False
def _start(self):
pass
def _stop(self):
pass
def _resume(self):
if self._resumed:
return
if self._spawned_pid is not None:
self._device.resume(self._spawned_pid)
self._resumed = True
def _exit(self, exit_status):
self._exit_status = exit_status
self._reactor.stop()
def _try_start(self):
if self._device is not None:
return
if self._device_id is not None:
try:
self._device = frida.get_device(self._device_id)
except:
self._update_status("Device '%s' not found" % self._device_id)
self._exit(1)
return
elif self._device_type is not None:
self._device = find_device(self._device_type)
if self._device is None:
return
elif self._host is not None:
self._device = frida.get_device_manager().add_remote_device(self._host)
else:
self._device = frida.get_local_device()
self._device.on('output', self._schedule_on_output)
self._device.on('lost', self._schedule_on_device_lost)
if self._target is not None:
spawning = True
try:
target_type, target_value = self._target
if target_type == 'file':
argv = target_value
if not self._quiet:
self._update_status("Spawning `%s`..." % " ".join(argv))
self._spawned_pid = self._device.spawn(argv)
self._spawned_argv = argv
attach_target = self._spawned_pid
else:
attach_target = target_value
if not self._quiet:
self._update_status("Attaching...")
spawning = False
self._session = self._device.attach(attach_target)
if self._enable_jit:
self._session.enable_jit()
if self._enable_debugger:
self._session.enable_debugger()
self._print("Debugger listening on port 5858\n")
self._session.on('detached', self._schedule_on_session_detached)
except Exception as e:
if spawning:
self._update_status("Failed to spawn: %s" % e)
else:
self._update_status("Failed to attach: %s" % e)
self._exit(1)
return
self._start()
self._started = True
def _show_message_if_no_device(self):
if self._device is None:
self._print("Waiting for USB device to appear...")
def _on_output(self, pid, fd, data):
if data is None:
return
if fd == 1:
prefix = "stdout> "
stream = sys.stdout
else:
prefix = "stderr> "
stream = sys.stderr
encoding = stream.encoding or 'UTF-8'
text = data.decode(encoding, errors='replace')
if text.endswith("\n"):
text = text[:-1]
lines = text.split("\n")
self._print(prefix + ("\n" + prefix).join(lines))
def _on_device_lost(self):
if self._exit_status is not None:
return
self._print("Device disconnected.")
self._exit(1)
def _on_session_detached(self, reason):
message = reason[0].upper() + reason[1:].replace("-", " ")
self._print(Fore.RED + Style.BRIGHT + message + Style.RESET_ALL)
self._exit(1)
def _clear_status(self):
if self._console_state == ConsoleState.STATUS:
print("\033[A" + (80 * " "))
def _update_status(self, message):
if self._have_terminal:
if self._console_state == ConsoleState.STATUS:
cursor_position = "\033[A"
else:
cursor_position = ""
print("%-80s" % (cursor_position + Style.BRIGHT + message + Style.RESET_ALL,))
self._console_state = ConsoleState.STATUS
else:
print(Style.BRIGHT + message + Style.RESET_ALL)
def _print(self, *args, **kwargs):
encoded_args = []
if sys.version_info[0] >= 3:
string_type = str
decoder = "unicode-escape"
else:
string_type = unicode
decoder = "string-escape"
encoding = sys.stdout.encoding or 'UTF-8'
for arg in args:
if isinstance(arg, string_type):
encoded_args.append(arg.encode(encoding, errors='replace').decode(decoder, errors='replace'))
else:
encoded_args.append(arg)
print(*encoded_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():
if device.type == type:
return device
return None
def infer_target(target_value):
if target_value.startswith('.') or target_value.startswith(os.path.sep) \
or (platform.system() == 'Windows' \
and target_value[0].isalpha() \
and target_value[1] == ":" \
and target_value[2] == "\\"):
target_type = 'file'
target_value = [target_value]
else:
try:
target_value = int(target_value)
target_type = 'pid'
except:
target_type = 'name'
return (target_type, target_value)
def expand_target(target):
target_type, target_value = target
if target_type == 'file':
target_value = [target_value[0]]
return (target_type, target_value)
class Reactor(object):
def __init__(self, run_until_return, on_stop=None):
self._running = False
self._run_until_return = run_until_return
self._on_stop = on_stop
self._pending = collections.deque([])
self._lock = threading.Lock()
self._cond = threading.Condition(self._lock)
def is_running(self):
with self._lock:
return self._running
def run(self):
with self._lock:
self._running = True
worker = threading.Thread(target=self._run)
worker.start()
self._run_until_return(self)
self.stop()
worker.join()
def _run(self):
running = True
while running:
now = time()
work = None
timeout = None
previous_pending_length = -1
with self._lock:
for item in self._pending:
(f, when) = item
if now >= when:
work = f
self._pending.remove(item)
break
if len(self._pending) > 0:
timeout = max([min(map(lambda item: item[1], self._pending)) - now, 0])
previous_pending_length = len(self._pending)
if work is not None:
work()
with self._lock:
if self._running and len(self._pending) == previous_pending_length:
self._cond.wait(timeout)
running = self._running
if self._on_stop is not None:
self._on_stop()
def stop(self):
with self._lock:
self._running = False
self._cond.notify()
def schedule(self, f, delay=None):
now = time()
if delay is not None:
when = now + delay
else:
when = now
with self._lock:
self._pending.append((f, when))
self._cond.notify()
+231
View File
@@ -0,0 +1,231 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function
from frida_tools.model import Module, Function, ModuleFunction
def main():
import threading
from frida_tools.application import await_enter, ConsoleApplication
class DiscovererApplication(ConsoleApplication, UI):
def __init__(self):
self._results_received = threading.Event()
ConsoleApplication.__init__(self, self._await_keys)
def _await_keys(self, reactor):
await_enter(reactor)
reactor.schedule(lambda: self._discoverer.stop())
while reactor.is_running() and not self._results_received.is_set():
self._results_received.wait(0.5)
def _usage(self):
return "usage: %prog [options] target"
def _initialize(self, parser, options, args):
self._discoverer = None
def _needs_target(self):
return True
def _start(self):
self._update_status("Injecting script...")
self._discoverer = Discoverer(self._reactor)
self._discoverer.start(self._session, self)
def _stop(self):
self._print("Stopping...")
self._discoverer.dispose()
self._discoverer = None
def on_sample_start(self, total):
self._update_status("Tracing %d threads. Press ENTER to stop." % total)
self._resume()
def on_sample_result(self, module_functions, dynamic_functions):
for module, functions in module_functions.items():
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):
self._print("\t%-10d\t%s" % (count, function))
self._print("")
if len(dynamic_functions) > 0:
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):
self._print("\t%-10d\t%s" % (count, function))
self._results_received.set()
app = DiscovererApplication()
app.run()
class Discoverer(object):
def __init__(self, reactor):
self._reactor = reactor
self._ui = None
self._script = None
def dispose(self):
if self._script is not None:
try:
self._script.unload()
except:
pass
self._script = None
def start(self, session, ui):
def on_message(message, data):
print(message, data)
session.enable_jit()
self._script = session.create_script(name="discoverer", source=self._create_discover_script())
self._script.on('message', on_message)
self._script.load()
params = self._script.exports.start()
ui.on_sample_start(params['total'])
self._ui = ui
def stop(self):
result = self._script.exports.stop()
modules = dict((int(module_id), Module(m['name'], int(m['base'], 16), m['size'], m['path']))
for module_id, m in result['modules'].items())
module_functions = {}
dynamic_functions = []
for module_id, name, visibility, raw_address, count in result['targets']:
address = int(raw_address, 16)
if module_id != 0:
module = modules[module_id]
exported = visibility == 'e'
function = ModuleFunction(module, name, address - module.base_address, exported)
functions = module_functions.get(module, [])
if len(functions) == 0:
module_functions[module] = functions
functions.append((function, count))
else:
function = Function(name, address)
dynamic_functions.append((function, count))
self._ui.on_sample_result(module_functions, dynamic_functions)
def _create_discover_script(self):
return """'use strict';
var threadIds = [];
var result = {};
rpc.exports = {
start: function () {
threadIds = Process.enumerateThreadsSync().map(function (thread) { return thread.id; });
threadIds.forEach(function (threadId) {
Stalker.follow(threadId, {
events: { call: true },
onCallSummary: function (summary) {
for (var address in summary) {
if (summary.hasOwnProperty(address)) {
var count = result[address] || 0;
result[address] = count + summary[address];
}
}
}
});
});
return {
total: threadIds.length
};
},
stop: function () {
threadIds.forEach(function (threadId) {
Stalker.unfollow(threadId);
});
threadIds = [];
var res = result;
result = {};
var map = new ModuleMap();
var allModules = map.values()
.reduce(function (result, module) {
result[module.path] = module;
return result;
}, {});
var seenModules = {};
var moduleDetails = {};
var nextModuleId = 1;
var targets = Object.keys(res)
.map(function (address) {
var moduleId = 0;
var name;
var visibility = 'i';
var addressPtr = ptr(address);
var count = res[address];
var path = map.findPath(addressPtr);
if (path !== null) {
var module = allModules[path];
var details = moduleDetails[path];
if (details !== undefined) {
moduleId = details.id;
} else {
moduleId = nextModuleId++;
details = {
id: moduleId,
exports: Module.enumerateExportsSync(path)
.reduce(function (result, e) {
result[e.address.toString()] = e.name;
return result;
}, {})
};
moduleDetails[path] = details;
seenModules[moduleId] = module;
}
var exportName = details.exports[address];
if (exportName !== undefined) {
name = exportName;
visibility = 'e';
} else {
name = 'sub_' + addressPtr.sub(module.base).toString(16);
}
} else {
name = 'dsub_' + addressPtr.toString(16);
}
return [moduleId, name, visibility, address, count];
});
return {
targets: targets,
modules: seenModules
};
}
};
"""
class UI(object):
def on_sample_start(self, total):
pass
def on_sample_result(self, module_functions, dynamic_functions):
pass
if __name__ == '__main__':
main()
+35
View File
@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
from __future__ import print_function
def main():
import frida
from frida_tools.application import ConsoleApplication, infer_target, expand_target
class KillApplication(ConsoleApplication):
def _usage(self):
return "usage: %prog [options] process"
def _start(self):
try:
self._device.kill(self._process)
except frida.ProcessNotFoundError:
self._update_status('unable to find process: %s' % self._process)
self._exit(1)
self._exit(0)
def _initialize(self, parser, options, args):
if len(args) < 1:
parser.error('process name or pid must be specified')
process = expand_target(infer_target(args[0]))
if process[0] == 'file':
parser.error('process name or pid must be specified')
self._process = process[1]
app = KillApplication()
app.run()
if __name__ == '__main__':
main()
+89
View File
@@ -0,0 +1,89 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function
def main():
import frida
from frida_tools.application import ConsoleApplication
class LSDApplication(ConsoleApplication):
def _usage(self):
return "usage: %prog [options]"
def _needs_device(self):
return False
def _start(self):
try:
devices = frida.enumerate_devices()
except Exception as e:
self._update_status("Failed to enumerate devices: %s" % e)
self._exit(1)
return
id_column_width = max(map(lambda device: len(device.id), devices))
type_column_width = max(map(lambda device: len(device.type), devices))
name_column_width = max(map(lambda device: len(device.name), devices))
header_format = "%-" + str(id_column_width) + "s " + \
"%-" + str(type_column_width) + "s " + \
"%-" + str(name_column_width) + "s"
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)):
self._print(line_format % (device.id, device.type, device.name))
self._exit(0)
def compare_devices(a, b):
a_score = score(a)
b_score = score(b)
if a_score == b_score:
if a.name > b.name:
return 1
elif a.name < b.name:
return -1
else:
return 0
else:
if a_score > b_score:
return -1
elif a_score < b_score:
return 1
else:
return 0
def score(device):
type = device.type
if type == 'local':
return 3
elif type == 'usb':
return 2
else:
return 1
def cmp_to_key(mycmp):
"Convert a cmp= function into a key= function"
class K:
def __init__(self, obj, *args):
self.obj = obj
def __lt__(self, other):
return mycmp(self.obj, other.obj) < 0
def __gt__(self, other):
return mycmp(self.obj, other.obj) > 0
def __eq__(self, other):
return mycmp(self.obj, other.obj) == 0
def __le__(self, other):
return mycmp(self.obj, other.obj) <= 0
def __ge__(self, other):
return mycmp(self.obj, other.obj) >= 0
def __ne__(self, other):
return mycmp(self.obj, other.obj) != 0
return K
device = LSDApplication()
device.run()
if __name__ == '__main__':
main()
+13
View File
@@ -0,0 +1,13 @@
sources = [
'__init__.py',
'application.py',
'discoverer.py',
'kill.py',
'lsd.py',
'model.py',
'ps.py',
'repl.py',
'tracer.py',
]
install_data(sources, install_dir: join_paths(python_site_packages, 'frida_tools'))
+71
View File
@@ -0,0 +1,71 @@
class Module(object):
def __init__(self, name, base_address, size, path):
self.name = name
self.base_address = base_address
self.size = size
self.path = path
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
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)
+126
View File
@@ -0,0 +1,126 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function
def main():
from frida_tools.application import ConsoleApplication
class PSApplication(ConsoleApplication):
def _add_options(self, parser):
parser.add_option("-a", "--applications", help="list only applications",
action='store_true', dest="list_only_applications", default=False)
parser.add_option("-i", "--installed", help="include all installed applications",
action='store_true', dest="include_all_applications", default=False)
def _initialize(self, parser, options, args):
if options.include_all_applications and not options.list_only_applications:
parser.error("-i cannot be used without -a")
self._list_only_applications = options.list_only_applications
self._include_all_applications = options.include_all_applications
def _usage(self):
return "usage: %prog [options]"
def _start(self):
if self._list_only_applications:
try:
applications = self._device.enumerate_applications()
except Exception as e:
self._update_status("Failed to enumerate applications: %s" % e)
self._exit(1)
return
if not self._include_all_applications:
applications = list(filter(lambda app: app.pid != 0, applications))
if len(applications) > 0:
pid_column_width = max(map(lambda app: len("%d" % app.pid), applications))
name_column_width = max(map(lambda app: len(app.name), applications))
identifier_column_width = max(map(lambda app: len(app.identifier), applications))
else:
pid_column_width = 0
name_column_width = 0
identifier_column_width = 0
header_format = "%" + str(pid_column_width) + "s " + \
"%-" + str(name_column_width) + "s " + \
"%-" + str(identifier_column_width) + "s"
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:
self._print(line_format % ("-", app.name, app.identifier))
else:
self._print(line_format % (app.pid, app.name, app.identifier))
else:
try:
processes = self._device.enumerate_processes()
except Exception as e:
self._update_status("Failed to enumerate processes: %s" % e)
self._exit(1)
return
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"
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)):
self._print(line_format % (process.pid, process.name))
self._exit(0)
def compare_applications(a, b):
a_is_running = a.pid != 0
b_is_running = b.pid != 0
if a_is_running == b_is_running:
if a.name > b.name:
return 1
elif a.name < b.name:
return -1
else:
return 0
elif a_is_running:
return -1
else:
return 1
def compare_processes(a, b):
a_has_icon = a.get_small_icon() is not None
b_has_icon = b.get_small_icon() is not None
if a_has_icon == b_has_icon:
if a.name > b.name:
return 1
elif a.name < b.name:
return -1
else:
return 0
elif a_has_icon:
return -1
else:
return 1
def cmp_to_key(mycmp):
"Convert a cmp= function into a key= function"
class K:
def __init__(self, obj, *args):
self.obj = obj
def __lt__(self, other):
return mycmp(self.obj, other.obj) < 0
def __gt__(self, other):
return mycmp(self.obj, other.obj) > 0
def __eq__(self, other):
return mycmp(self.obj, other.obj) == 0
def __le__(self, other):
return mycmp(self.obj, other.obj) <= 0
def __ge__(self, other):
return mycmp(self.obj, other.obj) >= 0
def __ne__(self, other):
return mycmp(self.obj, other.obj) != 0
return K
app = PSApplication()
app.run()
if __name__ == '__main__':
main()
+772
View File
@@ -0,0 +1,772 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function
import signal
import threading
def main():
import codecs
import hashlib
import json
import os
import platform
import re
import sys
try:
from urllib.request import build_opener
except:
from urllib2 import build_opener
from colorama import Fore, Style
import frida
from prompt_toolkit.shortcuts import create_prompt_application, create_output, create_eventloop
from prompt_toolkit.history import FileHistory
from prompt_toolkit.completion import Completion, Completer
from prompt_toolkit.interface import CommandLineInterface
from pygments.lexers import JavascriptLexer
from pygments.token import Token
from frida_tools.application import ConsoleApplication
class REPLApplication(ConsoleApplication):
def __init__(self):
self._script = None
self._seqno = 0
self._ready = threading.Event()
self._stopping = threading.Event()
self._errors = 0
config_dir = self._get_or_create_config_dir()
self._history = FileHistory(os.path.join(config_dir, 'history'))
self._completer = FridaCompleter(self)
self._cli = None
self._last_change_id = 0
self._script_monitor = None
self._monitored_file = None
super(REPLApplication, self).__init__(self._process_input, self._on_stop)
self._dumb_stdin_reader = None if self._have_terminal else DumbStdinReader(valid_until=self._stopping)
def _add_options(self, parser):
parser.add_option("-l", "--load", help="load SCRIPT", metavar="SCRIPT",
type='string', action='store', dest="user_script", default=None)
parser.add_option("-c", "--codeshare", help="load CODESHARE_URI", metavar="CODESHARE_URI",
type='string', action='store', dest="codeshare_uri", default=None)
parser.add_option("-e", "--eval", help="evaluate CODE", metavar="CODE",
type='string', action='append', dest="eval_items", default=None)
parser.add_option("-q", help="quiet mode (no prompt) and quit after -l and -e",
action='store_true', dest="quiet", default=False)
parser.add_option("--no-pause", help="automatically start main thread after startup",
action='store_true', dest="no_pause", default=False)
parser.add_option("-o", "--output", help="output to log file", dest="logfile", default=None)
def _initialize(self, parser, options, args):
self._user_script = options.user_script
self._codeshare_uri = options.codeshare_uri
self._codeshare_script = None
self._pending_eval = options.eval_items
self._quiet = options.quiet
self._no_pause = options.no_pause
if options.logfile is not None:
try:
self._logfile = open(options.logfile, 'w')
except Exception as e:
self._update_status('Failed to open logfile "%s"' % options.logfile)
self._exit(1)
else:
self._logfile = None
def _log(self, level, text):
ConsoleApplication._log(self, level, text)
if self._logfile is not None:
self._logfile.write(text + "\n")
def _usage(self):
return "usage: %prog [options] target"
def _needs_target(self):
return True
def _start(self):
self._prompt_string = self._create_prompt()
if self._codeshare_uri is not None:
self._codeshare_script = self._load_codeshare_script(self._codeshare_uri)
if self._codeshare_script is None:
self._print("Exiting!")
self._exit(1)
return
try:
self._load_script()
except Exception as e:
self._update_status("Failed to load script: {error}".format(error=e))
self._exit(1)
return
if self._spawned_argv is not None:
if self._no_pause:
self._update_status("Spawned `{command}`. Resuming main thread!".format(command=" ".join(self._spawned_argv)))
self._do_magic("resume")
else:
self._update_status("Spawned `{command}`. Use %resume to let the main thread start executing!".format(command=" ".join(self._spawned_argv)))
else:
self._clear_status()
self._ready.set()
def _on_stop(self):
def set_return():
raise EOFError()
self._stopping.set()
if self._cli is not None:
try:
self._cli.eventloop.call_from_executor(set_return)
except Exception:
pass
def _stop(self):
self._unload_script()
self._unmonitor_script()
self._print("\nThank you for using Frida!")
def _load_script(self):
self._monitor_script()
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):
self._reactor.schedule(lambda: self._process_message(message, data))
script.on('message', on_message)
script.load()
def _unload_script(self):
if self._script is None:
return
try:
self._script.unload()
except:
pass
self._script = None
def _monitor_script(self):
if self._monitored_file == self._user_script:
return
self._unmonitor_script()
if self._user_script is not None:
monitor = frida.FileMonitor(self._user_script)
monitor.on('change', self._on_change)
monitor.enable()
self._script_monitor = monitor
self._monitored_file = self._user_script
def _unmonitor_script(self):
if self._script_monitor is None:
return
self._script_monitor.disable()
self._script_monitor = None
def _process_input(self, reactor):
if not self._quiet:
self._print_startup_message()
while self._ready.wait(0.5) != True:
if not reactor.is_running():
return
while True:
expression = ""
line = ""
while len(expression) == 0 or line.endswith("\\"):
if not reactor.is_running():
return
prompt = "[%s]" % self._prompt_string + "-> " if len(expression) == 0 else "... "
pending_eval = self._pending_eval
if pending_eval is not None:
if len(pending_eval) > 0:
expression = pending_eval.pop(0)
if not self._quiet:
self._print(prompt + expression)
else:
self._pending_eval = None
else:
if self._quiet:
self._exit_status = 0 if self._errors == 0 else 1
return
try:
if self._have_terminal:
# We create the prompt manually instead of using get_input,
# so we can use the cli in the _on_stop method
eventloop = create_eventloop()
self._cli = CommandLineInterface(
application=create_prompt_application(prompt, history=self._history, completer=self._completer, lexer=JavascriptLexer),
eventloop=eventloop,
output=create_output())
try:
line = None
document = self._cli.run()
if document:
line = document.text
finally:
eventloop.close()
else:
line = self._dumb_stdin_reader.read_line(prompt)
self._print(line)
except EOFError:
if not self._have_terminal:
while not self._stopping.wait(1):
pass
return
except KeyboardInterrupt:
line = ""
if not self._have_terminal:
sys.stdout.write("\n" + prompt)
continue
if len(line.strip()) > 0:
if len(expression) > 0:
expression += "\n"
expression += line.rstrip("\\")
if expression.endswith("?"):
try:
self._print_help(expression)
except JavaScriptError as e:
error = e.error
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"):
return
elif expression == "help":
self._print("Help: #TODO :)")
else:
if not self._eval_and_print(expression):
self._errors += 1
def _eval_and_print(self, expression):
success = False
try:
(t, value) = self._evaluate(expression)
if t in ('function', 'undefined', 'null'):
output = t
elif t == 'binary':
output = hexdump(value).rstrip("\n")
else:
output = json.dumps(value, sort_keys=True, indent=4, separators=(",", ": "))
success = True
except JavaScriptError as e:
error = e.error
output = Fore.RED + Style.BRIGHT + error['name'] + Style.RESET_ALL + ": " + error['message']
except frida.InvalidOperationError:
return success
self._print(output)
return success
def _print_startup_message(self):
self._print("""\
____
/ _ | Frida {version} - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at http://www.frida.re/docs/home/""".format(version=frida.__version__))
def _print_help(self, expression):
# TODO: Figure out docstrings and implement here. This is real jankaty right now.
help_text = ""
if expression.endswith(".?"):
expression = expression[:-2] + "?"
obj_to_identify = [x for x in expression.split(' ') if x.endswith("?")][0][:-1]
(obj_type, obj_value) = self._evaluate(obj_to_identify)
if obj_type == "function":
signature = self._evaluate("%s.toString()" % obj_to_identify)[1]
clean_signature = signature.split("{")[0][:-1].split('function ')[-1]
if "[native code]" in signature:
help_text += "Type: Function (native)\n"
else:
help_text += "Type: Function\n"
help_text += "Signature: %s\n" % clean_signature
help_text += "Docstring: #TODO :)"
elif obj_type == "object":
help_text += "Type: Object\n"
help_text += "Docstring: #TODO :)"
elif obj_type == "boolean":
help_text += "Type: Boolean\n"
help_text += "Docstring: #TODO :)"
elif obj_type == "string":
help_text += "Type: Boolean\n"
help_text += "Text: %s\n" % self._evaluate("%s.toString()" % obj_to_identify)[1]
help_text += "Docstring: #TODO :)"
self._print(help_text)
# Negative means at least abs(val) - 1
_magic_command_args = {
'resume': 0,
'load': 1,
'reload': 0,
'unload': 0,
'time': -2 # At least 1 arg
}
def _do_magic(self, statement):
tokens = statement.split(" ")
command = tokens[0]
args = tokens[1:]
required_args = self._magic_command_args.get(command)
if required_args == None:
self._print("Unknown command: {}".format(command))
self._print("Valid commands: {}".format(", ".join(self._magic_command_args.keys())))
return
atleast_args = False
if required_args < 0:
atleast_args = True
required_args = abs(required_args) - 1
if (not atleast_args and len(args) != required_args) or \
(atleast_args and len(args) < required_args):
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
if command == 'resume':
self._reactor.schedule(lambda: self._resume())
elif command == 'load':
old_user_script = self._user_script
self._user_script = os.path.abspath(args[0])
if not self._reload():
self._user_script = old_user_script
elif command == 'reload':
self._reload()
elif command == 'unload':
self._user_script = None
self._reload()
elif command == 'time':
self._eval_and_print('''
(function () {
var _startTime = Date.now();
var _result = eval({expression});
var _endTime = Date.now();
console.log('Time: ' + (_endTime - _startTime).toLocaleString() + ' ms.');
return _result;
})();'''.format(expression=json.dumps(' '.join(args))))
def _reload(self):
completed = threading.Event()
result = [None]
def do_reload():
try:
self._load_script()
except Exception as e:
result[0] = e
completed.set()
self._reactor.schedule(do_reload)
completed.wait()
if result[0] is None:
return True
else:
self._print("Failed to load script: {error}".format(error=result[0]))
return False
def _create_prompt(self):
device_type = self._device.type
type_name = self._target[0]
if type_name == 'pid':
if self._target[1] == 0:
target = 'SystemSession'
else:
target = 'PID::%u' % self._target[1]
elif type_name == 'file':
target = os.path.basename(self._target[1][0])
else:
target = self._target[1]
if device_type in ('local', 'remote'):
prompt_string = "%s::%s" % (device_type.title(), target)
else:
prompt_string = "%s::%s" % (self._device.name, target)
return prompt_string
def _evaluate(self, text):
result = self._script.exports.evaluate(text)
if is_byte_array(result):
return ('binary', result)
elif isinstance(result, dict):
return ('binary', bytes())
elif result[0] == 'error':
raise JavaScriptError(result[1])
else:
return result
def _process_message(self, message, data):
message_type = message['type']
if message_type == 'error':
text = message.get('stack', message['description'])
self._log('error', text)
self._errors += 1
else:
self._print("message:", message, "data:", data)
def _on_change(self, changed_file, other_file, event_type):
if event_type == 'changes-done-hint':
return
self._last_change_id += 1
change_id = self._last_change_id
self._reactor.schedule(lambda: self._process_change(change_id), delay=0.05)
def _process_change(self, change_id):
if change_id != self._last_change_id:
return
try:
self._load_script()
except Exception as e:
self._print("Failed to load script: {error}".format(error=e))
def _create_repl_script(self):
user_script = ""
if self._codeshare_script is not None:
user_script = self._codeshare_script
if self._user_script is not None:
with codecs.open(self._user_script, 'rb', 'utf-8') as f:
user_script += f.read().rstrip("\r\n") + "\n\n// Frida REPL script:\n"
return user_script + """\
rpc.exports.evaluate = function (expression) {
try {
var result = (1, eval)(expression);
if (result instanceof ArrayBuffer) {
return result;
} else {
var type = (result === null) ? 'null' : typeof result;
return [type, result];
}
} catch (e) {
return ['error', {
name: e.name,
message: e.message,
stack: e.stack
}];
}
};
"""
def _load_codeshare_script(self, uri):
trust_store = self._get_or_create_truststore()
project_url = "https://codeshare.frida.re/api/project/{}/".format(uri)
response_json = None
try:
request = build_opener()
request.addheaders = [('User-Agent', 'Frida v{} | {}'.format(frida.__version__, platform.platform()))]
response = request.open(project_url)
response_content = response.read().decode('utf-8')
response_json = json.loads(response_content)
except Exception as e:
self._print("Got an unhandled exception while trying to retrieve {} - {}".format(uri, e))
return None
trusted_signature = trust_store.get(uri, "")
fingerprint = hashlib.sha256(response_json['source'].encode('utf-8')).hexdigest()
if fingerprint == trusted_signature:
return response_json['source']
self._print("""Hello! This is the first time you're running this particular snippet, or the snippet's source code has changed.
Project Name: {project_name}
Author: {author}
Slug: {slug}
Fingerprint: {fingerprint}
URL: {url}
""".format(
project_name=response_json['project_name'],
author="@" + uri.split('/')[0],
slug=uri,
fingerprint=fingerprint,
url="https://codeshare.frida.re/@{}".format(uri)
))
while True:
prompt_string = "Are you sure you'd like to trust this project? [y/N] "
response = get_input(prompt_string)
if response.lower() in ('n', 'no') or response == '':
return None
if response.lower() in ('y', 'yes'):
self._print("Adding fingerprint {} to the trust store! You won't be prompted again unless the code changes.".format(fingerprint))
script = response_json['source']
self._update_truststore({
uri: fingerprint
})
return script
def _get_or_create_config_dir(self):
config_dir = os.path.join(os.path.expanduser('~'), '.frida')
if not os.path.exists(config_dir):
os.makedirs(config_dir)
return config_dir
def _update_truststore(self, record):
trust_store = self._get_or_create_truststore()
trust_store.update(record)
config_dir = self._get_or_create_config_dir()
codeshare_trust_store = os.path.join(config_dir, "codeshare-truststore.json")
with open(codeshare_trust_store, 'w') as f:
f.write(json.dumps(trust_store))
def _get_or_create_truststore(self):
config_dir = self._get_or_create_config_dir()
codeshare_trust_store = os.path.join(config_dir, "codeshare-truststore.json")
if os.path.exists(codeshare_trust_store):
try:
with open(codeshare_trust_store) as f:
trust_store = json.load(f)
except Exception as e:
self._print("Unable to load the codeshare truststore ({}), defaulting to an empty truststore. You will be prompted every time you want to run a script!".format(e))
trust_store = {}
else:
with open(codeshare_trust_store, 'w') as f:
f.write(json.dumps({}))
trust_store = {}
return trust_store
class FridaCompleter(Completer):
def __init__(self, repl):
self._repl = repl
self._lexer = JavascriptLexer()
def get_completions(self, document, complete_event):
prefix = document.text_before_cursor
magic = len(prefix) > 0 and prefix[0] == '%' and not any(map(lambda c: c.isspace(), prefix))
tokens = list(self._lexer.get_tokens(prefix))[:-1]
# 0.toString() is invalid syntax,
# but pygments doesn't seem to know that
for i in range(len(tokens) - 1):
if tokens[i][0] == Token.Literal.Number.Integer \
and tokens[i + 1][0] == Token.Punctuation and tokens[i + 1][1] == '.':
tokens[i] = (Token.Literal.Number.Float, tokens[i][1] + tokens[i + 1][1])
del tokens[i + 1]
before_dot = ''
after_dot = ''
encountered_dot = False
for t in tokens[::-1]:
if t[0] in Token.Name.subtypes:
before_dot = t[1] + before_dot
elif t[0] == Token.Punctuation and t[1] == '.':
before_dot = '.' + before_dot
if not encountered_dot:
encountered_dot = True
after_dot = before_dot[1:]
before_dot = ''
else:
if encountered_dot:
# The value/contents of the string, number or array doesn't matter,
# so we just use the simplest value with that type
if t[0] in Token.Literal.String.subtypes:
before_dot = '""' + before_dot
elif t[0] in Token.Literal.Number.subtypes:
before_dot = '0.0' + before_dot
elif t[0] == Token.Punctuation and t[1] == ']':
before_dot = '[]' + before_dot
break
try:
if encountered_dot:
for key in self._get_keys("""try {
(function (o) {
"use strict";
var k = Object.getOwnPropertyNames(o);
if (o !== null && o !== undefined) {
var p;
if (typeof o !== 'object')
p = o.__proto__;
else
p = Object.getPrototypeOf(o);
if (p !== null && p !== undefined)
k = k.concat(Object.getOwnPropertyNames(p));
}
return k;
})(""" + before_dot + """);
} catch (e) {
[];
}"""):
if self._pattern_matches(after_dot, key):
yield Completion(key, -len(after_dot))
else:
if magic:
keys = self._repl._magic_command_args.keys()
else:
keys = self._get_keys("Object.getOwnPropertyNames(this)")
for key in keys:
if not self._pattern_matches(before_dot, key) or (key.startswith('_') and before_dot == ''):
continue
yield Completion(key, -len(before_dot))
except frida.InvalidOperationError:
pass
except Exception as e:
self._repl._print(e)
def _get_keys(self, code):
return sorted(
filter(self._is_valid_name,
set(self._repl._evaluate(code)[1])))
def _is_valid_name(self, name):
tokens = list(self._lexer.get_tokens(name))
return len(tokens) == 2 and tokens[0][0] in Token.Name.subtypes
def _pattern_matches(self, pattern, text):
return re.search(re.escape(pattern), text, re.IGNORECASE) != None
def hexdump(src, length=16):
try:
xrange
except NameError:
xrange = range
FILTER = "".join([(len(repr(chr(x))) == 3) and chr(x) or "." for x in range(256)])
lines = []
for c in xrange(0, len(src), length):
chars = src[c:c + length]
hex = " ".join(["%02x" % x for x in iterbytes(chars)])
printable = ''.join(["%s" % ((x <= 127 and FILTER[x]) or ".") for x in iterbytes(chars)])
lines.append("%04x %-*s %s\n" % (c, length * 3, hex, printable))
return "".join(lines)
def is_byte_array(value):
if sys.version_info[0] >= 3:
return isinstance(value, bytes)
else:
return isinstance(value, str)
if sys.version_info[0] >= 3:
iterbytes = lambda x: iter(x)
else:
def iterbytes(data):
return (ord(char) for char in data)
app = REPLApplication()
app.run()
class JavaScriptError(Exception):
def __init__(self, error):
super(JavaScriptError, self).__init__(error['message'])
self.error = error
class DumbStdinReader(object):
def __init__(self, valid_until):
self._valid_until = valid_until
self._prompt = None
self._result = None
self._lock = threading.Lock()
self._cond = threading.Condition(self._lock)
worker = threading.Thread(target=self._process_requests, name="stdin-reader")
worker.daemon = True
worker.start()
signal.signal(signal.SIGINT, lambda n, f: self._cancel_line())
def read_line(self, prompt_string):
with self._lock:
self._prompt = prompt_string
self._cond.notify()
with self._lock:
while self._result is None:
if self._valid_until.is_set():
raise EOFError()
self._cond.wait(1)
line, error = self._result
self._result = None
if error is not None:
raise error
return line
def _process_requests(self):
error = None
while error is None:
with self._lock:
while self._prompt is None:
self._cond.wait()
prompt = self._prompt
try:
line = get_input(prompt)
except Exception as e:
line = None
error = e
with self._lock:
self._prompt = None
self._result = (line, error)
self._cond.notify()
def _cancel_line(self):
with self._lock:
self._prompt = None
self._result = (None, KeyboardInterrupt())
self._cond.notify()
try:
input_impl = raw_input
except NameError:
input_impl = input
def get_input(prompt_string):
return input_impl(prompt_string)
if __name__ == '__main__':
main()
+934
View File
@@ -0,0 +1,934 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function
import binascii
import codecs
import os
import platform
import re
import subprocess
import threading
import time
from frida import FileMonitor
from frida_tools.model import Module, Function, ModuleFunction, ObjCMethod
def main():
from colorama import Fore, Style
from frida_tools.application import ConsoleApplication, input_with_timeout
class TracerApplication(ConsoleApplication, UI):
def __init__(self):
super(TracerApplication, self).__init__(self._await_ctrl_c)
self._palette = [Fore.CYAN, Fore.MAGENTA, Fore.YELLOW, Fore.GREEN, Fore.RED, Fore.BLUE]
self._next_color = 0
self._attributes_by_thread_id = {}
self._last_event_tid = -1
def _add_options(self, parser):
pb = TracerProfileBuilder()
def process_builder_arg(option, opt_str, value, parser, method, **kwargs):
method(value)
parser.add_option("-I", "--include-module", help="include MODULE", metavar="MODULE",
type='string', action='callback', callback=process_builder_arg, callback_args=(pb.include_modules,))
parser.add_option("-X", "--exclude-module", help="exclude MODULE", metavar="MODULE",
type='string', action='callback', callback=process_builder_arg, callback_args=(pb.exclude_modules,))
parser.add_option("-i", "--include", help="include FUNCTION", metavar="FUNCTION",
type='string', action='callback', callback=process_builder_arg, callback_args=(pb.include,))
parser.add_option("-x", "--exclude", help="exclude FUNCTION", metavar="FUNCTION",
type='string', action='callback', callback=process_builder_arg, callback_args=(pb.exclude,))
parser.add_option("-a", "--add", help="add MODULE!OFFSET", metavar="MODULE!OFFSET",
type='string', action='callback', callback=process_builder_arg, callback_args=(pb.include_relative_address,))
parser.add_option("-T", "--include-imports", help="include program's imports",
action='callback', callback=process_builder_arg, callback_args=(pb.include_imports,))
parser.add_option("-t", "--include-module-imports", help="include MODULE imports", metavar="MODULE",
type='string', action='callback', callback=process_builder_arg, callback_args=(pb.include_imports,))
parser.add_option("-m", "--include-objc-method", help="include OBJC_METHOD", metavar="OBJC_METHOD",
type='string', action='callback', callback=process_builder_arg, callback_args=(pb.include_objc_method,))
parser.add_option("-s", "--include-debug-symbol", help="include DEBUG_SYMBOL", metavar="DEBUG_SYMBOL",
type='string', action='callback', callback=process_builder_arg, callback_args=(pb.include_debug_symbol,))
self._profile_builder = pb
def _usage(self):
return "usage: %prog [options] target"
def _initialize(self, parser, options, args):
self._tracer = None
self._targets = None
self._profile = self._profile_builder.build()
def _needs_target(self):
return True
def _start(self):
self._tracer = Tracer(self._reactor, FileRepository(self._reactor), self._profile, log_handler=self._log)
try:
self._targets = self._tracer.start_trace(self._session, self)
except Exception as e:
self._update_status("Failed to start tracing: {error}".format(error=e))
self._exit(1)
def _stop(self):
self._tracer.stop()
self._tracer = None
def _await_ctrl_c(self, reactor):
while reactor.is_running():
try:
input_with_timeout(0.5)
except KeyboardInterrupt:
break
def on_trace_progress(self, operation):
if operation == 'resolve':
self._update_status("Resolving functions...")
elif operation == 'instrument':
self._update_status("Instrumenting functions...")
elif operation == 'ready':
if len(self._targets) == 1:
plural = ""
else:
plural = "s"
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):
self._print(Fore.RED + Style.BRIGHT + "Error" + Style.RESET_ALL + ": " + error['message'])
def on_trace_events(self, events):
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:
self._print("%s /* TID 0x%x */%s" % (attributes, thread_id, Style.RESET_ALL))
self._last_event_tid = thread_id
self._print("%6d ms %s%s%s%s" % (timestamp, attributes, indent, message, no_attributes))
def on_trace_handler_create(self, function, handler, source):
self._print("%s: Auto-generated handler at \"%s\"" % (function, source.replace("\\", "\\\\")))
def on_trace_handler_load(self, function, handler, source):
self._print("%s: Loaded handler at \"%s\"" % (function, source.replace("\\", "\\\\")))
def _get_attributes(self, thread_id):
attributes = self._attributes_by_thread_id.get(thread_id, None)
if attributes is None:
color = self._next_color
self._next_color += 1
attributes = self._palette[color % len(self._palette)]
if (1 + int(color / len(self._palette))) % 2 == 0:
attributes += Style.BRIGHT
self._attributes_by_thread_id[thread_id] = attributes
return attributes
app = TracerApplication()
app.run()
class TracerProfileBuilder(object):
_RE_REL_ADDRESS = re.compile("(?P<module>[^\s!]+)!(?P<offset>(0x)?[0-9a-fA-F]+)")
def __init__(self):
self._spec = []
def include_modules(self, *module_name_globs):
for m in module_name_globs:
self._spec.append(('include', 'module', m))
return self
def exclude_modules(self, *module_name_globs):
for m in module_name_globs:
self._spec.append(('exclude', 'module', m))
return self
def include(self, *function_name_globs):
for f in function_name_globs:
self._spec.append(('include', 'function', f))
return self
def exclude(self, *function_name_globs):
for f in function_name_globs:
self._spec.append(('exclude', 'function', f))
return self
def include_relative_address(self, *address_rel_offsets):
for f in address_rel_offsets:
m = TracerProfileBuilder._RE_REL_ADDRESS.search(f)
if m is None:
continue
self._spec.append(('include', 'relative_function', {
'module': m.group('module'),
'offset': int(m.group('offset'), base=16)
}))
return self
def include_imports(self, *module_name_globs):
for m in module_name_globs:
self._spec.append(('include', 'imports', m))
return self
def include_objc_method(self, *function_name_globs):
for f in function_name_globs:
self._spec.append(('include', 'objc_method', f))
return self
def include_debug_symbol(self, *function_name_globs):
for f in function_name_globs:
self._spec.append(('include', 'debug_symbol', f))
return self
def build(self):
return TracerProfile(self._spec)
class TracerProfile(object):
_BLACKLIST = set([
"libSystem.B.dylib!dyld_stub_binder"
])
def __init__(self, spec):
self._spec = spec
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)
def on_message(message, data):
print(message)
script.on('message', on_message)
script.load()
try:
data = script.exports.resolve(self._spec)
finally:
script.unload()
modules = {}
for module_id, m in data['modules'].items():
module = Module(m['name'], int(m['base'], 16), m['size'], m['path'])
modules[int(module_id)] = module
working_set = []
for target in data['targets']:
objc = target.get('objc')
if objc is not None:
method = objc['method']
of = ObjCMethod(method['type'], objc['className'], method['name'], int(target['address'], 16))
working_set.append(of)
else:
name = target['name']
absolute_address = int(target['address'], 16)
module_id = target.get('module')
if module_id is not None:
module = modules[module_id]
relative_address = absolute_address - module.base_address
exported = not target.get('private', False)
mf = ModuleFunction(module, name, relative_address, exported)
if not self._is_blacklisted(mf):
working_set.append(mf)
else:
f = Function(name, absolute_address)
working_set.append(f)
return working_set
def _is_blacklisted(self, module_function):
key = module_function.module.name + "!" + module_function.name
return key in TracerProfile._BLACKLIST
def _create_resolver_script(self):
return r""""use strict";
rpc.exports = {
resolve: function (spec) {
var workingSet = spec.reduce(function (workingSet, item) {
var operation = item[0];
var scope = item[1];
var param = item[2];
switch (scope) {
case 'module':
if (operation === 'include')
workingSet = includeModule(param, workingSet);
else if (operation === 'exclude')
workingSet = excludeModule(param, workingSet);
break;
case 'function':
if (operation === 'include')
workingSet = includeFunction(param, workingSet);
else if (operation === 'exclude')
workingSet = excludeFunction(param, workingSet);
break;
case 'relative_function':
if (operation === 'include')
workingSet = includeRelativeFunction(param, workingSet);
break;
case 'imports':
if (operation === 'include')
workingSet = includeImports(param, workingSet);
break;
case 'objc_method':
if (operation === 'include')
workingSet = includeObjCMethod(param, workingSet);
break;
case 'debug_symbol':
if (operation === 'include')
workingSet = includeDebugSymbol(param, workingSet);
break;
}
return workingSet;
}, {});
var modules = {};
var targets = [];
for (var address in workingSet) {
if (workingSet.hasOwnProperty(address)) {
var target = workingSet[address];
var moduleId = target.module;
if (moduleId !== undefined && !modules.hasOwnProperty(moduleId)) {
var m = allModules()[moduleId];
delete m._cachedFunctionExports;
modules[moduleId] = m;
}
targets.push(target);
}
}
return {
modules: modules,
targets: targets
};
}
};
function includeModule(pattern, workingSet) {
moduleResolver().enumerateMatchesSync('exports:' + pattern + '!*').forEach(function (m) {
workingSet[m.address.toString()] = moduleExportFromMatch(m);
});
return workingSet;
}
function excludeModule(pattern, workingSet) {
moduleResolver().enumerateMatchesSync('exports:' + pattern + '!*').forEach(function (m) {
delete workingSet[m.address.toString()];
});
return workingSet;
}
function includeFunction(pattern, workingSet) {
moduleResolver().enumerateMatchesSync('exports:*!' + pattern).forEach(function (m) {
workingSet[m.address.toString()] = moduleExportFromMatch(m);
});
return workingSet;
}
function excludeFunction(pattern, workingSet) {
moduleResolver().enumerateMatchesSync('exports:*!' + pattern).forEach(function (m) {
delete workingSet[m.address.toString()];
});
return workingSet;
}
function includeRelativeFunction(func, workingSet) {
var relativeToModule = func.module;
var modules = allModules();
for (var moduleIndex = 0; moduleIndex !== modules.length; moduleIndex++) {
var module = modules[moduleIndex];
if (module.path === relativeToModule || module.name === relativeToModule) {
var relativeAddress = ptr(func.offset);
var absoluteAddress = module.base.add(relativeAddress);
workingSet[absoluteAddress] = {
name: "sub_" + relativeAddress.toString(16),
address: absoluteAddress,
module: moduleIndex,
private: true
};
}
}
return workingSet;
}
function includeImports(pattern, workingSet) {
var matches;
if (pattern === null) {
var mainModule = allModules()[0].path;
matches = moduleResolver().enumerateMatchesSync('imports:' + mainModule + '!*');
} else {
matches = moduleResolver().enumerateMatchesSync('imports:' + pattern + '!*');
}
matches.map(moduleExportFromMatch).forEach(function (e) {
workingSet[e.address.toString()] = e;
});
return workingSet;
}
function includeObjCMethod(pattern, workingSet) {
objcResolver().enumerateMatchesSync(pattern).forEach(function (m) {
workingSet[m.address.toString()] = objcMethodFromMatch(m);
});
return workingSet;
}
function includeDebugSymbol(pattern, workingSet) {
DebugSymbol.findFunctionsMatching(pattern).forEach(function (addr) {
workingSet[addr.toString()] = debugSymbolFromAddress(addr);
});
return workingSet;
}
var cachedModuleResolver = null;
function moduleResolver() {
if (cachedModuleResolver === null)
cachedModuleResolver = new ApiResolver('module');
return cachedModuleResolver;
}
var cachedObjcResolver = null;
function objcResolver() {
if (cachedObjcResolver === null) {
try {
cachedObjcResolver = new ApiResolver('objc');
} catch (e) {
throw new Error("Objective-C runtime is not available");
}
}
return cachedObjcResolver;
}
var cachedModules = null;
function allModules() {
if (cachedModules === null) {
cachedModules = Process.enumerateModulesSync();
cachedModules._idByPath = cachedModules.reduce(function (mappings, module, index) {
mappings[module.path] = index;
return mappings;
}, {});
cachedModules._idByName = cachedModules.reduce(function (mappings, module, index) {
mappings[module.name] = index;
return mappings;
}, {});
}
return cachedModules;
}
function moduleExportFromMatch(m) {
var encodedName = m.name;
var delimiterIndex = encodedName.indexOf('!');
var modulePath = encodedName.substring(0, delimiterIndex);
var functionName = encodedName.substring(delimiterIndex + 1);
return {
name: functionName,
address: m.address,
module: allModules()._idByPath[modulePath]
};
}
function objcMethodFromMatch(m) {
var encodedName = m.name;
var methodType = encodedName[0];
var delimiterIndex = encodedName.indexOf(' ', 3);
var className = encodedName.substring(2, delimiterIndex);
var methodName = encodedName.substring(delimiterIndex + 1, encodedName.length - 1);
return {
objc: {
className: className,
method: {
type: methodType,
name: methodName
}
},
address: m.address
};
}
function debugSymbolFromAddress(address) {
var symbol = DebugSymbol.fromAddress(address);
return {
name: symbol.name,
address: symbol.address,
module: allModules()._idByName[symbol.moduleName]
};
}
"""
class Tracer(object):
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):
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_update(function, handler, source):
self._script.exports.update([{
'name': function.name,
'absolute_address': hex(function.absolute_address),
'handler': handler
}])
self._repository.on_update(on_update)
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(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)]:
targets = [{
'name': function.name,
'absolute_address': hex(function.absolute_address),
'handler': self._repository.ensure_handler(function)
} for function in chunk]
self._script.exports.add(targets)
self._repository.commit_handlers()
self._reactor.schedule(lambda: ui.on_trace_progress('ready'))
return working_set
def stop(self):
if self._script is not None:
try:
self._script.unload()
except:
pass
self._script = None
def _create_trace_script(self):
return """"use strict";
var started = Date.now();
var handlers = {};
var state = {};
var pending = [];
var timer = null;
rpc.exports = {
dispose: function () {
flush();
},
add: function (targets) {
targets.forEach(function (target) {
var h = [parseHandler(target)];
var name = target.name;
var targetAddress = target.absolute_address;
target = null;
handlers[targetAddress] = h;
function invokeCallback(callback, context, param) {
if (callback === undefined)
return;
var timestamp = Date.now() - started;
var threadId = context.threadId;
var depth = context.depth;
function log(message) {
emit([timestamp, threadId, depth, targetAddress, message]);
}
callback.call(context, log, param, state);
}
try {
Interceptor.attach(ptr(targetAddress), {
onEnter: function (args) {
invokeCallback(h[0].onEnter, this, args);
},
onLeave: function (retval) {
invokeCallback(h[0].onLeave, this, retval);
}
});
} catch (e) {
send({
from: "/targets",
name: '+error',
payload: {
message: "Skipping '" + name + "': " + e.message
}
});
}
});
},
update: function (targets) {
targets.forEach(function (target) {
handlers[target.absolute_address][0] = parseHandler(target);
});
}
};
function emit(event) {
pending.push(event);
if (timer === null)
timer = setTimeout(flush, 50);
}
function flush() {
if (timer !== null) {
clearTimeout(timer);
timer = null;
}
if (pending.length === 0)
return;
var items = pending;
pending = [];
send({
from: "/events",
name: '+add',
payload: {
items: items
}
});
}
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 {};
}
}
"""
def _process_message(self, message, data, ui):
handled = False
if message['type'] == 'send':
stanza = message['payload']
if isinstance(stanza, dict):
if stanza['from'] == "/events":
if stanza['name'] == '+add':
events = [(timestamp, thread_id, depth, int(target_address.rstrip("L"), 16), message) for timestamp, thread_id, depth, target_address, message in stanza['payload']['items']]
ui.on_trace_events(events)
handled = True
elif stanza['from'] == "/targets" and stanza['name'] == '+error':
ui.on_trace_error(stanza['payload'])
handled = True
if not handled:
print(message)
class Repository(object):
def __init__(self):
self._on_create_callback = None
self._on_load_callback = None
self._on_update_callback = None
def ensure_handler(self, function):
raise NotImplementedError("not implemented")
def commit_handlers(self):
pass
def on_create(self, callback):
self._on_create_callback = callback
def on_load(self, callback):
self._on_load_callback = callback
def on_update(self, callback):
self._on_update_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 _notify_update(self, function, handler, source):
if self._on_update_callback is not None:
self._on_update_callback(function, handler, source)
def _create_stub_handler(self, function):
if isinstance(function, ObjCMethod):
display_name = function.display_name()
state = {"index": 2}
def objc_arg(m):
index = state["index"]
r = ":\" + args[%d] + \" " % index
state["index"] = index + 1
return r
log_str = '"' + re.sub(r':', objc_arg, display_name) + '"'
if log_str.endswith("\" ]\""):
log_str = log_str[:-3] + "]\""
else:
display_name = function.name
for man_section in (2, 3):
args = []
try:
with open(os.devnull, 'w') as devnull:
man_argv = ["man"]
if platform.system() != "Darwin":
man_argv.extend(["-E", "UTF-8"])
man_argv.extend(["-P", "col -b", str(man_section), function.name])
output = subprocess.check_output(man_argv, stderr=devnull)
match = re.search(r"^SYNOPSIS(?:.|\n)*?((?:^.+$\n)* {5}\w+[ \*\n]*" + function.name + r"\((?:.+\,\s*?$\n)*?(?:.+\;$\n))(?:.|\n)*^DESCRIPTION", output.decode('UTF-8', errors='replace'), re.MULTILINE)
if match:
decl = match.group(1)
for argm in re.finditer(r"[\(,]\s*(.+?)\s*\b(\w+)(?=[,\)])", decl):
typ = argm.group(1)
arg = argm.group(2)
if arg == "void":
continue
if arg == "...":
args.append("\", ...\" +");
continue
cast_pre = ""
cast_post = ""
annotate_pre = ""
annotate_post = ""
normalized_type = re.sub(r"\s+", "", typ)
if normalized_type.endswith("*restrict"):
normalized_type = normalized_type[:-8]
if normalized_type in ("char*", "constchar*"):
cast_pre = "Memory.readUtf8String("
cast_post = ")"
annotate_pre = "\\\""
annotate_post = " + \"\\\"\""
arg_index = len(args)
args.append("\"%(arg_delimiter)s%(arg_name)s=%(annotate_pre)s\" + %(cast_pre)sargs[%(arg_index)s]%(cast_post)s%(annotate_post)s +" % {
"arg_name": arg,
"arg_index": arg_index,
"arg_delimiter": ", " if arg_index > 0 else "",
"cast_pre": cast_pre,
"cast_post": cast_post,
"annotate_pre": annotate_pre,
"annotate_post": annotate_post
})
break
except Exception as e:
pass
if len(args) == 0:
log_str = "\"%(name)s()\"" % { "name": function.name }
else:
indent_outer = " "
indent_inner = " "
log_str = "\"%(name)s(\" +\n%(indent_inner)s%(args)s\n%(indent_outer)s\")\"" % {
"name": function.name,
"args": ("\n" + indent_inner).join(args),
"indent_outer": indent_outer,
"indent_inner": indent_inner
}
return """\
/*
* Auto-generated by Frida. Please modify to match the signature of %(display_name)s.
* This stub is currently auto-generated from manpages when available.
*
* For full API reference, see: http://www.frida.re/docs/javascript-api/
*/
{
/**
* Called synchronously when about to call %(display_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 (log, args, state) {
log(%(log_str)s);
},
/**
* Called synchronously when about to return from %(display_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 (log, retval, state) {
}
}
""" % {"display_name": display_name, "log_str": log_str}
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, reactor):
super(FileRepository, self).__init__()
self._reactor = reactor
self._handler_by_address = {}
self._handler_by_file = {}
self._changed_files = set()
self._last_change_id = 0
self._repo_dir = os.path.join(os.getcwd(), "__handlers__")
self._repo_monitors = {}
def ensure_handler(self, function):
entry = self._handler_by_address.get(function.absolute_address)
if entry is not None:
(function, handler, handler_file) = entry
return handler
handler = None
handler_files_to_try = []
if isinstance(function, ModuleFunction):
module_dir = os.path.join(self._repo_dir, to_filename(function.module.name))
module_handler_file = os.path.join(module_dir, to_handler_filename(function.name))
handler_files_to_try.append(module_handler_file)
any_module_handler_file = os.path.join(self._repo_dir, to_handler_filename(function.name))
handler_files_to_try.append(any_module_handler_file)
for handler_file in handler_files_to_try:
if os.path.isfile(handler_file):
with codecs.open(handler_file, 'r', 'utf-8') 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)
entry = (function, handler, handler_file)
self._handler_by_address[function.absolute_address] = entry
self._handler_by_file[handler_file] = entry
self._ensure_monitor(handler_file)
return handler
def _ensure_monitor(self, handler_file):
handler_dir = os.path.dirname(handler_file)
monitor = self._repo_monitors.get(handler_dir)
if monitor is None:
monitor = FileMonitor(handler_dir)
monitor.on('change', self._on_change)
self._repo_monitors[handler_dir] = monitor
def commit_handlers(self):
for monitor in self._repo_monitors.values():
monitor.enable()
def _on_change(self, changed_file, other_file, event_type):
if changed_file not in self._handler_by_file or event_type == 'changes-done-hint':
return
self._changed_files.add(changed_file)
self._last_change_id += 1
change_id = self._last_change_id
self._reactor.schedule(lambda: self._sync_handlers(change_id), delay=0.05)
def _sync_handlers(self, change_id):
if change_id != self._last_change_id:
return
changes = self._changed_files.copy()
self._changed_files.clear()
for changed_handler_file in changes:
(function, old_handler, handler_file) = self._handler_by_file[changed_handler_file]
with codecs.open(handler_file, 'r', 'utf-8') as f:
new_handler = f.read()
changed = new_handler != old_handler
if changed:
entry = (function, new_handler, handler_file)
self._handler_by_address[function.absolute_address] = entry
self._handler_by_file[handler_file] = entry
self._notify_update(function, new_handler, handler_file)
class UI(object):
def on_trace_progress(self, operation):
pass
def on_trace_error(self, error):
pass
def on_trace_events(self, events):
pass
def on_trace_handler_create(self, function, handler, source):
pass
def on_trace_handler_load(self, function, handler, source):
pass
def to_filename(name):
result = ""
for c in name:
if c.isalnum() or c == ".":
result += c
else:
result += "_"
return result
def to_handler_filename(name):
full_filename = to_filename(name)
if len(full_filename) <= 41:
return full_filename + ".js"
crc = binascii.crc32(full_filename.encode())
return full_filename[0:32] + "_%08x.js" % crc
if __name__ == '__main__':
main()
+22
View File
@@ -0,0 +1,22 @@
project('frida-tools', 'c', version: '1.0.0')
python = get_option('with-python')
if python == ''
python = find_program('python3', required: false)
if not python.found()
python = find_program('python')
endif
endif
result = run_command(python, '-c',
'import sys; sys.stdout.write("%d.%d" % (sys.version_info[0], sys.version_info[1]))')
if result.returncode() != 0
error('Unable to detect Python version: ' + result.stdout() + result.stderr())
endif
python_version = result.stdout()
python_name = 'python' + python_version
python_site_packages = join_paths(get_option('libdir'), python_name, 'site-packages')
subdir('frida_tools')
subdir('scripts')
+1
View File
@@ -0,0 +1 @@
option('with-python', type: 'string', description: 'Python interpreter to build for')
+10
View File
@@ -0,0 +1,10 @@
#!@PYTHON@
from os.path import dirname, join, realpath
import sys
sys.path.insert(1, join(dirname(dirname(realpath(__file__))), '@pythondir@'))
import frida_tools.discoverer
if __name__ == '__main__':
frida_tools.discoverer.main()
+10
View File
@@ -0,0 +1,10 @@
#!@PYTHON@
from os.path import dirname, join, realpath
import sys
sys.path.insert(1, join(dirname(dirname(realpath(__file__))), '@pythondir@'))
import frida_tools.kill
if __name__ == '__main__':
frida_tools.kill.main()
+10
View File
@@ -0,0 +1,10 @@
#!@PYTHON@
from os.path import dirname, join, realpath
import sys
sys.path.insert(1, join(dirname(dirname(realpath(__file__))), '@pythondir@'))
import frida_tools.lsd
if __name__ == '__main__':
frida_tools.lsd.main()
+10
View File
@@ -0,0 +1,10 @@
#!@PYTHON@
from os.path import dirname, join, realpath
import sys
sys.path.insert(1, join(dirname(dirname(realpath(__file__))), '@pythondir@'))
import frida_tools.ps
if __name__ == '__main__':
frida_tools.ps.main()
+10
View File
@@ -0,0 +1,10 @@
#!@PYTHON@
from os.path import dirname, join, realpath
import sys
sys.path.insert(1, join(dirname(dirname(realpath(__file__))), '@pythondir@'))
import frida_tools.tracer
if __name__ == '__main__':
frida_tools.tracer.main()
+10
View File
@@ -0,0 +1,10 @@
#!@PYTHON@
from os.path import dirname, join, realpath
import sys
sys.path.insert(1, join(dirname(dirname(realpath(__file__))), '@pythondir@'))
import frida_tools.repl
if __name__ == '__main__':
frida_tools.repl.main()
+24
View File
@@ -0,0 +1,24 @@
scripts = [
'frida',
'frida-discover',
'frida-kill',
'frida-ls-devices',
'frida-ps',
'frida-trace',
]
cdata = configuration_data()
cdata.set('PYTHON', python)
cdata.set('pythondir', python_site_packages)
foreach s : scripts
generated_script = configure_file(
input: s + '.in',
output: s,
configuration: cdata,
)
install_data(generated_script,
install_dir: get_option('bindir'),
install_mode: 'rwxr-xr-x',
)
endforeach
Executable
+56
View File
@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
from setuptools import setup
setup(
name="frida-tools",
version="1.0.0",
description="Frida CLI tools",
long_description="CLI tools for [Frida](http://www.frida.re).",
long_description_content_type="text/markdown",
author="Frida Developers",
author_email="[email protected]",
url="https://www.frida.re",
install_requires=[
"colorama >= 0.2.7, < 1.0.0",
"frida >= 12.0.0, < 13.0.0",
"prompt-toolkit >= 0.57, < 2.0.0",
"pygments >= 2.0.2, < 3.0.0"
],
license="wxWindows Library Licence, Version 3.1",
zip_safe=True,
keywords="frida debugger dynamic instrumentation inject javascript windows macos linux ios iphone ipad android qnx",
classifiers=[
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Environment :: MacOS X",
"Environment :: Win32 (MS Windows)",
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"License :: OSI Approved",
"Natural Language :: English",
"Operating System :: MacOS :: MacOS X",
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: JavaScript",
"Topic :: Software Development :: Debuggers",
"Topic :: Software Development :: Libraries :: Python Modules"
],
packages=['frida_tools'],
entry_points={
'console_scripts': [
"frida = frida_tools.repl:main",
"frida-ls-devices = frida_tools.lsd:main",
"frida-ps = frida_tools.ps:main",
"frida-kill = frida_tools.kill:main",
"frida-discover = frida_tools.discoverer:main",
"frida-trace = frida_tools.tracer:main"
]
}
)
+4
View File
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from .test_discoverer import TestDiscoverer
from .test_tracer import TestTracer
+14
View File
@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
import platform
import os
system = platform.system()
if system == 'Windows':
target_program = r"C:\Windows\notepad.exe"
elif system == 'Darwin':
target_program = os.path.join(os.path.dirname(__file__), "unixvictim-macos")
else:
target_program = "/bin/cat"
__all__ = ['target_program']
BIN
View File
Binary file not shown.
+59
View File
@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
import platform
import subprocess
import threading
try:
import unittest2 as unittest
except:
import unittest
import frida
from frida_tools.application import Reactor
from frida_tools.discoverer import Discoverer, UI
from .data import target_program
class TestDiscoverer(unittest.TestCase):
@classmethod
def setUpClass(cls):
system = platform.system()
cls.target = subprocess.Popen([target_program], stdin=subprocess.PIPE)
cls.session = frida.attach(cls.target.pid)
@classmethod
def tearDownClass(cls):
cls.session.detach()
cls.target.terminate()
cls.target.stdin.close()
cls.target.wait()
def test_basics(self):
test_ui = TestUI()
reactor = Reactor(lambda reactor: test_ui.on_result.wait())
def start():
d = Discoverer(reactor)
d.start(self.session, test_ui)
reactor.schedule(d.stop, 0.1)
reactor.schedule(start)
reactor.run()
self.assertIsInstance(test_ui.module_functions, dict)
self.assertIsInstance(test_ui.dynamic_functions, list)
class TestUI(UI):
def __init__(self):
super(UI, self).__init__()
self.module_functions = None
self.dynamic_functions = None
self.on_result = threading.Event()
def on_sample_result(self, module_functions, dynamic_functions):
self.module_functions = module_functions
self.dynamic_functions = dynamic_functions
self.on_result.set()
if __name__ == '__main__':
unittest.main()
+47
View File
@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
import platform
import subprocess
import threading
try:
import unittest2 as unittest
except:
import unittest
import frida
from frida_tools.application import Reactor
from frida_tools.tracer import Tracer, TracerProfileBuilder, MemoryRepository, UI
from .data import target_program
class TestTracer(unittest.TestCase):
@classmethod
def setUpClass(cls):
system = platform.system()
cls.target = subprocess.Popen([target_program], stdin=subprocess.PIPE)
cls.session = frida.attach(cls.target.pid)
@classmethod
def tearDownClass(cls):
cls.session.detach()
cls.target.terminate()
cls.target.stdin.close()
cls.target.wait()
def test_basics(self):
done = threading.Event()
reactor = Reactor(lambda reactor: done.wait())
def start():
tp = TracerProfileBuilder().include("open*")
t = Tracer(reactor, MemoryRepository(), tp.build())
targets = t.start_trace(self.session, UI())
t.stop()
reactor.stop()
done.set()
reactor.schedule(start)
reactor.run()
if __name__ == '__main__':
unittest.main()