Beispiel #1
0
    def run(self):
        Log.info("Dumping classes with otool")
        try:
            class_dump = otool_class_dump_to_dict(otool_class_dump(
                self.binary))
        except Exception as e:
            Log.error("An error ocurred when trying to use otool")
            Log.debug(e)
            Log.info("Trying jtool")
            class_dump = jtool_class_dump_to_dict(jtool_class_dump(
                self.binary))

        dump_name = self.binary.rsplit("/", 1)[-1].replace(" ", ".")
        result = {
            "{}_class_dump".format(dump_name.replace(".", "_")): class_dump
        }

        if hasattr(self, "output") and self.output:
            Log.info("Saving classes to file")
            dump_path = "{}/{}.class.dump".format(self.output, dump_name)

            # create output folder
            execute("mkdir -p {}".format(dump_path))
            save_class_dump(class_dump, dump_path)

            result.update({
                "{}_dump_path".format(dump_name.replace(".", "_")): dump_path,
                "print": "Dump saved in {}.".format(dump_path)
            })

        return result
Beispiel #2
0
    def _start_connection(self):
        """
        Starts an SSH connection to the remote device
        """
        from scrounger.utils.ssh import SSHClient
        from scrounger.utils.general import process
        from scrounger.utils.config import SSH_COMMAND_TIMEOUT
        from scrounger.utils.config import SSH_SESSION_TIMEOUT

        # setup
        if not self._ssh_session:
            # this was breaking
            #self._iproxy_process = process(
            #    'iproxy 2222 22 {}'.format(self._device_id))
            self._iproxy_process = process("iproxy 2222 22")
            self._ssh_session = SSHClient("127.0.0.1", 2222, self._username,
                                          self._password, SSH_COMMAND_TIMEOUT)
            self._ssh_session.connect()

            # Log a new sessions
            _Log.debug("new ssh session started.")

        # start a tiemout for the connection
        from threading import Timer
        if self._timer:
            self._timer.cancel()  # cancel old timer and start new one

        self._timer = Timer(SSH_SESSION_TIMEOUT, self._stop_connection)
        self._timer.start()
Beispiel #3
0
    def _start_connection(self):
        """
        Starts an SSH connection to the remote device
        """
        from scrounger.utils.ssh import SSHClient
        from scrounger.utils.config import SSH_COMMAND_TIMEOUT
        from scrounger.utils.config import SSH_SESSION_TIMEOUT
        from scrounger.utils.config import _SCROUNGER_HOME
        from scrounger.lib.tcprelay import create_server

        # setup
        if not self._ssh_session:

            self._relay_process = create_server()
            self._ssh_session = SSHClient("127.0.0.1", 2222, self._username,
                                          self._password, SSH_COMMAND_TIMEOUT)
            self._ssh_session.connect()

            # Log a new sessions
            _Log.debug("new ssh session started.")

        # add scrounger's key
        key_path = "{}/bin/ios/scrounger.pub".format(_SCROUNGER_HOME)
        if not self._ssh_session.add_key(key_path):
            _Log.debug("Scrounger's ssh key not in authorized_keys")

        # start a tiemout for the connection
        from threading import Timer
        if self._timer:
            self._timer.cancel()  # cancel old timer and start new one

        self._timer = Timer(SSH_SESSION_TIMEOUT, self._stop_connection)
        self._timer.start()
Beispiel #4
0
    def execute(self, command):
        """
        Executes a command on the target device

        :param str command: the command to be executed
        :return: stdout and stderr of the executed command
        """

        # log command that is going to be run
        _Log.debug("Running: {}".format(command))

        return _adb_command("-s {} shell {}".format(self._device_id, command))
Beispiel #5
0
    def write(self, command):
        """
        Writes a command into the interactive process

        :param str command: the command to be sent to the interactive process
        :return: nothing
        """
        import os

        _Log.debug("Sending to process {}: {}".format(
            self._executable, command))

        # add a new line and send the command to the process stdin
        os.write(self._process.stdin.fileno(), "{}\n".format(command))
Beispiel #6
0
    def error(self):
        """
        Reads from the process stderr

        :return: a str with the stderr result or None
        """
        import os

        try:
            return self._process.stderr.read()
        except:
            _Log.debug("Nothing to read stderr {}".format(self._executable))
            # there is nothing to read, return None
            return None
