Merge pull request #21 from Fitblip/master

CLI improvements.
This commit is contained in:
Ole André Vadla Ravnås
2015-03-29 01:22:21 +01:00
3 changed files with 187 additions and 21 deletions
+1
View File
@@ -32,3 +32,4 @@
/src/frida/Makefile
/src/frida/Makefile.in
/stamp-h1
.idea/
+185 -20
View File
@@ -2,8 +2,12 @@ def main():
from frida.application import ConsoleApplication
from colorama import Fore, Style
import json
import platform
try:
import readline
# Stupid hack to workaround oxs's shitty readline impl
if platform.system() == "Darwin":
import gnureadline as readline
HAVE_READLINE = True
except:
HAVE_READLINE = False
@@ -14,7 +18,16 @@ def main():
def __init__(self):
if HAVE_READLINE:
readline.parse_and_bind("tab: complete")
# force completor to run on first tab press
readline.parse_and_bind("set show-all-if-unmodified on")
readline.parse_and_bind("set show-all-if-ambiguous on")
# Set our custom completer
readline.set_completer(self.completer)
self._idle = threading.Event()
self._cond = threading.Condition()
self._response = None
self._completor_locals = []
super(REPLApplication, self).__init__(self._process_input)
def _usage(self):
@@ -24,6 +37,7 @@ def main():
return True
def _start(self):
self._prompt_string = self._create_prompt()
def on_message(message, data):
self._reactor.schedule(lambda: self._process_message(message, data))
self._script = self._session.create_script(self._create_repl_script())
@@ -41,8 +55,7 @@ def main():
self._script = None
def _create_repl_script(self):
return """\
return """
(function () {
this.resume = function () {
send({ name: '+resume' });
@@ -66,17 +79,27 @@ def main():
function onExpression(expression) {
try {
var result;
eval("result = " + expression + ";");
var sentRaw = false;
if (result && result.hasOwnProperty('length')) {
try {
send({ name: '+result', payload: "OOB" }, result);
sentRaw = true;
} catch (e) {
// If we passed in an object
// TODO: Fix this gross nonsense
if (typeof(expression) == "object"){
if (expression.eval){
eval("result = " + expression.eval + ";");
}
send({ name: '+silent_result', payload: result });
// Otherwise expression is a string
} else {
eval("result = " + expression + ";");
var sentRaw = false;
if (result && result.hasOwnProperty('length')) {
try {
send({ name: '+result', payload: "OOB" }, result);
sentRaw = true;
} catch (e) {
}
}
if (!sentRaw) {
send({ name: '+result', payload: result });
}
}
if (!sentRaw) {
send({ name: '+result', payload: result });
}
} catch (e) {
send({ name: '+error', payload: e.toString() });
@@ -87,11 +110,78 @@ def main():
}).call(this);
"""
def _synchronous_evaluate(self, text):
self._reactor.schedule(lambda: self._send_expression({"eval": text}))
self._cond.acquire()
while self._response is None:
self._cond.wait()
response = self._response
self._response = None
self._cond.release()
# TODO: This is super gross, need to actually fix this and unify conventions
# once we know what we're doing with messages.
if "payload" not in response[0]:
response[0]['payload'] = None
return response[0]['payload']
def _create_prompt(self):
# Todo: Make this prompt less shitty and make sure all platforms are covered ;)
device_type = self._device.type
type_name = self._target[0]
target = self._target[1]
if device_type == "local":
if self._target[0] == "name":
type_name = "ProcName"
elif self._target[0] == "pid":
type_name = "PID"
prompt_string = "%s::%s::%s" % ("Local", type_name, target)
elif device_type == "tether" :
prompt_string = "%s::%s::%s" % ("USB", self._device.name, target)
else:
prompt_string = "%s::%s::%s" % (self._device.name, self._device.name, target)
return prompt_string
def completer(self, prefix, index):
# If it's the first loop, do the init
if index == 0:
# If nothing is in the readline buffer
if not prefix:
# Give every key that doesn't start with a "_"
self._completor_locals = [key for key in self._synchronous_evaluate("Object.keys(this)") if not key.startswith('_')]
else:
if prefix.endswith("."):
thing_to_check = prefix.split(' ')[0][:-1]
self._completor_locals = self._synchronous_evaluate("Object.keys(" + thing_to_check + ")")
else:
if "." in prefix:
# grab the last statement
target = prefix.split(' ')[-1]
needle = target.split('.')[-1]
# Chop off everything
target = ".".join(target.split('.')[:-1])
self._completor_locals = [target + "." + key for key in self._synchronous_evaluate("Object.keys(" + target + ")") if key.startswith(needle)]
# Do stuff
# Give every key that starts with the root prefix
else:
self._completor_locals = [key for key in self._synchronous_evaluate("Object.keys(this)") if key.startswith(prefix)]
try:
return self._completor_locals[index]
except IndexError:
return None
def _process_input(self):
if sys.version_info[0] >= 3:
input_impl = input
else:
input_impl = raw_input
self._print_startup_message()
while True:
self._idle.wait()
expression = ""
@@ -99,10 +189,12 @@ def main():
while len(expression) == 0 or line.endswith("\\"):
try:
if len(expression) == 0:
line = input_impl(">>> ")
line = input_impl("[%s]" % self._prompt_string + "-> ")
else:
line = input_impl("... ")
except EOFError:
# An extra newline after EOF to exit the REPL cleanly
print "\nThank you for using Frida!"
return
if len(line.strip()) > 0:
if len(expression) > 0:
@@ -111,8 +203,21 @@ def main():
if HAVE_READLINE:
readline.add_history(expression)
self._idle.clear()
self._reactor.schedule(lambda: self._send_expression(expression))
if expression.endswith("?"):
# Help feature
self._print_help(expression)
elif expression.startswith("%"):
# "Magic" commands
self._do_magic(expression)
elif expression in ("quit", "q", "exit"):
print "Thank you for using Frida!"
return
elif expression == "help":
print "Frida help 1.0!"
else:
self._idle.clear()
self._reactor.schedule(lambda: self._send_expression(expression))
def _send_expression(self, expression):
self._script.post_message(expression)
@@ -136,18 +241,79 @@ def main():
output = Fore.RED + Style.BRIGHT + value + Style.RESET_ALL
sys.stdout.write(output + "\n")
sys.stdout.flush()
self._idle.set()
handled = True
elif name == '+resume':
self._resume()
self._idle.set()
handled = True
elif name == "+silent_result":
# TODO: This is hacky as shit, need to make it cleaner/better
self._cond.acquire()
self._response = (stanza, data)
self._cond.notify()
self._cond.release()
self._idle.set()
handled = True
if not handled:
print("message:", message, "data:", data)
def _print_startup_message(self):
print """ _____
(_____)
| | Frida v3.0 - A world-class dynamic instrumentation framework
| |
|`-'| Commands:
| | help -> Displays the help system
| | object? -> Display information about 'object'
| | exit/quit -> Exit
| |
| | More info at http://www.frida.re/docs/home/
`._.'
"""
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 = self._synchronous_evaluate("typeof(%s)" % obj_to_identify)
if obj_type == "function":
signature = self._synchronous_evaluate("%s.toString()" % obj_to_identify)
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._synchronous_evaluate("%s.toString()" % obj_to_identify)
help_text += "Docstring: #TODO :)"
print help_text
def _do_magic(self, expression):
#TODO: add local file read capabilities i.e. %run /tmp/script.txt, and other stuff?
print "You thought I was sleeping, didn't you. Acting."
def hexdump(src, length=16):
try:
xrange
@@ -171,6 +337,5 @@ def main():
app = REPLApplication()
app.run()
if __name__ == '__main__':
main()
+1 -1
View File
@@ -60,7 +60,7 @@ setup(
"frida-ps = frida.ps:main",
"frida-discover = frida.discoverer:main",
"frida-trace = frida.tracer:main",
"frida-repl = frida.repl:main"
"frida = frida.repl:main"
]
},
ext_modules=[Extension('_frida', [])],