def __init__(self, server, session_id, arguments): cmd.Cmd.__init__(self) self.__base = "" self.__has_context = None self.__module_pushed_completers = 0 self.__permissions = None self.__server = server self.__session_id = session_id self.__onecmd = arguments.onecmd self.active = True self.aliases = {"l": "list", "ls": "list", "ll": "list"} self.intro = "drozer Console (v%s)" % meta.version self.history_file = os.path.sep.join( [os.path.expanduser("~"), ".drozer_history"]) self.modules = collection.ModuleCollection(loader.ModuleLoader()) self.prompt = "dz> " self.reflector = Reflector(self) if hasattr(arguments, 'no_color') and not arguments.no_color: # session stdout self.stdout = ColouredStream(self.stdout) self.filestdout = FileColouredStream(self.stdout) # session xmlstdout self.txmlstdout = XMLColouredStream(self.stdout) self.sqlstdout = MYSQLDB() self.stderr = ColouredStream(self.stderr) else: self.stdout = DecolouredStream(self.stdout) self.stderr = DecolouredStream(self.stderr) m = Module(self) if m.has_context(): dataDir = str(m.getContext().getApplicationInfo().dataDir) else: dataDir = str( m.new("java.io.File", ".").getCanonicalPath().native()) self.variables = { 'PATH': dataDir + '/bin:/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin', 'WD': dataDir } self.__load_variables() if arguments.onecmd == None: self.__print_banner()
def __init__(self, server: ServerConnector, session_id: str, arguments): cmd.Cmd.__init__(self) self.__base = "" self.__has_context = None self.__module_pushed_completers = 0 self.__permissions = None self.__server = server self.__session_id = session_id self.__onecmd = arguments.onecmd self.active = True self.aliases = {"l": "list", "ls": "list", "ll": "list"} self.intro = "drozer Console (v%s)" % meta.version self.history_file = os.path.sep.join( [os.path.expanduser("~"), ".drozer_history"]) self.modules = collection.ModuleCollection(loader.ModuleLoader()) self.prompt = "dz> " self.reflector = Reflector(self) self.ftp = Ftp(self) if hasattr(arguments, 'no_color') and not arguments.no_color: self.stdout = ColouredStream(self.stdout) self.stderr = ColouredStream(self.stderr) else: self.stdout = DecolouredStream(self.stdout) self.stderr = DecolouredStream(self.stderr) self.agent_version: int = 0 try: self.agent_version: int = int( self.reflector.resolve("com.mwr.dz.BuildConfig").VERSION_CODE) except: pass m = Module(self) if m.has_context(): dataDir = str(m.getContext().getApplicationInfo().dataDir) else: dataDir = str( m.new("java.io.File", ".").getCanonicalPath().native()) self.variables = { 'PATH': dataDir + '/bin:/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin', 'WD': dataDir } self.__load_variables() if arguments.onecmd is None: self.__print_banner()
def __init__(self, server, session_id): cmd.Cmd.__init__(self) self.__base = "" self.__reflector = Reflector(self) self.__server = server self.__session_id = session_id self.active = True self.aliases = { "ls": "list" } self.intro = "Mercury Console" self.history_file = ".mercury_history" self.prompt = "mercury> " self.stdout = ColouredStream(self.stdout) self.stderr = ColouredStream(self.stderr)
def __init__(self, server, session_id): cmd.Cmd.__init__(self) self.__base = "" self.__module_pushed_completers = 0 self.__reflector = Reflector(self) self.__server = server self.__session_id = session_id self.active = True self.aliases = { "l": "list", "ls": "list", "ll": "list" } self.intro = "Mercury Console" self.history_file = os.path.sep.join([os.path.expanduser("~"), ".mercury_history"]) self.prompt = "mercury> " self.stdout = ColouredStream(self.stdout) self.stderr = ColouredStream(self.stderr)
def __init__(self, server, session_id): cmd.Cmd.__init__(self) self.__base = "" self.__module_pushed_completers = 0 self.__reflector = Reflector(self) self.__server = server self.__session_id = session_id self.active = True self.aliases = {"l": "list", "ls": "list", "ll": "list"} self.intro = "Mercury Console" self.history_file = os.path.sep.join( [os.path.expanduser("~"), ".mercury_history"]) self.prompt = "mercury> " self.stdout = ColouredStream(self.stdout) self.stderr = ColouredStream(self.stderr)
def __init__(self, server, session_id): cmd.Cmd.__init__(self) self.__base = "" self.__module_pushed_completers = 0 self.__permissions = None self.__reflector = Reflector(self) self.__server = server self.__session_id = session_id self.active = True self.aliases = { "l": "list", "ls": "list", "ll": "list" } self.intro = "Mercury Console" self.history_file = os.path.sep.join([os.path.expanduser("~"), ".mercury_history"]) self.prompt = "mercury> " self.stdout = ColouredStream(self.stdout) self.stderr = ColouredStream(self.stderr) self.variables = { 'PATH': '/data/data/com.mwr.droidhg.agent/bin:/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin', 'WD': '/data/data/com.mwr.droidhg.agent' } self.__load_variables()
class Session(cmd.Cmd): """ Mercury: the Heavy Metal that Poisoned the Droid Type `help COMMAND` for more information on a particular command, or `help MODULE` for a particular module. """ def __init__(self, server, session_id): cmd.Cmd.__init__(self) self.__base = "" self.__reflector = Reflector(self) self.__server = server self.__session_id = session_id self.active = True self.aliases = { "ls": "list" } self.intro = "Mercury Console" self.history_file = ".mercury_history" self.prompt = "mercury> " self.stdout = ColouredStream(self.stdout) self.stderr = ColouredStream(self.stderr) def completefilename(self, text, line, begidx, endidx): """ Provides readline auto-completion for filenames on the local (Console) file system. """ return common.path_completion.on_console(text) def completemodules(self, text): """ Provides readline auto-completion for Mercury module names. """ if self.__base == "": return filter(lambda m: m.startswith(text), self.__modules()) elif text.startswith("."): return filter(lambda m: m.startswith(text[1:]), self.__modules()) else: return map(lambda m: m[len(self.__base):], filter(lambda m: m.startswith(self.__base + text), self.__modules())) def completenamespaces(self, text): """ Provides readline auto-completion for Mercury namespaces. """ if self.__base == "": return filter(lambda m: m.startswith(text), self.__namespaces()) elif text.startswith("."): namespaces = self.__namespaces(global_scope=True) namespaces.add("..") return map(lambda m: "." + m, filter(lambda m: m.startswith(text[1:]), namespaces)) else: return map(lambda m: m[len(self.__base):], filter(lambda m: m.startswith(self.__base + text), self.__namespaces())) def do_cd(self, args): """ usage: cd NAMESPACE The namespace is taken as relative to the current location in the module tree: mercury> cd information mercury#information> cd native mercury#information.native> To specify an absolute path, prefix it with a period character: mercury#information.native> cd .package mercury#package> It is still possible to run commands from other namespaces, by specifying the absolute path (prefixed by a period) to the `run` command: mercury> cd package mercury#package> run .activity.info Passing an empty string to `cd` will switch back to the root namespace: mercury#information.native> cd mercury> Passing '..' will move up one level: mercury#information.native> cd .. mercury#information> """ argv = shlex.split(args, comments=True) if len(argv) == 1 and (argv[0] == "-h" or argv[0] == "--help"): self.do_help("cd") return if len(argv) == 0: argv.append("") if not self.__setBase(argv[0]): self.stderr.write("invalid path: %s\n"%argv[0]) def complete_cd(self, *args): """ Provides readline auto-completion for the `cd` command, suggesting namespaces. """ return self.completenamespaces(args[0]) def do_contributors(self, args): """ Display a list of Mercury contributors. """ argv = shlex.split(args, comments=True) if len(argv) == 1 and (argv[0] == "-h" or argv[0] == "--help"): self.do_help("contributors") return contributors = map(lambda m: Module.get(m).author, Module.all()) contribution = [(c[0], len(list(c[1]))) for c in itertools.groupby(sorted(flatten(contributors)))] self.stdout.write("Core Contributors:\n") for contributor in ['MWR InfoSecurity (@mwrlabs)', 'Luander ([email protected])', 'Rodrigo Chiossi ([email protected])']: self.stdout.write(" %s\n"%contributor) self.stdout.write("\nModule Contributors:\n") for contributor in sorted(contribution, key=lambda c: -c[1]): self.stdout.write(" %s\n"%contributor[0]) def do_exit(self, args): """ Terminate your Mercury session. """ if self.active: self.__server.stopSession(self.__session_id) self.active = False return True def do_help(self, args): """ usage: help [COMMAND OR MODULE] Displays help information. """ argv = shlex.split(args, comments=True) if len(argv) == 1 and (argv[0] == "-h" or argv[0] == "--help"): self.do_help("help") return if len(argv) > 0: if self.__module_name(argv[0]) in Module.all() or self.__module_name("." + argv[0]) in Module.all(): self.do_run(" ".join([argv[0], "--help"])) else: try: func = getattr(self, 'help_' + argv[0]) except AttributeError: try: doc = getattr(self, 'do_' + argv[0]).__doc__ if doc: self.stdout.write("%s\n" % wrap(textwrap.dedent(str(doc)).strip(), width=console.get_size()[0])) return except AttributeError: pass self.stdout.write("%s\n" % str(self.nohelp) % (argv[0],)) return func() else: cmd.Cmd.do_help(self, args) def complete_help(self, *args): """ Provides readline auto-completion for the `help` command, offering commands, modules and topics. """ commands = set(self.completenames(args[0])) modules = set(self.completemodules(args[0])) topics = set(a[5:] for a in self.get_names() if a.startswith('help_' + args[0])) return list(commands | modules | topics) def do_list(self, args): """ usage: list [FILTER] Displays a list of the available modules, optionally filtered by name. Examples: mercury> list activity.forintent activity.info ... snip ... mercury> list debug information.debuggable mercury> """ argv = shlex.split(args, comments=True) if len(argv) == 1 and (argv[0] == "-h" or argv[0] == "--help"): self.do_help("list") return term = len(argv) > 0 and argv[0] or None print console.format_dict(dict(map(lambda m: [m, Module.get(m).name], filter(lambda m: term == None or m.find(term.lower()) >= 0, self.__modules())))) def do_load(self, args): """ usage: load FILE Loads a file, and executes it as a script. """ argv = shlex.split(args, comments=True) if len(argv) == 1 and (argv[0] == "-h" or argv[0] == "--help"): self.do_help("load") return if len(argv) > 0: try: Sequencer(argv).run(self) except KeyboardInterrupt: self.stderr.write("\nCaught SIGINT. Interrupt again to terminate you session.\n") except Exception as e: self.handleException(e) else: self.do_help("load") def complete_load(self, text, line, begidx, endidx): """ Provides readline auto-completion for the `load` command, offering local file names. """ return self.completefilename(text, line, begidx, endidx) def do_module(self, args): """ module [COMMAND] (inside Mercury) Run the Mercury Module and Repository Manager. The Repository Manager handles Mercury modules and module repositories. """ ModuleManager().run(shlex.split(args, comments=True)) Module.reload() def do_run(self, args): """ usage: run MODULE [OPTIONS] To see the options for a particular module, run `help MODULE`. """ argv = shlex.split(args, comments=True) if len(argv) == 1 and (argv[0] == "-h" or argv[0] == "--help"): self.do_help("run") return if len(argv) > 0: try: module = self.__module(argv[0]) except KeyError as e: self.stderr.write("unknown module: %s\n" % str(e)) return None try: module.run(argv[1:]) except KeyboardInterrupt: self.stderr.write("\nCaught SIGINT. Interrupt again to terminate you session.\n") except Exception as e: self.handleException(e) else: self.do_help("run") def complete_run(self, text, line, begidx, endidx): """ Provides readline auto-completion for the `run` command. If auto-completion is requested on the first token after the 'run' command, we offer module names. Otherwise, we delegate to the complete() method defined on the specified module. """ _line = re.match("(run\s+)([^\s]*)(\s*)", line) # figure out where the module name starts in the string cmdidx = len(_line.group(1)) if begidx == cmdidx: # if we are trying to autocomplete the module name, offer modules as suggestions return self.completemodules(text) else: # otherwise, we are trying to autocomplete some options for the module, and should # defer to it offset = len(_line.group(0)) # we pass over the arguments for autocompletion, but strip off the command and module # name for simplicity return self.__module(_line.group(2)).complete(text, line[offset:], begidx - offset, endidx - offset) def do_shell(self, args): """ usage: `! [COMMAND]` or `shell [COMMAND]` Execute a Linux command in the context of Mercury. If a COMMAND is specified, this is shorthand for `run shell.exec COMMAND`. Otherwise, it will launch an interactive shell. Example: mercury> ! date Fri Dec 21 23:59:59 GMT 2012 mercury> ! cat /etc/hosts 127.0.0.1 localhost The working directory of your shell will be the Mercury Agent root folder. """ if len(args) > 0: return self.do_run(".shell.exec \"%s\"" % args) else: return self.do_run(".shell.start") def sendAndReceive(self, message): """ Delivers a message to the Agent, and returns the response. If the send operation times out, or the response indicates a fatal error, an error message is displayed and the console terminates with a status code of -1. """ try: message = self.__server.sendAndReceive(message.setSessionId(self.__session_id)) except socket.timeout: self.stderr.write("lost session: %s\n"%self.__session_id) sys.exit(-1) if message and message.type == Message.REFLECTION_RESPONSE and message.reflection_response.status == Message.ReflectionResponse.FATAL: self.stderr.write("lost session: %s\n"%self.__session_id) sys.exit(-1) return message def __module(self, key): """ Gets a module instance, by identifier, and initialises it with the required session parameters. """ module = None try: module = Module.get(self.__module_name(key)) except KeyError: pass if module == None: try: module = Module.get(key) except KeyError: pass if module == None: raise KeyError(key) else: return module(self.__reflector, self.stdout, self.stderr) def __module_name(self, key): """ Decodes a full module identifier, given a user's input. This helps to find modules after the user has changed namespace. """ if key.startswith("."): return key[1:] elif self.__base == "": return key else: return self.__base + key def __modules(self): """ Gets a full list of all module identifiers. """ if self.__base == "": return Module.all() else: return filter(lambda m: m.startswith(self.__base), Module.all()) def __namespaces(self, global_scope=False): """ Gets a full list of all namespace identifiers, either globally or in the current namespace scope. """ if global_scope: return set(map(lambda m: self.__module("." + m).namespace(), Module.all())) else: return set(map(lambda m: self.__module("." + m).namespace(), self.__modules())) def __setBase(self, base): """ Changes the user's namespace. Changing to: 'str' - selects the 'str' namespace, within the currently active namespace '.str' - selects the 'str' namespace, in the root namespace '..' - goes back on namespace '' - goes back to the root namespace """ if base == "": self.__base = base else: if base == "..": path = self.__base.split(".") try: path.pop(-2) except IndexError: pass target = ".".join(path) elif base.startswith("."): target = base[1:] + "." else: target = self.__base + base + "." if True in map(lambda m: m.startswith(target), Module.all()): self.__base = target else: self.stderr.write("no such namespace: %s\n"%base) if base == "": self.prompt = "mercury> " else: self.prompt = "mercury#{}> ".format(self.__base[0:len(self.__base)-1]) return True
class Session(cmd.Cmd): """ Mercury: the Heavy Metal that Poisoned the Droid Type `help COMMAND` for more information on a particular command, or `help MODULE` for a particular module. """ def __init__(self, server, session_id): cmd.Cmd.__init__(self) self.__base = "" self.__module_pushed_completers = 0 self.__reflector = Reflector(self) self.__server = server self.__session_id = session_id self.active = True self.aliases = { "l": "list", "ls": "list", "ll": "list" } self.intro = "Mercury Console" self.history_file = os.path.sep.join([os.path.expanduser("~"), ".mercury_history"]) self.prompt = "mercury> " self.stdout = ColouredStream(self.stdout) self.stderr = ColouredStream(self.stderr) def completefilename(self, text, line, begidx, endidx): """ Provides readline auto-completion for filenames on the local (Console) file system. """ return common.path_completion.on_console(text) def completemodules(self, text): """ Provides readline auto-completion for Mercury module names. """ if self.__base == "": return filter(lambda m: m.startswith(text), self.__modules()) elif text.startswith("."): return filter(lambda m: m.startswith(text[1:]), self.__modules()) else: return map(lambda m: m[len(self.__base):], filter(lambda m: m.startswith(self.__base + text), self.__modules())) def completenamespaces(self, text): """ Provides readline auto-completion for Mercury namespaces. """ if self.__base == "": return filter(lambda m: m.startswith(text), self.__namespaces()) elif text.startswith("."): namespaces = self.__namespaces(global_scope=True) namespaces.add("..") return map(lambda m: "." + m, filter(lambda m: m.startswith(text[1:]), namespaces)) else: return map(lambda m: m[len(self.__base):], filter(lambda m: m.startswith(self.__base + text), self.__namespaces())) def do_cd(self, args): """ usage: cd NAMESPACE The namespace is taken as relative to the current location in the module tree: mercury> cd information mercury#information> cd native mercury#information.native> To specify an absolute path, prefix it with a period character: mercury#information.native> cd .package mercury#package> It is still possible to run commands from other namespaces, by specifying the absolute path (prefixed by a period) to the `run` command: mercury> cd package mercury#package> run .activity.info Passing an empty string to `cd` will switch back to the root namespace: mercury#information.native> cd mercury> Passing '..' will move up one level: mercury#information.native> cd .. mercury#information> """ argv = shlex.split(args, comments=True) if len(argv) == 1 and (argv[0] == "-h" or argv[0] == "--help"): self.do_help("cd") return if len(argv) == 0: argv.append("") if not self.__setBase(argv[0]): self.stderr.write("invalid path: %s\n"%argv[0]) def complete_cd(self, *args): """ Provides readline auto-completion for the `cd` command, suggesting namespaces. """ return self.completenamespaces(args[0]) def do_clean(self, args): """ usage: clean Cleans APK and DEX files from Mercury's cache. During normal operation, Mercury uploads a number of APK files to your device, and extracts the DEX bytecode from others already on your device. This can start to consume a large amount of space, particularly if you are developing Mercury modules. The `clean` command removes all of these cached files for you. Mercury will automatically re-upload any files that it needs as you continue to use it. """ files = clean.clean(self.__reflector) self.stdout.write("Removed %d cached files.\n" % files) def do_contributors(self, args): """ Display a list of Mercury contributors. """ argv = shlex.split(args, comments=True) if len(argv) == 1 and (argv[0] == "-h" or argv[0] == "--help"): self.do_help("contributors") return contributors = map(lambda m: Module.get(m).author, Module.all()) contribution = [(c[0], len(list(c[1]))) for c in itertools.groupby(sorted(flatten(contributors)))] self.stdout.write("Core Contributors:\n") for contributor in ['MWR InfoSecurity (@mwrlabs)', 'Luander ([email protected])', 'Rodrigo Chiossi ([email protected])']: self.stdout.write(" %s\n"%contributor) self.stdout.write("\nModule Contributors:\n") for contributor in sorted(contribution, key=lambda c: -c[1]): self.stdout.write(" %s\n"%contributor[0]) def do_exit(self, args): """ Terminate your Mercury session. """ if self.active: self.__server.stopSession(self.__session_id) self.active = False return True def do_help(self, args): """ usage: help [COMMAND OR MODULE] Displays help information. """ argv = shlex.split(args, comments=True) if len(argv) == 1 and (argv[0] == "-h" or argv[0] == "--help"): self.do_help("help") return if len(argv) > 0: if self.__module_name(argv[0]) in Module.all() or self.__module_name("." + argv[0]) in Module.all(): self.do_run(" ".join([argv[0], "--help"])) else: try: func = getattr(self, 'help_' + argv[0]) except AttributeError: try: doc = getattr(self, 'do_' + argv[0]).__doc__ if doc: self.stdout.write("%s\n" % wrap(textwrap.dedent(str(doc)).strip(), width=console.get_size()[0])) return except AttributeError: pass self.stdout.write("%s\n" % str(self.nohelp) % (argv[0],)) return func() else: cmd.Cmd.do_help(self, args) def complete_help(self, *args): """ Provides readline auto-completion for the `help` command, offering commands, modules and topics. """ commands = set(self.completenames(args[0])) modules = set(self.completemodules(args[0])) topics = set(a[5:] for a in self.get_names() if a.startswith('help_' + args[0])) return list(commands | modules | topics) def do_list(self, args): """ usage: list [FILTER] Displays a list of the available modules, optionally filtered by name. Examples: mercury> list activity.forintent activity.info ... snip ... mercury> list debug information.debuggable mercury> """ argv = shlex.split(args, comments=True) if len(argv) == 1 and (argv[0] == "-h" or argv[0] == "--help"): self.do_help("list") return term = len(argv) > 0 and argv[0] or None self.stdout.write(console.format_dict(dict(map(lambda m: [m, Module.get(m).name], filter(lambda m: term == None or m.find(term.lower()) >= 0, self.__modules())))) + "\n") def do_load(self, args): """ usage: load FILE Loads a file, and executes it as a script. """ argv = shlex.split(args, comments=True) if len(argv) == 1 and (argv[0] == "-h" or argv[0] == "--help"): self.do_help("load") return if len(argv) > 0: try: Sequencer(argv).run(self) except KeyboardInterrupt: self.stderr.write("\nCaught SIGINT. Interrupt again to terminate you session.\n") except Exception as e: self.handleException(e) else: self.do_help("load") def complete_load(self, text, line, begidx, endidx): """ Provides readline auto-completion for the `load` command, offering local file names. """ return self.completefilename(text, line, begidx, endidx) def do_module(self, args): """ module [COMMAND] (inside Mercury) Run the Mercury Module and Repository Manager. The Repository Manager handles Mercury modules and module repositories. """ ModuleManager().run(shlex.split(args, comments=True)) Module.reload() def do_run(self, args): """ usage: run MODULE [OPTIONS] To see the options for a particular module, run `help MODULE`. """ argv = shlex.split(args, comments=True) if len(argv) == 1 and (argv[0] == "-h" or argv[0] == "--help"): self.do_help("run") return if len(argv) > 0: try: module = self.__module(argv[0]) module.push_completer = self.__push_module_completer module.pop_completer = self.__pop_module_completer self.__module_pushed_completers = 0 except KeyError as e: self.stderr.write("unknown module: %s\n" % str(e)) return None try: module.run(argv[1:]) except KeyboardInterrupt: self.stderr.write("\nCaught SIGINT. Interrupt again to terminate you session.\n") except Exception as e: self.handleException(e) while self.__module_pushed_completers > 0: self.__pop_module_completer() else: self.do_help("run") def complete_run(self, text, line, begidx, endidx): """ Provides readline auto-completion for the `run` command. If auto-completion is requested on the first token after the 'run' command, we offer module names. Otherwise, we delegate to the complete() method defined on the specified module. """ _line = re.match("(run\s+)([^\s]*)(\s*)", line) # figure out where the module name starts in the string cmdidx = len(_line.group(1)) if begidx == cmdidx: # if we are trying to autocomplete the module name, offer modules as suggestions return self.completemodules(text) else: # otherwise, we are trying to autocomplete some options for the module, and should # defer to it offset = len(_line.group(0)) # we pass over the arguments for autocompletion, but strip off the command and module # name for simplicity return self.__module(_line.group(2)).complete(text, line[offset:], begidx - offset, endidx - offset) def do_shell(self, args): """ usage: `! [COMMAND]` or `shell [COMMAND]` Execute a Linux command in the context of Mercury. If a COMMAND is specified, this is shorthand for `run shell.exec COMMAND`. Otherwise, it will launch an interactive shell. Example: mercury> ! date Fri Dec 21 23:59:59 GMT 2012 mercury> ! cat /etc/hosts 127.0.0.1 localhost The working directory of your shell will be the Mercury Agent root folder. """ if len(args) > 0: return self.do_run(".shell.exec \"%s\"" % args) else: return self.do_run(".shell.start") def help_intents(self): """ An intent is an abstract description of an operation to be performed. It can be used with app.activity.start to launch an Activity, app.broadcast.send to send it to any interested BroadcastReceiver components, and app.service.start to communicate with a background Service. An Intent provides a facility for performing late runtime binding between the code in different applications. Its most significant use is in the launching of activities, where it can be thought of as the glue between activities. It is basically a passive data structure holding an abstract description of an action to be performed. Intent Structure ---------------- The primary pieces of information in an intent are: action: the general action to be performed data: the data to operate on In addition to these primary attributes, there are a number of secondary attributes that you can also include with an intent: category: gives additional information about the action to execute type: specifies an explicit MIME type (a MIME type) of the data component: specifies an explicit component class extras: a bundle of any additional information Put together, the set of actions, data types, categories, and extra data defines a language for the system allowing for the expression of phrases such as "call john smith's cell". As applications are added to the system, they can extend this language by adding new actions, types, and categories, or they can modify the behavior of existing phrases by supplying their own activities that handle them. Intent Formulation ------------------ In Mercury, intents are formulated using a set of command-line options. Some of these set a simple String in the Intent: --action ACTION --category CATEGORY --component PACKAGE COMPONENT --data-uri URI --flags FLAG [FLAG ...] --mimetype TYPE When specifying a component, the fully-qualified name of both the package and component must be used, for example to specify the BrowserActivity within the com.android.browser package: --component com.android.browser com.android.browser.BrowserActivity Intents can carry messages or commands inside of them in the form of extras. Applications may want to pass additional information inside of the intents they send to one another, possibly containing the data to perform a task on, or any other user-defined task to initiate from the received data. Passing the extras is a little more complex. You need to tell Mercury the data type, key and value: --extra TYPE KEY VALUE Mercury supports a few common types: boolean byte char double float integer short string """ self.stdout.write(wrap(textwrap.dedent(self.help_intents.__doc__).strip() + "\n\n", console.get_size()[0])) def sendAndReceive(self, message): """ Delivers a message to the Agent, and returns the response. If the send operation times out, or the response indicates a fatal error, an error message is displayed and the console terminates with a status code of -1. """ try: message = self.__server.sendAndReceive(message.setSessionId(self.__session_id)) except socket.timeout: self.stderr.write("lost session: %s\n"%self.__session_id) sys.exit(-1) if message and message.type == Message.REFLECTION_RESPONSE and message.reflection_response.status == Message.ReflectionResponse.FATAL: self.stderr.write("lost session: %s\n"%self.__session_id) sys.exit(-1) return message def __module(self, key): """ Gets a module instance, by identifier, and initialises it with the required session parameters. """ module = None try: module = Module.get(self.__module_name(key)) except KeyError: pass if module == None: try: module = Module.get(key) except KeyError: pass if module == None: raise KeyError(key) else: return module(self.__reflector, self.stdout, self.stderr) def __module_name(self, key): """ Decodes a full module identifier, given a user's input. This helps to find modules after the user has changed namespace. """ if key.startswith("."): return key[1:] elif self.__base == "": return key else: return self.__base + key def __modules(self): """ Gets a full list of all module identifiers. """ if self.__base == "": return Module.all() else: return filter(lambda m: m.startswith(self.__base), Module.all()) def __namespaces(self, global_scope=False): """ Gets a full list of all namespace identifiers, either globally or in the current namespace scope. """ if global_scope: return set(map(lambda m: self.__module("." + m).namespace(), Module.all())) else: return set(map(lambda m: self.__module("." + m).namespace(), self.__modules())) def __push_module_completer(self, completer, history_file=None): """ Delegate, passed to the module, so it can add a new readline completer to the stack. """ self.__module_pushed_completers += 1 self.push_completer(completer, history_file) def __pop_module_completer(self): """ Delegate, passed to the module, so it can add a remove a readline completer from the stack. """ self.__module_pushed_completers -= 1 self.pop_completer() def __setBase(self, base): """ Changes the user's namespace. Changing to: 'str' - selects the 'str' namespace, within the currently active namespace '.str' - selects the 'str' namespace, in the root namespace '..' - goes back on namespace '' - goes back to the root namespace """ if base == "": self.__base = base else: if base == "..": path = self.__base.split(".") try: path.pop(-2) except IndexError: pass target = ".".join(path) elif base.startswith("."): target = base[1:] + "." else: target = self.__base + base + "." if True in map(lambda m: m.startswith(target), Module.all()): self.__base = target else: self.stderr.write("no such namespace: %s\n"%base) if base == "": self.prompt = "mercury> " else: self.prompt = "mercury#{}> ".format(self.__base[0:len(self.__base)-1]) return True
class Session(cmd.Cmd): """ Mercury: the Heavy Metal that Poisoned the Droid Type `help COMMAND` for more information on a particular command, or `help MODULE` for a particular module. """ def __init__(self, server, session_id): cmd.Cmd.__init__(self) self.__base = "" self.__module_pushed_completers = 0 self.__reflector = Reflector(self) self.__server = server self.__session_id = session_id self.active = True self.aliases = {"l": "list", "ls": "list", "ll": "list"} self.intro = "Mercury Console" self.history_file = os.path.sep.join( [os.path.expanduser("~"), ".mercury_history"]) self.prompt = "mercury> " self.stdout = ColouredStream(self.stdout) self.stderr = ColouredStream(self.stderr) def completefilename(self, text, line, begidx, endidx): """ Provides readline auto-completion for filenames on the local (Console) file system. """ return common.path_completion.on_console(text) def completemodules(self, text): """ Provides readline auto-completion for Mercury module names. """ if self.__base == "": return filter(lambda m: m.startswith(text), self.__modules()) elif text.startswith("."): return filter(lambda m: m.startswith(text[1:]), self.__modules()) else: return map( lambda m: m[len(self.__base):], filter(lambda m: m.startswith(self.__base + text), self.__modules())) def completenamespaces(self, text): """ Provides readline auto-completion for Mercury namespaces. """ if self.__base == "": return filter(lambda m: m.startswith(text), self.__namespaces()) elif text.startswith("."): namespaces = self.__namespaces(global_scope=True) namespaces.add("..") return map(lambda m: "." + m, filter(lambda m: m.startswith(text[1:]), namespaces)) else: return map( lambda m: m[len(self.__base):], filter(lambda m: m.startswith(self.__base + text), self.__namespaces())) def do_cd(self, args): """ usage: cd NAMESPACE The namespace is taken as relative to the current location in the module tree: mercury> cd information mercury#information> cd native mercury#information.native> To specify an absolute path, prefix it with a period character: mercury#information.native> cd .package mercury#package> It is still possible to run commands from other namespaces, by specifying the absolute path (prefixed by a period) to the `run` command: mercury> cd package mercury#package> run .activity.info Passing an empty string to `cd` will switch back to the root namespace: mercury#information.native> cd mercury> Passing '..' will move up one level: mercury#information.native> cd .. mercury#information> """ argv = shlex.split(args, comments=True) if len(argv) == 1 and (argv[0] == "-h" or argv[0] == "--help"): self.do_help("cd") return if len(argv) == 0: argv.append("") if not self.__setBase(argv[0]): self.stderr.write("invalid path: %s\n" % argv[0]) def complete_cd(self, *args): """ Provides readline auto-completion for the `cd` command, suggesting namespaces. """ return self.completenamespaces(args[0]) def do_clean(self, args): """ usage: clean Cleans APK and DEX files from Mercury's cache. During normal operation, Mercury uploads a number of APK files to your device, and extracts the DEX bytecode from others already on your device. This can start to consume a large amount of space, particularly if you are developing Mercury modules. The `clean` command removes all of these cached files for you. Mercury will automatically re-upload any files that it needs as you continue to use it. """ files = clean.clean(self.__reflector) self.stdout.write("Removed %d cached files.\n" % files) def do_contributors(self, args): """ Display a list of Mercury contributors. """ argv = shlex.split(args, comments=True) if len(argv) == 1 and (argv[0] == "-h" or argv[0] == "--help"): self.do_help("contributors") return contributors = map(lambda m: Module.get(m).author, Module.all()) contribution = [ (c[0], len(list(c[1]))) for c in itertools.groupby(sorted(flatten(contributors))) ] self.stdout.write("Core Contributors:\n") for contributor in [ 'MWR InfoSecurity (@mwrlabs)', 'Luander ([email protected])', 'Rodrigo Chiossi ([email protected])' ]: self.stdout.write(" %s\n" % contributor) self.stdout.write("\nModule Contributors:\n") for contributor in sorted(contribution, key=lambda c: -c[1]): self.stdout.write(" %s\n" % contributor[0]) def do_exit(self, args): """ Terminate your Mercury session. """ if self.active: self.__server.stopSession(self.__session_id) self.active = False return True def do_help(self, args): """ usage: help [COMMAND OR MODULE] Displays help information. """ argv = shlex.split(args, comments=True) if len(argv) == 1 and (argv[0] == "-h" or argv[0] == "--help"): self.do_help("help") return if len(argv) > 0: if self.__module_name(argv[0]) in Module.all( ) or self.__module_name("." + argv[0]) in Module.all(): self.do_run(" ".join([argv[0], "--help"])) else: try: func = getattr(self, 'help_' + argv[0]) except AttributeError: try: doc = getattr(self, 'do_' + argv[0]).__doc__ if doc: self.stdout.write( "%s\n" % wrap(textwrap.dedent(str(doc)).strip(), width=console.get_size()[0])) return except AttributeError: pass self.stdout.write("%s\n" % str(self.nohelp) % (argv[0], )) return func() else: cmd.Cmd.do_help(self, args) def complete_help(self, *args): """ Provides readline auto-completion for the `help` command, offering commands, modules and topics. """ commands = set(self.completenames(args[0])) modules = set(self.completemodules(args[0])) topics = set(a[5:] for a in self.get_names() if a.startswith('help_' + args[0])) return list(commands | modules | topics) def do_list(self, args): """ usage: list [FILTER] Displays a list of the available modules, optionally filtered by name. Examples: mercury> list activity.forintent activity.info ... snip ... mercury> list debug information.debuggable mercury> """ argv = shlex.split(args, comments=True) if len(argv) == 1 and (argv[0] == "-h" or argv[0] == "--help"): self.do_help("list") return term = len(argv) > 0 and argv[0] or None self.stdout.write( console.format_dict( dict( map( lambda m: [m, Module.get(m).name], filter( lambda m: term == None or m.find(term.lower()) >= 0, self.__modules())))) + "\n") def do_load(self, args): """ usage: load FILE Loads a file, and executes it as a script. """ argv = shlex.split(args, comments=True) if len(argv) == 1 and (argv[0] == "-h" or argv[0] == "--help"): self.do_help("load") return if len(argv) > 0: try: Sequencer(argv).run(self) except KeyboardInterrupt: self.stderr.write( "\nCaught SIGINT. Interrupt again to terminate you session.\n" ) except Exception as e: self.handleException(e) else: self.do_help("load") def complete_load(self, text, line, begidx, endidx): """ Provides readline auto-completion for the `load` command, offering local file names. """ return self.completefilename(text, line, begidx, endidx) def do_module(self, args): """ module [COMMAND] (inside Mercury) Run the Mercury Module and Repository Manager. The Repository Manager handles Mercury modules and module repositories. """ ModuleManager().run(shlex.split(args, comments=True)) Module.reload() def do_run(self, args): """ usage: run MODULE [OPTIONS] To see the options for a particular module, run `help MODULE`. """ argv = shlex.split(args, comments=True) if len(argv) == 1 and (argv[0] == "-h" or argv[0] == "--help"): self.do_help("run") return if len(argv) > 0: try: module = self.__module(argv[0]) module.push_completer = self.__push_module_completer module.pop_completer = self.__pop_module_completer self.__module_pushed_completers = 0 except KeyError as e: self.stderr.write("unknown module: %s\n" % str(e)) return None try: module.run(argv[1:]) except KeyboardInterrupt: self.stderr.write( "\nCaught SIGINT. Interrupt again to terminate you session.\n" ) except Exception as e: self.handleException(e) while self.__module_pushed_completers > 0: self.__pop_module_completer() else: self.do_help("run") def complete_run(self, text, line, begidx, endidx): """ Provides readline auto-completion for the `run` command. If auto-completion is requested on the first token after the 'run' command, we offer module names. Otherwise, we delegate to the complete() method defined on the specified module. """ _line = re.match("(run\s+)([^\s]*)(\s*)", line) # figure out where the module name starts in the string cmdidx = len(_line.group(1)) if begidx == cmdidx: # if we are trying to autocomplete the module name, offer modules as suggestions return self.completemodules(text) else: # otherwise, we are trying to autocomplete some options for the module, and should # defer to it offset = len(_line.group(0)) # we pass over the arguments for autocompletion, but strip off the command and module # name for simplicity return self.__module(_line.group(2)).complete( text, line[offset:], begidx - offset, endidx - offset) def do_shell(self, args): """ usage: `! [COMMAND]` or `shell [COMMAND]` Execute a Linux command in the context of Mercury. If a COMMAND is specified, this is shorthand for `run shell.exec COMMAND`. Otherwise, it will launch an interactive shell. Example: mercury> ! date Fri Dec 21 23:59:59 GMT 2012 mercury> ! cat /etc/hosts 127.0.0.1 localhost The working directory of your shell will be the Mercury Agent root folder. """ if len(args) > 0: return self.do_run(".shell.exec \"%s\"" % args) else: return self.do_run(".shell.start") def help_intents(self): """ An intent is an abstract description of an operation to be performed. It can be used with app.activity.start to launch an Activity, app.broadcast.send to send it to any interested BroadcastReceiver components, and app.service.start to communicate with a background Service. An Intent provides a facility for performing late runtime binding between the code in different applications. Its most significant use is in the launching of activities, where it can be thought of as the glue between activities. It is basically a passive data structure holding an abstract description of an action to be performed. Intent Structure ---------------- The primary pieces of information in an intent are: action: the general action to be performed data: the data to operate on In addition to these primary attributes, there are a number of secondary attributes that you can also include with an intent: category: gives additional information about the action to execute type: specifies an explicit MIME type (a MIME type) of the data component: specifies an explicit component class extras: a bundle of any additional information Put together, the set of actions, data types, categories, and extra data defines a language for the system allowing for the expression of phrases such as "call john smith's cell". As applications are added to the system, they can extend this language by adding new actions, types, and categories, or they can modify the behavior of existing phrases by supplying their own activities that handle them. Intent Formulation ------------------ In Mercury, intents are formulated using a set of command-line options. Some of these set a simple String in the Intent: --action ACTION --category CATEGORY --component PACKAGE COMPONENT --data-uri URI --flags FLAG [FLAG ...] --mimetype TYPE When specifying a component, the fully-qualified name of both the package and component must be used, for example to specify the BrowserActivity within the com.android.browser package: --component com.android.browser com.android.browser.BrowserActivity Intents can carry messages or commands inside of them in the form of extras. Applications may want to pass additional information inside of the intents they send to one another, possibly containing the data to perform a task on, or any other user-defined task to initiate from the received data. Passing the extras is a little more complex. You need to tell Mercury the data type, key and value: --extra TYPE KEY VALUE Mercury supports a few common types: boolean byte char double float integer short string """ self.stdout.write( wrap( textwrap.dedent(self.help_intents.__doc__).strip() + "\n\n", console.get_size()[0])) def sendAndReceive(self, message): """ Delivers a message to the Agent, and returns the response. If the send operation times out, or the response indicates a fatal error, an error message is displayed and the console terminates with a status code of -1. """ try: message = self.__server.sendAndReceive( message.setSessionId(self.__session_id)) except socket.timeout: self.stderr.write("lost session: %s\n" % self.__session_id) sys.exit(-1) if message and message.type == Message.REFLECTION_RESPONSE and message.reflection_response.status == Message.ReflectionResponse.FATAL: self.stderr.write("lost session: %s\n" % self.__session_id) sys.exit(-1) return message def __module(self, key): """ Gets a module instance, by identifier, and initialises it with the required session parameters. """ module = None try: module = Module.get(self.__module_name(key)) except KeyError: pass if module == None: try: module = Module.get(key) except KeyError: pass if module == None: raise KeyError(key) else: return module(self.__reflector, self.stdout, self.stderr) def __module_name(self, key): """ Decodes a full module identifier, given a user's input. This helps to find modules after the user has changed namespace. """ if key.startswith("."): return key[1:] elif self.__base == "": return key else: return self.__base + key def __modules(self): """ Gets a full list of all module identifiers. """ if self.__base == "": return Module.all() else: return filter(lambda m: m.startswith(self.__base), Module.all()) def __namespaces(self, global_scope=False): """ Gets a full list of all namespace identifiers, either globally or in the current namespace scope. """ if global_scope: return set( map(lambda m: self.__module("." + m).namespace(), Module.all())) else: return set( map(lambda m: self.__module("." + m).namespace(), self.__modules())) def __push_module_completer(self, completer, history_file=None): """ Delegate, passed to the module, so it can add a new readline completer to the stack. """ self.__module_pushed_completers += 1 self.push_completer(completer, history_file) def __pop_module_completer(self): """ Delegate, passed to the module, so it can add a remove a readline completer from the stack. """ self.__module_pushed_completers -= 1 self.pop_completer() def __setBase(self, base): """ Changes the user's namespace. Changing to: 'str' - selects the 'str' namespace, within the currently active namespace '.str' - selects the 'str' namespace, in the root namespace '..' - goes back on namespace '' - goes back to the root namespace """ if base == "": self.__base = base else: if base == "..": path = self.__base.split(".") try: path.pop(-2) except IndexError: pass target = ".".join(path) elif base.startswith("."): target = base[1:] + "." else: target = self.__base + base + "." if True in map(lambda m: m.startswith(target), Module.all()): self.__base = target else: self.stderr.write("no such namespace: %s\n" % base) if base == "": self.prompt = "mercury> " else: self.prompt = "mercury#{}> ".format( self.__base[0:len(self.__base) - 1]) return True