Beispiel #7
0
def execute(command):
    """
    Executes a command on the local host.

    :param str command: the command to be executed
    :return: returns the output of the STDOUT or STDERR
    """
    from subprocess import check_output, STDOUT
    command = "{}; exit 0".format(command)

    # log command that is going to be run
    _Log.debug("Shell Command: {}".format(command))

    return check_output(command, stderr=STDOUT, shell=True)
Beispiel #8
0
    def run(self):
        result = {
            "title": "Application Does Not Encrypt Shared Preferences",
            "details": "",
            "severity": "Medium",
            "report": False
        }

        if not self.device.installed(self.identifier):
            return {"print": "Application not installed"}

        Log.info("Starting the application")
        self.device.start(self.identifier)
        sleep(5)

        Log.info("Finding files in application's data")
        target_paths = [
            "{}/shared_prefs".format(file_path)
            for file_path in self.device.data_paths(self.identifier)
        ]

        listed_files = []
        report_files = []
        for data_path in target_paths:
            listed_files += self.device.find_files(data_path)

        Log.info("Analysing application's data")

        for filename in listed_files:
            if filename:
                file_content = self.device.file_content(filename)

                lang = detect_langs(file_content)[0]
                Log.debug("{} language {}: {}".format(filename, lang.lang,
                                                      lang.prob))

                if lang.prob > float("0.{}".format(self.min_percentage)):
                    report_files += [filename]

        if report_files:
            result.update({
                "report":
                True,
                "details":
                "* Unencrypted Files:\n * {}".format(
                    "\n * ".join(report_files))
            })

        return {"{}_result".format(self.name()): result}
Beispiel #9
0
        def __decrypt_app_helper(app_id, decrypt_type):
            from socket import timeout
            scrounger_clutch_log_file = "/tmp/scrounger-clutch.log"

            try:
                output = self.execute("clutch -n {} {} &> {}".format(
                    decrypt_type, app_id, scrounger_clutch_log_file))[0]
            except timeout:
                _Log.debug("ssh command timedout.")

            output = self._cat_file(scrounger_clutch_log_file)

            # cleanup log file
            self._rm_file(scrounger_clutch_log_file)

            return output
Beispiel #10
0
    def run(self):
        results = []
        exceptions = []

        # run all modules
        Log.info("Running all Android analysis modules")
        for module in self._analysis_modules:

            instance = module.Module()
            for option in self.options:
                if hasattr(self, option["name"]):
                    setattr(instance, option["name"],
                            getattr(self, option["name"]))

            try:
                Log.debug("Validating and Running: {}".format(instance.name()))

                instance.validate_options()
                run_result = instance.run()

                for key in run_result:
                    if key.endswith("_result") and validate_analysis_result(
                            run_result[key]) and run_result[key]["report"]:
                        results += [run_result[key]]
            except Exception as e:
                exceptions += [{"module": instance.name(), "exception": e}]

        # setup output folders
        Log.info("Creating output folders")
        output_directory = "{}{}".format(self.output, self._output_directory)
        execute("mkdir -p {}".format(output_directory))
        output_file = "{}/results.json".format(output_directory)

        # write results to json file
        Log.info("Writing results to file")
        with open(output_file, "w") as fp:
            fp.write(dumps(results))

        return {
            "android_analysis":
            results,
            "exceptions":
            exceptions,
            "print":
            "The following issues were found:\n* {}".format("\n* ".join(
                [result["title"] for result in results]))
        }
Beispiel #11
0
    def put(self, local_file_path, remote_file_path):
        """
        Copies a file to the remote device.

        :param str file_path: the local file path
        :param str remote_file_path: the remote path where to copy the file to
        (it needs to contain the file name too)
        :return: returns nothing
        """
        # start a connection if there is none
        self._start_connection()

        # logging files
        _Log.debug("copying {} to {}.".format(local_file_path,
                                              remote_file_path))

        # put file
        self._ssh_session.put_file(local_file_path, remote_file_path)
Beispiel #12
0
    def _stop_connection(self):
        """
        Stops the SSH connection with the remote device
        """
        from scrounger.utils.general import execute

        # cleanup
        if self._timer:
            self._timer.cancel()
            self._timer = None

        if self._ssh_session:
            self._ssh_session.disconnect()
            self._relay_process.stop()
            self._relay_process = self._ssh_session = None

            # Log session stop
            _Log.debug("ssh session killed.")
Beispiel #13
0
    def pid(self, package):
        """
        Returns the PID of a running application

        :param str package: the identifier of the app to get the PID from
        :return int: a PID if the app with package is running or None if not
        """
        apps = self.packages()
        if package not in apps:
            _Log.debug("App {} is not installed on the device".format(package))
            return None

        processes = self.processes()
        for process in processes:
            if package.lower() in process["name"].lower():
                return int(process["pid"])

        return None
Beispiel #14
0
    def __init__(self, device):
        """
        Creates a new GDB object representiong a GDB instance on the remote
        device

        :param IOSDevice device: A device representing the remote device
        """
        from scrounger.lib.tcprelay import create_server

        self._device = device
        host, port = device._ssh_session._ip, device._ssh_session._port
        key = "{}/bin/ios/scrounger.key".format(_SCROUNGER_HOME)

        _log.debug("Starting a new SSH connection to the remote device")
        self._process = InteractiveProcess("ssh -i {} -p {} root@{}".format(
            key, port, host))

        _log.debug("Starting GDB on the remote device")
        self._running = self._start_gdb()
Beispiel #15
0
    def _stop_connection(self):
        """
        Stops the SSH connection with the remote device
        """
        from scrounger.utils.general import execute

        # cleanup
        if self._timer:
            self._timer.cancel()
            self._timer = None

        if self._ssh_session:
            self._ssh_session.disconnect()
            #self._iproxy_process.kill()
            self._iproxy_process = self._ssh_session = None
            execute('killall iproxy')  # make sure iproxy is killed

            # Log session stop
            _Log.debug("ssh session killed.")
Beispiel #16
0
    def pid(self, app_id):
        """
        Returns the PID of a running application

        :param str app_id: the identifier of the app to get the PID from
        :return int: a PID if the app with app_id is running or None if not
        """
        apps = self.apps()
        if app_id not in apps:
            _Log.debug("App {} is not installed on the device".format(app_id))
            return None

        install_path = apps[app_id]["application"]
        processes = self.processes()
        for process in processes:
            if install_path.rsplit("/", 1)[-1].lower() in \
            process["name"].lower():
                return int(process["pid"])

        return None
Beispiel #17
0
    def execute(self, command):
        """
        Executes a command on the device and returns STDOUT and STDERR

        :param str command: the command to be executed
        :return: returns the STDOUT and STDERR of the executed command
        """

        # start a connection if there is none
        self._start_connection()

        # log command that is going to be run
        _Log.debug("Running: {}".format(command))

        # execute
        stdout, stderr = self._ssh_session.execute(command)

        # log result
        #_Log.debug("stdout: {}".format(stdout))
        #_Log.debug("stderr: {}".format(stderr))

        return stdout, stderr
Beispiel #18
0
    def get(self, remote_file_path, local_file_path):
        """
        Retrieves a file from the remote device

        :param str remote_file_path: the path on the remote device
        :param str local_file_path: the path on the local host to copy the file
        to (it needs to contain the file name too)
        :return: returns nothing
        """
        from scrounger.utils.general import execute

        # start a connection if there is none
        self._start_connection()

        # logging files
        _Log.debug("copying {} to {}.".format(remote_file_path,
                                              local_file_path))

        # create local file path if not exists
        execute("mkdir -p {}".format(local_file_path.split("/", 1)[0]))

        # get file
        self._ssh_session.get_file(remote_file_path, local_file_path)
Beispiel #19
0
    def __init__(self, host, port):
        """
        Creates a new JDB object representiong a JDB instance on the remote
        device

        :param str host: the host to connect JDB to
        :param int port: the port where JDB app is listening
        """
        from time import sleep

        self._host, self._port = host, port

        _log.debug("Starting a new jdb process")
        self._process = InteractiveProcess("jdb -attach {}:{}".format(
            host, port))

        sleep(5)  # wait for it to start

        self._last_read = self._process.read()
        self._last_error = self._process.error()

        self._running = not self._last_error or (self._last_error and \
            "unable to attach" not in self._last_error.lower())
Beispiel #20
0
    def __init__(self, command):
        """
        Creates an interactive process to interact with out of a command

        :param str command: the command to be executed
        """

        from fcntl import fcntl, F_GETFL, F_SETFL
        from subprocess import Popen, PIPE
        import os

        self._command = command
        self._executable = command.split(" ", 1)[0]

        _Log.debug("Starting the interactive process: {}".format(command))

        self._process = Popen(command, shell=True, stdout=PIPE, stdin=PIPE,
            stderr=PIPE)
        fcntl(self._process.stdin, F_SETFL,
            fcntl(self._process.stdin, F_GETFL) | os.O_NONBLOCK)
        fcntl(self._process.stdout, F_SETFL,
            fcntl(self._process.stdout, F_GETFL) | os.O_NONBLOCK)
        fcntl(self._process.stderr, F_SETFL,
            fcntl(self._process.stderr, F_GETFL) | os.O_NONBLOCK)
Beispiel #21
0
    def run(self):
        result = {
            "title": "Application Does Not Use Obfuscation",
            "details": "",
            "severity": "Medium",
            "report": False
        }

        exceptions = []

        # get class dump
        class_dump_module = ClDumpModule()
        class_dump_module.binary = self.binary
        class_dump_module.output = None
        class_dump_result, classes_dumped = class_dump_module.run(), None
        for key in class_dump_result:
            if key.endswith("_class_dump"):
                classes_dumped = class_dump_result[key]

            if key.endswith("exceptions"):
                exceptions += class_dump_result[key]

        Log.info("Analysing class dump")

        class_strings = []
        for class_dumped in classes_dumped:

            # add to class name list
            class_strings += [class_dumped["name"]]

            # enumerate property names
            if "base_properties" in class_dumped:
                for property_dumped in class_dumped["base_properties"]:
                    class_strings += [property_dumped.rsplit(" ", 1)[-1][:-1]]
            if "instance_property" in class_dumped:
                for property_dumped in class_dumped["instance_property"]:
                    class_strings += [property_dumped.rsplit(" ", 1)[-1][:-1]]

            # enumerate method names
            if "base_methods" in class_dumped:
                for method_dumped in class_dumped["base_methods"]:
                    class_strings += [
                        part.split(":", 1)[0].split(")",
                                                    1)[-1].replace(";", "")
                        for part in method_dumped.split(" ") if ":" in part
                    ]
            if "instance_methods" in class_dumped:
                for method_dumped in class_dumped["instance_methods"]:
                    class_strings += [
                        part.split(":", 1)[0].split(")",
                                                    1)[-1].replace(";", "")
                        for part in method_dumped.split(" ") if ":" in part
                    ]

        # put them all together and get an analysis
        class_detect_lang = detect_langs(" ".join(class_strings))[0]
        class_small_strings = [
            string for string in class_strings if len(string) < 4
        ]

        # check if lang != expected or probability lower than required for str
        if class_detect_lang.lang != self.language or \
        class_detect_lang.prob*100 < self.min_percentage:
            result.update({
                "title":
                "Application shows evidence of obfuscation",
                "details":
                "Detected language {} with probability {}%.".format(
                    class_detect_lang.lang, class_detect_lang.prob * 100),
                "report":
                True
            })

        # check small_strings/total_strings >= min_percentage_small_strings
        sclass_percent = len(class_small_strings) * 1.0 / len(
            class_strings) * 100
        if sclass_percent >= self.min_percentage_small_names:
            result.update({
                "title":
                "Application shows evidence of obfuscation",
                "details":
                "{}\n\nDetected small strings: {}%".format(
                    result["details"], sclass_percent),
                "report":
                True
            })

        Log.debug("Strings detected {} with probability of {}%".format(
            class_detect_lang.lang, class_detect_lang.prob * 100))
        Log.debug("Small len strings {}/{} = {}%".format(
            len(class_small_strings), len(class_strings), sclass_percent))
        Log.debug("Unique small len classes {}/{} = {}%".format(
            len(set(class_small_strings)), len(set(class_strings)),
            len(set(class_small_strings)) * 1.0 / len(set(class_strings)) *
            100))

        if not result["report"]:
            result.update({
                "details": "No evidence of obfuscation found.",
                "report": True
            })

        return {
            "{}_result".format(self.name()): result,
            "exceptions": exceptions
        }
Beispiel #22
0
    def _uncrypt_app_helper(self, app_id, decrypt_type):
        """
        Decrypts an app using uncrypt11 and returns the result output

        :param str app_id: the id of the app to be decrypted
        :param str decrypt_type: the type of decryption to be done - either
        binary only (-b) or packed into ipa (-d)
        :return: returns the output of the decryption
        """
        from time import sleep

        uncrypt_path = "/Library/MobileSubstrate/DynamicLibraries/\
uncrypt11.dylib"

        if not self.file_exists(uncrypt_path):
            _Log.debug("Uncrypt11 not found")
            return "FAIL: Uncrypt11 not installed."

        self.start(app_id)  # start app - needs to be running
        sleep(5)  # wait to start
        pid = self.pid(app_id)  # get pid

        if not pid:
            _Log.debug("PID not found")
            return "FAIL: Could not get PID of {}".format(app_id)

        result = self.execute("/electra/inject_criticald {} {}".format(
            pid, uncrypt_path))

        if "No error occured!" not in result[0] and \
        "No error occured!" not in result[1]:
            _Log.debug("Not decrypted:\n{}\n{}".format(result[0], results[1]))
            return "FAIL: An error occured trying to decrypt {}".format(app_id)

        list_apps = self.apps()
        app_info = list_apps[app_id]
        decrypted_binary = "{}/Documents/{}\ decrypted".format(
            app_info["data"], app_info["binary_name"])

        if not self.file_exists(decrypted_binary):
            _Log.debug("File {} does not exist".format(decrypted_binary))
            return "FAIL: Could not decrypt {}".format(app_id)

        # move binary to tmp
        end_path = "/tmp/{}.decrypted".format(app_id)
        self.execute("mv {} {}".format(decrypted_binary, end_path))

        if decrypt_type == "-b":
            _Log.debug("Dumpped binary")
            return "Finished dumping {} to {}\n".format(app_id, end_path)

        _Log.debug("Creating IPA")

        # create IPA scructure
        self.execute("rm -rf /tmp/scrounger-tmp/Payload")
        self.execute("mkdir -p /tmp/scrounger-tmp/Payload")

        # copy App to /tmp
        self.execute("cp -r {} /tmp/scrounger-tmp/Payload".format(
            app_info["application"]))

        # move decrypted binary to the Payload
        app_name = app_info["application"].rsplit("/", 1)[-1]
        self.execute("mv {} /tmp/scrounger-tmp/Payload/{}/{}".format(
            end_path, app_name, app_info["binary_name"]))

        # zip everything
        self.execute(
            "cd /tmp/scrounger-tmp; zip -r ../{}.ipa Payload/".format(app_id))

        # cleanup
        self.execute("rm -rf /tmp/scrounger-tmp")

        # Success: DONE: /path/to/ipa\n
        # Success: Finished dumping app_id to /path/to/dump/binary\n
        return "DONE: /tmp/{}.ipa\n".format(app_id)
Beispiel #23
0
    def run(self):
        result = {
            "title": "Application Does Not Use Obfuscation",
            "details": "",
            "severity": "Medium",
            "report": False
        }

        exceptions = []

        # preparing variable to run
        ignore = [filepath.strip() for filepath in self.ignore.split(";")]

        # get identifier
        Log.info("Checking identifier package only")
        if self.check_package_only:
            manifest_module = ManifestModule()
            manifest_module.decompiled_apk = self.decompiled_apk
            self.manifest = manifest_module.run()
            if "print" not in self.manifest:
                identifier = self.manifest.popitem()[1].package()
            else:
                identifier = None
                exceptions += [Exception(self.manifest["print"])]

        Log.info("Identifying class names")
        class_names_list = class_names(self.decompiled_apk, ignore, identifier)

        Log.info("Identifying method names")
        method_names_list = method_names(self.decompiled_apk, ignore,
                                         identifier)

        Log.info("Identifying strings")
        strings_list = app_strings(self.decompiled_apk, ignore, identifier)

        Log.info("Identifying resources")
        resources_list = app_used_resources(self.decompiled_apk, ignore,
                                            identifier)

        Log.info("Analysing identified strings")

        # start by analysing class names
        class_detect_lang = detect_langs(" ".join(class_names_list))[0]
        class_small_names = [
            class_name for class_name in class_names_list
            if len(class_name) < 4
        ]

        # check if lang != expected or probability lower than required for class
        # names
        if class_detect_lang.lang != self.language or \
        class_detect_lang.prob*100 < self.min_percentage:
            result.update({
                "title":
                "Application shows evidence of obfuscation",
                "details":
                "Detected language {} with probability {}% on \
class names.".format(class_detect_lang.lang, class_detect_lang.prob * 100),
                "report":
                True
            })

        # check small_classes/total_classes >= min_percent_class_names
        sclass_percent = len(class_small_names) * 1.0 / len(
            class_names_list) * 100
        if sclass_percent >= self.min_percentage_small_names:
            result.update({
                "title":
                "Application shows evidence of obfuscation",
                "details":
                "{}\n\nDetected small class names: {}%".format(
                    result["details"], sclass_percent),
                "report":
                True
            })

        Log.debug("Classes detected {} with probability of {}%".format(
            class_detect_lang.lang, class_detect_lang.prob * 100))
        Log.debug("Small len classes {}/{} = {}%".format(
            len(class_small_names), len(class_names_list), sclass_percent))
        Log.debug("Unique small len classes {}/{} = {}%".format(
            len(set(class_small_names)), len(set(class_names_list)),
            len(set(class_small_names)) * 1.0 / len(set(class_names_list)) *
            100))

        # analyse method names
        method_detect_lang = detect_langs(" ".join(method_names_list))[0]
        method_small_names = [
            method_name for method_name in method_names_list
            if len(method_name) < 4
        ]

        # check if lang != expected or probability lower than required for
        # method names
        if method_detect_lang.lang != self.language or \
        method_detect_lang.prob*100 < self.min_percentage:
            result.update({
                "title":
                "Application shows evidence of obfuscation",
                "details":
                "{}\n\nDetected language {} with probability {}% on \
method names.".format(result["details"], method_detect_lang.lang,
                      method_detect_lang.prob * 100),
                "report":
                True
            })

        # check small_methods/total_methods >= min_percent_mathod_names
        smthod_percent = len(method_small_names) * 1.0 / len(
            method_names_list) * 100
        if smthod_percent >= self.min_percentage_small_names:
            result.update({
                "title":
                "Application shows evidence of obfuscation",
                "details":
                "{}\n\nDetected small method names: {}%".format(
                    result["details"], smthod_percent),
                "report":
                True
            })

        Log.debug("Methods detected {} with probability of {}%".format(
            method_detect_lang.lang, method_detect_lang.prob * 100))
        Log.debug("Small len methods {}/{} = {}%".format(
            len(method_small_names), len(method_names_list), smthod_percent))
        Log.debug("Unique small len classes {}/{} = {}%".format(
            len(set(method_small_names)), len(set(method_names_list)),
            len(set(method_small_names)) * 1.0 / len(set(method_names_list)) *
            100))

        # analyse strings and resources
        strings_detect_lang = detect_langs(" ".join(strings_list +
                                                    resources_list))[0]

        # check if lang != expected or probability lower than required for
        # strings and resources
        if strings_detect_lang.lang != self.language or \
        strings_detect_lang.prob*100 < self.min_percentage:
            result.update({
                "title":
                "Application shows evidence of obfuscation",
                "details":
                "{}\n\nDetected language {} with probability {}% on \
strings and resources.".format(result["details"], strings_detect_lang.lang,
                               strings_detect_lang.prob * 100),
                "report":
                True
            })

        Log.debug("Strings detected {} with probability of {}%".format(
            strings_detect_lang.lang, strings_detect_lang.prob * 100))

        if not result["report"]:
            result.update({
                "details": "No evidence of obfuscation found.",
                "report": True
            })

        return {
            "{}_result".format(self.name()): result,
            "exceptions": exceptions
        }