Пример #1
0
 def __init__(self):
     # Init related objects
     self.app = App(self)
     self.installer = Installer(self)
     self.local_op = LocalOperations()
     self.remote_op = RemoteOperations(self)
     self.printer = Printer()
     self.connect()
     self.setup()
Пример #2
0
 def __init__(self, ip, port, username, password, pub_key_auth, tools):
     # Setup params
     self._ip = ip
     self._port = port
     self._username = username
     self._password = password
     self._pub_key_auth = bool(pub_key_auth)
     self._tools_local = tools
     # Init related objects
     self.app = App(self)
     self.installer = Installer(self)
     self.local_op = LocalOperations()
     self.remote_op = RemoteOperations(self)
     self.printer = Printer()
Пример #3
0
 def __init__(self, ip, port, agent_port, username, password, pub_key_auth, tools):
     # Setup params
     self._ip = ip
     self._port = port
     self._agent_port = agent_port
     self._username = username
     self._password = password
     self._pub_key_auth = bool(pub_key_auth)
     self._tools_local = tools
     # Init related objects
     self.app = App(self)
     self.local_op = LocalOperations()
     self.remote_op = RemoteOperations(self)
     self.printer = Printer()
     self.agent = NeedleAgent(self)
Пример #4
0
class Device(object):
    # ==================================================================================================================
    # FRAMEWORK ATTRIBUTES
    # ==================================================================================================================
    # Connection Parameters
    _ip = ''
    _port = ''
    _username = ''
    _password = ''
    _tools_local = None
    _portforward = None
    _frida_server = None
    _debug_server = None
    # App specific
    _is_iOS8 = False
    _is_iOS9 = False
    _is_iOS10 = False
    _is_iOS7_or_less = False
    _applist = None
    _device_ready = False
    # On-Device Paths
    TEMP_FOLDER = Constants.DEVICE_PATH_TEMP_FOLDER
    DEVICE_TOOLS = Constants.DEVICE_TOOLS
    # On-Server Paths
    APP_DB_PATH = "/tmp/applicationState.db"  #for iOS 10
    # Reference to External Objects
    conn = None
    app = None
    installer = None
    local_op = None
    remote_op = None
    printer = None

    # ==================================================================================================================
    # INIT
    # ==================================================================================================================
    def __init__(self, ip, port, username, password, pub_key_auth, tools):
        # Setup params
        self._ip = ip
        self._port = port
        self._username = username
        self._password = password
        self._pub_key_auth = bool(pub_key_auth)
        self._tools_local = tools
        # Init related objects
        self.app = App(self)
        self.installer = Installer(self)
        self.local_op = LocalOperations()
        self.remote_op = RemoteOperations(self)
        self.printer = Printer()

    # ==================================================================================================================
    # UTILS - USB
    # ==================================================================================================================
    def _portforward_usb_start(self):
        """Setup USB port forwarding with TCPRelay."""
        # Check if the user chose a valid port
        if str(self._port) == '22':
            raise Exception(
                'Chosen port must be different from 22 in order to use USB over SSH'
            )
        # Setup the forwarding
        self.printer.verbose('Setting up USB port forwarding on port %s' %
                             self._port)
        cmd = '{app} -t 22:{port}'.format(app=self._tools_local['TCPRELAY'],
                                          port=self._port)
        self._portforward = self.local_op.command_subproc_start(cmd)

    def _portforward_usb_stop(self):
        """Stop USB port forwarding."""
        self.printer.verbose('Stopping USB port forwarding')
        self.local_op.command_subproc_stop(self._portforward)

    # ==================================================================================================================
    # UTILS - SSH
    # ==================================================================================================================
    def _connect_ssh(self):
        """Open a new connection using Paramiko."""
        try:
            self.printer.verbose('Setting up SSH connection...')
            self.conn = paramiko.SSHClient()
            self.conn.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            self.conn.connect(self._ip,
                              port=self._port,
                              username=self._username,
                              password=self._password,
                              allow_agent=self._pub_key_auth,
                              look_for_keys=self._pub_key_auth)

        except paramiko.AuthenticationException as e:
            raise Exception(
                'Authentication failed when connecting to %s. %s: %s' %
                (self._ip, type(e).__name__, e.message))
        except paramiko.SSHException as e:
            raise Exception(
                'Connection dropped. Please check your connection with the device, '
                'and reload the module. %s: %s' %
                (type(e).__name__, e.message))
        except Exception as e:
            raise Exception('Could not open a connection to %s. %s - %s' %
                            (self._ip, type(e).__name__, e.message))

    def _disconnect_ssh(self):
        """Close the connection, if available."""
        if self.conn:
            self.conn.close()

    def _exec_command_ssh(self, cmd, internal):
        """Execute a shell command on the device, then parse/print output."""
        def hotfix_67():
            # TODO: replace with a more long-term fix
            import time
            timeout = 30
            endtime = time.time() + timeout
            while not stdout.channel.eof_received:
                time.sleep(1)
                if time.time() > endtime:
                    stdout.channel.close()
                    break

        # Paramiko Exec Command
        stdin, stdout, stderr = self.conn.exec_command(cmd)
        hotfix_67()

        # Parse STDOUT/ERR
        out = stdout.readlines()
        err = stderr.readlines()
        if internal:
            # For processing, don't display output
            if err:
                # Show error and abort run
                err_str = ''.join(err)
                raise Exception(err_str)
        else:
            # Display output
            if out: map(lambda x: print('\t%s' % x, end=''), out)
            if err:
                map(
                    lambda x: print('\t%s%s%s' % (Colors.R, x, Colors.N),
                                    end=''), err)
        return out, err

    # ==================================================================================================================
    # FRIDA PORT FORWARDING
    # ==================================================================================================================
    def _portforward_frida_start(self):
        """Setup local port forward to enable communication with the Frida server running on the device"""
        localhost = '127.0.0.1'
        self._frida_server = SSHTunnelForwarder(
            (self._ip, int(self._port)),
            ssh_username=self._username,
            ssh_password=self._password,
            local_bind_address=(localhost, Constants.FRIDA_PORT),
            remote_bind_address=(localhost, Constants.FRIDA_PORT),
        )
        self._frida_server.start()

    def _portforward_frida_stop(self):
        """Stop local port forwarding"""
        if self._frida_server:
            self._frida_server.stop()

    # ==================================================================================================================
    # LLDB PORT FORWARDING
    # ==================================================================================================================
    def _portforward_debug_start(self):
        """Setup local port forward to enable communication with the debug server running on the device"""
        localhost = '127.0.0.1'
        self._debug_server = SSHTunnelForwarder(
            (self._ip, int(self._port)),
            ssh_username=self._username,
            ssh_password=self._password,
            local_bind_address=(localhost, Constants.DEBUG_PORT),
            remote_bind_address=(localhost, Constants.DEBUG_PORT),
        )
        self._debug_server.start()

    def _portforward_debug_stop(self):
        """Stop local port forwarding"""
        if self._debug_server:
            self._debug_server.stop()

    # ==================================================================================================================
    # UTILS - OS
    # ==================================================================================================================
    def _detect_ios_version(self):
        """Detect the iOS version running on the device."""
        if self.remote_op.file_exist(Constants.DEVICE_PATH_APPLIST_iOS8):
            self._is_iOS8 = True
        elif self.remote_op.file_exist(Constants.DEVICE_PATH_APPLIST_iOS9):
            self._is_iOS9 = True
        elif self.remote_op.file_exist(Constants.DEVICE_PATH_DB_iOS10):
            self._is_iOS10 = True
        else:
            self._is_iOS7_or_less = True

    def _list_apps(self):
        """List all the 3rd party apps installed on the device."""
        def list_iOS_7():
            raise Exception('Support for iOS < 8 not yet implemented')

        def list_iOS_8(applist):
            # Refresh UICache in case an app was installed after the last reboot
            self.printer.verbose("Refreshing list of installed apps...")
            self.remote_op.command_blocking(
                '/bin/su mobile -c /usr/bin/uicache', internal=True)
            # Parse plist file
            pl = self.remote_op.parse_plist(applist)
            self._applist = pl["User"]

        def list_iOS_10(applist):
            self.printer.verbose(
                "Respring if an application is not listed to rebuild the application db"
            )
            if self._applist == None:
                self._applist = dict()
            self.printer.verbose("fetching application state db to %s" %
                                 (self.APP_DB_PATH))
            #fetch the current database to parse
            self.pull(applist, self.APP_DB_PATH)
            #open the loaded db
            conn = sqlite3.connect(self.APP_DB_PATH)
            c = conn.cursor()
            c.execute(
                "SELECT id,application_identifier FROM application_identifier_tab"
            )
            for row in c:
                self._applist[row[1]] = row[0]  #key = application name
            c.close()

        # Dispatch
        self._detect_ios_version()
        if self._is_iOS8: list_iOS_8(Constants.DEVICE_PATH_APPLIST_iOS8)
        elif self._is_iOS9: list_iOS_8(Constants.DEVICE_PATH_APPLIST_iOS9)
        elif self._is_iOS10: list_iOS_10(Constants.DEVICE_PATH_DB_iOS10)
        else: list_iOS_7()

    def select_target_app(self):
        """List all 3rd party apps installed and let the user choose which one to target"""
        self._list_apps()
        self.printer.notify('Apps found:')
        app_name = choose_from_list(self._applist.keys())
        return app_name

    # ==================================================================================================================
    # EXPOSED COMMANDS
    # ==================================================================================================================
    def is_usb(self):
        """Returns true if using SSH over USB."""
        return self._ip == '127.0.0.1' or self._ip == 'localhost'

    def connect(self):
        """Connect to the device."""
        if self.is_usb():
            # Using SSH over USB, setup port forwarding first
            self._portforward_usb_start()
        # Connect
        self._connect_ssh()

    def disconnect(self):
        """Disconnect from the device."""
        if self._portforward:
            # Using SSH over USB, stop port forwarding
            self._portforward_usb_stop()
        self._disconnect_ssh()

    def setup(self, install_tools=True):
        """Create temp folder, and check if all tools are available"""
        # Setup temp folder
        self.printer.verbose("Creating temp folder: %s" % self.TEMP_FOLDER)
        self.remote_op.dir_create(self.TEMP_FOLDER)
        # Install tools
        if install_tools:
            if not self._device_ready:
                self.printer.info("Configuring device...")
                self._device_ready = self.installer.configure()
        else:
            self._device_ready = True

    def cleanup(self):
        """Remove temp folder from device."""
        self.printer.verbose("Cleaning up remote temp folder: %s" %
                             self.TEMP_FOLDER)
        self.remote_op.dir_delete(self.TEMP_FOLDER)

    def shell(self):
        """Spawn a system shell on the device."""
        cmd = 'sshpass -p "{password}" ssh {hostverification} -p {port} {username}@{ip}'.format(
            password=self._password,
            hostverification=Constants.DISABLE_HOST_VERIFICATION,
            port=self._port,
            username=self._username,
            ip=self._ip)
        self.local_op.command_interactive(cmd)

    def pull(self, src, dst):
        """Pull a file from the device."""
        self.printer.info("Pulling: %s -> %s" % (src, dst))
        self.remote_op.download(src, dst)

    def push(self, src, dst):
        """Push a file on the device."""
        self.printer.info("Pushing: %s -> %s" % (src, dst))
        self.remote_op.upload(src, dst)
Пример #5
0
class Device(object):
    # ==================================================================================================================
    # FRAMEWORK ATTRIBUTES
    # ==================================================================================================================
    # Connection Parameters
    _ip, _port, _agent_port, _username, _password = '', '', '', '', ''
    _tools_local = None
    # Port Forwarding
    _frida_server = None
    _port_forward_ssh, _port_forward_agent = None, None
    # App specific
    _applist, _device_ready, _ios_version = None, None, None
    # Reference to External Objects
    ssh, agent = None, None
    app, installer = None, None
    local_op, remote_op = None, None
    printer = None
    # On-Device Paths
    TEMP_FOLDER = Constants.DEVICE_PATH_TEMP_FOLDER
    DEVICE_TOOLS = Constants.DEVICE_TOOLS

    # ==================================================================================================================
    # INIT
    # ==================================================================================================================
    def __init__(self, ip, port, agent_port, username, password, pub_key_auth,
                 tools):
        # Setup params
        self._ip = ip
        self._port = port
        self._agent_port = agent_port
        self._username = username
        self._password = password
        self._pub_key_auth = bool(pub_key_auth)
        self._tools_local = tools
        # Init related objects
        self.app = App(self)
        self.installer = Installer(self)
        self.local_op = LocalOperations()
        self.remote_op = RemoteOperations(self)
        self.printer = Printer()
        self.agent = NeedleAgent(self)

    # ==================================================================================================================
    # UTILS - USB
    # ==================================================================================================================
    def _portforward_usb_start(self):
        """Setup USB port forwarding with TCPRelay."""
        # Check if the user chose a valid port
        if str(self._port) == '22':
            raise Exception(
                'Chosen port must be different from 22 in order to use USB over SSH'
            )
        # Setup the forwarding
        self.printer.debug('Setting up USB port forwarding on port %s' %
                           self._port)
        cmd = '{app} -t 22:{port}'.format(app=self._tools_local['TCPRELAY'],
                                          port=self._port)
        self._port_forward_ssh = self.local_op.command_subproc_start(cmd)

    def _portforward_usb_stop(self):
        """Stop USB port forwarding."""
        self.printer.debug('Stopping USB port forwarding')
        self.local_op.command_subproc_stop(self._port_forward_ssh)

    # ==================================================================================================================
    # UTILS - SSH
    # ==================================================================================================================
    def _connect_ssh(self):
        """Open a new SSH connection using Paramiko."""
        try:
            self.printer.verbose("[SSH] Connecting ({}:{})...".format(
                self._ip, self._port))
            self.ssh = paramiko.SSHClient()
            self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            self.ssh.connect(self._ip,
                             port=self._port,
                             username=self._username,
                             password=self._password,
                             allow_agent=self._pub_key_auth,
                             look_for_keys=self._pub_key_auth)
            self.printer.notify("[SSH] Connected ({}:{})".format(
                self._ip, self._port))
        except paramiko.AuthenticationException as e:
            raise Exception(
                'Authentication failed when connecting to %s. %s: %s' %
                (self._ip, type(e).__name__, e.message))
        except paramiko.SSHException as e:
            raise Exception(
                'Connection dropped. Please check your connection with the device, '
                'and reload the module. %s: %s' %
                (type(e).__name__, e.message))
        except Exception as e:
            raise Exception('Could not open a connection to %s. %s - %s' %
                            (self._ip, type(e).__name__, e.message))

    def _disconnect_ssh(self):
        """Close the SSH connection, if available."""
        self.printer.verbose("[SSH] Disconnecting...")
        if self.ssh:
            self.ssh.close()

    def _exec_command_ssh(self, cmd, internal):
        """Execute a shell command on the device, then parse/print output."""
        def hotfix_67():
            # TODO: replace with a more long-term fix
            import time
            timeout = 30
            endtime = time.time() + timeout
            while not stdout.channel.eof_received:
                time.sleep(1)
                if time.time() > endtime:
                    stdout.channel.close()
                    break

        # Paramiko Exec Command
        stdin, stdout, stderr = self.ssh.exec_command(cmd)
        hotfix_67()
        # Parse STDOUT/ERR
        out = stdout.readlines()
        err = stderr.readlines()
        if internal:
            # For processing, don't display output
            if err:
                # Show error and abort run
                err_str = ''.join(err)
                raise Exception(err_str)
        else:
            # Display output
            if out: map(lambda x: print('\t%s' % x, end=''), out)
            if err:
                map(
                    lambda x: print('\t%s%s%s' % (Colors.R, x, Colors.N),
                                    end=''), err)
        return out, err

    # ==================================================================================================================
    # UTILS - AGENT
    # ==================================================================================================================
    def _portforward_agent_start(self):
        """Setup local port forward to enable communication with the Needle server running on the device."""
        self.printer.debug('{} Setting up port forwarding on port {}'.format(
            Constants.AGENT_TAG, self._agent_port))
        localhost = '127.0.0.1'
        self._port_forward_agent = SSHTunnelForwarder(
            (self._ip, int(self._port)),
            ssh_username=self._username,
            ssh_password=self._password,
            local_bind_address=(localhost, self._agent_port),
            remote_bind_address=(localhost, self._agent_port),
        )
        self._port_forward_agent.start()

    def _portforward_agent_stop(self):
        """Stop local port forwarding for Needle server."""
        self.printer.debug('{} Stopping port forwarding'.format(
            Constants.AGENT_TAG))
        if self._port_forward_agent:
            self._port_forward_agent.stop()

    def _connect_agent(self):
        self.agent.connect()

    def _disconnect_agent(self):
        self.agent.disconnect()

    # ==================================================================================================================
    # FRIDA PORT FORWARDING
    # ==================================================================================================================
    def _portforward_frida_start(self):
        """Setup local port forward to enable communication with the Frida server running on the device."""
        self.printer.debug('{} Setting up port forwarding on port {}'.format(
            "[FRIDA]", Constants.FRIDA_PORT))
        localhost = '127.0.0.1'
        self._frida_server = SSHTunnelForwarder(
            (self._ip, int(self._port)),
            ssh_username=self._username,
            ssh_password=self._password,
            local_bind_address=(localhost, Constants.FRIDA_PORT),
            remote_bind_address=(localhost, Constants.FRIDA_PORT),
        )
        self._frida_server.start()

    def _portforward_frida_stop(self):
        """Stop local port forwarding for Frida server."""
        self.printer.debug('{} Stopping port forwarding'.format("FRIDA"))
        if self._frida_server:
            self._frida_server.stop()

    # ==================================================================================================================
    # UTILS - OS
    # ==================================================================================================================
    def _list_apps(self):
        """Retrieve all the 3rd party apps installed on the device."""
        agent_list = self.agent.exec_command_agent(
            Constants.AGENT_CMD_LIST_APPS)
        self._applist = Utils.string_to_json(agent_list)

    def select_target_app(self):
        """List all 3rd party apps installed and let the user choose which one to target."""
        # List all 3rd party apps
        self._list_apps()
        # Show menu to user
        self.printer.notify('Apps found:')
        app_name = choose_from_list(self._applist.keys())
        return app_name

    # ==================================================================================================================
    # EXPOSED COMMANDS
    # ==================================================================================================================
    def is_usb(self):
        """Returns true if using SSH over USB."""
        return self._ip == '127.0.0.1' or self._ip == 'localhost'

    def connect(self):
        """Connect to the device (both SSH and AGENT)."""
        # Using USB, setup port forwarding first
        if self.is_usb():
            self._portforward_usb_start()
            self._portforward_agent_start()
        # Setup channels
        self._connect_agent()
        self._connect_ssh()

    def disconnect(self):
        """Disconnect from the device (both SSH and AGENT)."""
        # Close channels
        self._disconnect_ssh()
        self._disconnect_agent()
        # Using USB, stop port forwarding first
        if self._port_forward_ssh:
            self._portforward_usb_stop()
            self._portforward_agent_stop()

    def setup(self, install_tools=True):
        """Create temp folder, and check if all tools are available"""
        # Setup temp folder
        self.printer.debug("Creating temp folder: %s" % self.TEMP_FOLDER)
        self.remote_op.dir_create(self.TEMP_FOLDER)
        # Detect OS version
        self._ios_version = self.agent.exec_command_agent(
            Constants.AGENT_CMD_OS_VERSION)
        # Install tools
        if install_tools:
            if not self._device_ready:
                self.printer.info("Configuring device...")
                self._device_ready = self.installer.configure()
        else:
            self._device_ready = True

    def cleanup(self):
        """Remove temp folder from device."""
        self.printer.debug("Cleaning up remote temp folder: %s" %
                           self.TEMP_FOLDER)
        self.remote_op.dir_delete(self.TEMP_FOLDER)

    def shell(self):
        """Spawn a system shell on the device."""
        cmd = 'sshpass -p "{password}" ssh {hostverification} -p {port} {username}@{ip}'.format(
            password=self._password,
            hostverification=Constants.DISABLE_HOST_VERIFICATION,
            port=self._port,
            username=self._username,
            ip=self._ip)
        self.local_op.command_interactive(cmd)

    def pull(self, src, dst):
        """Pull a file from the device."""
        self.printer.info("Pulling: %s -> %s" % (src, dst))
        self.remote_op.download(src, dst)

    def push(self, src, dst):
        """Push a file on the device."""
        self.printer.info("Pushing: %s -> %s" % (src, dst))
        self.remote_op.upload(src, dst)
Пример #6
0
class Device(object):
    # ==================================================================================================================
    # FRAMEWORK ATTRIBUTES
    # ==================================================================================================================
    # Connection Parameters
    _ip = Constants.GLOBAL_IP
    _port = Constants.GLOBAL_PORT
    _username = Constants.GLOBAL_USERNAME
    _password = Constants.GLOBAL_PASSWORD
    _pub_key_auth = bool(Constants.GLOBAL_PUB_KEY_AUTH)
    _tools_local = Constants.PATH_TOOLS_LOCAL
    _portforward = None
    _frida_server = None
    _debug_server = None
    # App specific
    _is_iOS8 = False
    _is_iOS9 = False
    _is_iOS7_or_less = False
    _applist = None
    _device_not_ready = bool(Constants.GLOBAL_SETUP_DEVICE)
    # On-Device Paths
    TEMP_FOLDER = Constants.DEVICE_PATH_TEMP_FOLDER
    DEVICE_TOOLS = Constants.DEVICE_TOOLS
    # Reference to External Objects
    conn = None
    app = None
    installer = None
    local_op = None
    remote_op = None
    printer = None

    # ==================================================================================================================
    # INIT
    # ==================================================================================================================
    def __init__(self):
        # Init related objects
        self.app = App(self)
        self.installer = Installer(self)
        self.local_op = LocalOperations()
        self.remote_op = RemoteOperations(self)
        self.printer = Printer()
        self.connect()
        self.setup()

    # ==================================================================================================================
    # UTILS - USB
    # ==================================================================================================================
    def _portforward_usb_start(self):
        """Setup USB port forwarding with TCPRelay."""
        # Check if the user chose a valid port
        if str(self._port) == '22':
            raise Exception(
                'Chosen port must be different from 22 in order to use USB over SSH'
            )
        # Setup the forwarding
        self.printer.verbose('Setting up USB port forwarding on port %s' %
                             self._port)
        cmd = '{app} -t 22:{port}'.format(app=self._tools_local['TCPRELAY'],
                                          port=self._port)
        self._portforward = self.local_op.command_subproc_start(cmd)

    def _portforward_usb_stop(self):
        """Stop USB port forwarding."""
        self.printer.verbose('Stopping USB port forwarding')
        self.local_op.command_subproc_stop(self._portforward)

    # ==================================================================================================================
    # UTILS - SSH
    # ==================================================================================================================
    def _connect_ssh(self):
        """Open a new connection using Paramiko."""
        try:
            path = os.path.join(os.environ['HOME'], '.ssh', 'id_rsa')
            key = paramiko.RSAKey.from_private_key_file(path)
            self.printer.verbose('Setting up SSH connection...')
            self.conn = paramiko.SSHClient()
            self.conn.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            self.conn.connect(self._ip,
                              port=self._port,
                              username=self._username,
                              password=self._password,
                              allow_agent=self._pub_key_auth,
                              pkey=key)

        except paramiko.AuthenticationException as e:
            raise Exception(
                'Authentication failed when connecting to %s. %s: %s' %
                (self._ip, type(e).__name__, e.message))
        except paramiko.SSHException as e:
            raise Exception(
                'Connection dropped. Please check your connection with the device, '
                'and reload the module. %s: %s' %
                (type(e).__name__, e.message))
        except Exception as e:
            raise Exception('Could not open a connection to %s. %s - %s' %
                            (self._ip, type(e).__name__, e.message))

    def _disconnect_ssh(self):
        """Close the connection, if available."""
        if self.conn:
            self.conn.close()

    def _exec_command_ssh(self, cmd, internal):
        """Execute a shell command on the device, then parse/print output."""
        # Paramiko Exec Command
        stdin, stdout, stderr = self.conn.exec_command(cmd)
        # Parse STDOUT/ERR
        out = stdout.readlines()
        err = stderr.readlines()
        if internal:
            # For processing, don't display output
            if err:
                # Show error and abort run
                err_str = ''.join(err)
                raise Exception(err_str)
        else:
            # Display output
            if out: map(lambda x: print('\t%s' % x, end=''), out)
            if err:
                map(
                    lambda x: print('\t%s%s%s' % (Colors.R, x, Colors.N),
                                    end=''), err)
        return out, err

    # ==================================================================================================================
    # FRIDA PORT FORWARDING
    # ==================================================================================================================
    def _portforward_frida_start(self):
        """Setup local port forward to enable communication with the Frida server running on the device"""
        localhost = '127.0.0.1'
        self._frida_server = SSHTunnelForwarder(
            (self._ip, int(self._port)),
            ssh_username=self._username,
            ssh_password=self._password,
            local_bind_address=(localhost, Constants.FRIDA_PORT),
            remote_bind_address=(localhost, Constants.FRIDA_PORT),
        )
        self._frida_server.start()

    def _portforward_frida_stop(self):
        """Stop local port forwarding"""
        if self._frida_server:
            self._frida_server.stop()

    # ==================================================================================================================
    # LLDB PORT FORWARDING
    # ==================================================================================================================
    def _portforward_debug_start(self):
        """Setup local port forward to enable communication with the debug server running on the device"""
        localhost = '127.0.0.1'
        self._debug_server = SSHTunnelForwarder(
            (self._ip, int(self._port)),
            ssh_username=self._username,
            ssh_password=self._password,
            local_bind_address=(localhost, Constants.DEBUG_PORT),
            remote_bind_address=(localhost, Constants.DEBUG_PORT),
        )
        self._debug_server.start()

    def _portforward_debug_stop(self):
        """Stop local port forwarding"""
        if self._debug_server:
            self._debug_server.stop()

    # ==================================================================================================================
    # UTILS - OS
    # ==================================================================================================================
    def _detect_ios_version(self):
        """Detect the iOS version running on the device."""
        if self.remote_op.file_exist(Constants.DEVICE_PATH_APPLIST_iOS8):
            self._is_iOS8 = True
        elif self.remote_op.file_exist(Constants.DEVICE_PATH_APPLIST_iOS9):
            self._is_iOS9 = True
        else:
            self._is_iOS7_or_less = True

    def _list_apps(self):
        """List all the 3rd party apps installed on the device."""
        def list_iOS_7():
            raise Exception('Support for iOS < 8 not yet implemented')

        def list_iOS_89(applist):
            # Refresh UICache in case an app was installed after the last reboot
            self.printer.verbose("Refreshing list of installed apps...")
            self.remote_op.command_blocking(
                '/bin/su mobile -c /usr/bin/uicache', internal=True)
            # Parse plist file
            pl = self.remote_op.parse_plist(applist)
            self._applist = pl["User"]

        # Dispatch
        self._detect_ios_version()
        if self._is_iOS8: list_iOS_89(Constants.DEVICE_PATH_APPLIST_iOS8)
        elif self._is_iOS9: list_iOS_89(Constants.DEVICE_PATH_APPLIST_iOS9)
        else: list_iOS_7()

    # ==================================================================================================================
    # EXPOSED COMMANDS
    # ==================================================================================================================
    def is_usb(self):
        """Returns true if using SSH over USB."""
        return self._ip == '127.0.0.1' or self._ip == 'localhost'

    def connect(self):
        """Connect to the device."""
        if self.is_usb():
            # Using SSH over USB, setup port forwarding first
            self._portforward_usb_start()
        # Connect
        self._connect_ssh()

    def disconnect(self):
        """Disconnect from the device."""
        if self._portforward:
            # Using SSH over USB, stop port forwarding
            self._portforward_usb_stop()
        self._disconnect_ssh()

    def setup(self):
        """Create temp folder, and check if all tools are available"""
        # Setup temp folder
        self.printer.verbose("Creating temp folder: %s" % self.TEMP_FOLDER)
        self.remote_op.dir_create(self.TEMP_FOLDER)
        # Install tools
        if self._device_not_ready:
            self.printer.info("Configuring device...")
            self._device_not_ready = self.installer.configure()

    def cleanup(self):
        """Remove temp folder from device."""
        self.printer.verbose("Cleaning up temp folder: %s" % self.TEMP_FOLDER)
        self.remote_op.dir_delete(self.TEMP_FOLDER)

    def shell(self):
        """Spawn a system shell on the device."""
        cmd = 'sshpass -p "{password}" ssh {hostverification} -p {port} {username}@{ip}'.format(
            password=self._password,
            hostverification=Constants.DISABLE_HOST_VERIFICATION,
            port=self._port,
            username=self._username,
            ip=self._ip)
        self.local_op.command_interactive(cmd)

    def pull(self, src, dst):
        """Pull a file from the device."""
        self.printer.info("Pulling: %s -> %s" % (src, dst))
        self.remote_op.download(src, dst)

    def push(self, src, dst):
        """Push a file on the device."""
        self.printer.info("Pushing: %s -> %s" % (src, dst))
        self.remote_op.upload(src, dst)

    def sync_files(self, src, dst):
        """sync files with device."""
        device_ip = self.remote_op.get_ip()
        device_ip = str(device_ip[0].strip())
        self.printer.verbose("The Device IP address is: %s" % device_ip)
        remote_dir = self._username + "@" + device_ip + ":" + src
        self.printer.verbose("Start to sync data from %s >> %s" %
                             (remote_dir, dst))
        subprocess.check_call(["rsync", "-avz", "--delete", remote_dir, dst])

    def install_ipa(self, src):
        """Install app with ipa file."""
        self.printer.verbose("Start to install %s to device" % src)
        dst = self.remote_op.build_temp_path_for_file("app.ipa")
        # Upload binary to device
        self.printer.verbose("Uploading binary: %s" % src)
        self.remote_op.upload(src, dst)
        # Install
        self.printer.verbose("Installing binary...")
        cmd = "{bin} {app}".format(bin=self.DEVICE_TOOLS['IPAINSTALLER'],
                                   app=dst)
        self.remote_op.command_interactive_tty(cmd)

    def uninstall_app(self, identifier):
        self.printer.verbose("Uninstall binary...")
        cmd = "{bin} -u {app}".format(bin=self.DEVICE_TOOLS['IPAINSTALLER'],
                                      app=identifier)
        self.remote_op.command_interactive_tty(cmd)

    def have_installed(self, app_name):
        self.printer.verbose("Start to check App whether have been installed")
        self._list_apps()
        if app_name in self._applist.keys():
            self.printer.verbose("The %s App have been installed" % app_name)
            return True
        else:
            self.printer.verbose("The %s App not been installed" % app_name)
            return False

    def get_keyboard_cache(self, LOCAL_KeyboardCache_DIR):
        """get keyboard cache from device."""
        self.printer.verbose("Start to get Keyobard cache data from device.")
        try:
            self.remote_op.download(Constants.KEYBOARD_CACHE +
                                    "en-dynamic.lm/",
                                    LOCAL_KeyboardCache_DIR,
                                    recursive=True)
            self.pull(Constants.KEYBOARD_CACHE + "dynamic-text.dat",
                      LOCAL_KeyboardCache_DIR + ".")
        except:
            self.printer.error("Cannot sync the keyboard cache data.")

    def get_cookies(self, LOCAL_COOKIES_DIR):
        """get Cookies file from device."""
        self.printer.verbose("Start to get cookies files from device.")
        try:
            self.remote_op.download(Constants.COOKIES_PATH,
                                    LOCAL_COOKIES_DIR,
                                    recursive=True)
        except:
            self.printer.error("Cannot sync the cookies files.")

    def analyze_cookies(self, fname):
        cmd = 'python {bin} {temp_file}'.format(
            bin=self._tools_local['BINARYCOOKIEREADER'], temp_file=fname)
        out = self.local_op.command_interactive(cmd)
        self.printer.verbose("COOKIES's value %s" % out)
        return out

    def dump_keychain(self):
        self.printer.verbose("Start to dump keychain data from device.")
        keychaindata = ''
        cmd = '{} --action dump'.format(self.DEVICE_TOOLS['KEYCHAIN_DUMP'])
        stdin, stdout, stderr = self.conn.exec_command(cmd)
        out = stdout.read()
        data = json.loads(out)
        for key in data:
            keychaindata += "," + (json.dumps(data[key]))
        return keychaindata[1:]

    def dump_head_memory(self, app_name, local_head_folder):
        try:
            self._list_apps()
            metadata = self.app.get_metadata(app_name)
            self.printer.info("Launching the app...")
            self.app.open(metadata['bundle_id'])
            pid = self.app.search_pid(metadata['name'])
            # Create temp files/folders
            dir_dumps = self.remote_op.build_temp_path_for_file("gdb_dumps")
            fname_mach = self.remote_op.build_temp_path_for_file("gdb_mach")
            fname_ranges = self.remote_op.build_temp_path_for_file(
                "gdb_ranges")
            self.remote_op.write_file(fname_mach, "info mach-regions")
            if self.remote_op.dir_exist(dir_dumps):
                self.remote_op.dir_delete(dir_dumps)
            self.remote_op.dir_create(dir_dumps)
            # Enumerate Mach Regions
            self.printer.info("Enumerating mach regions...")
            cmd = '''\
             gdb --pid="%s" --batch --command=%s 2>/dev/null | grep sub-regions | awk '{print $3,$5}' | while read range; do
               echo "mach-regions: $range"
               cmd="dump binary memory %s/dump`echo $range| awk '{print $1}'`.dmp $range"
               echo "$cmd" >> %s
           done ''' % (pid, fname_mach, dir_dumps, fname_ranges)
            self.remote_op.command_blocking(cmd)

            # Dump memory
            self.printer.info("Dumping memory (it might take a while)...")
            cmd = 'gdb --pid="%s" --batch --command=%s &>>/dev/null' % (
                pid, fname_ranges)
            self.remote_op.command_blocking(cmd)
            self.printer.info("Dump memory done.be stored under %s" %
                              dir_dumps)

            self.remote_op.download(dir_dumps, local_head_folder, True)
            #return dir_dumps
            '''
           # Check if we have dumps
           self.printer.verbose("Checking if we have dumps...")
           file_list = self.remote_op.dir_list(dir_dumps, recursive=True)
           failure = filter(lambda x: 'total 0' in x, file_list)
           if failure:
              self.printer.error('It was not possible to attach to the process (known issue in iOS9. A Fix is coming soon)')
              return
         '''
        except:
            self.printer.error("Can't dump head memory data, Plese retry!!! ")
Пример #7
0
class Device(object):
    # ==================================================================================================================
    # FRAMEWORK ATTRIBUTES
    # ==================================================================================================================
    # Connection Parameters
    _ip, _port, _agent_port, _username, _password = '', '', '', '', ''
    _tools_local = None
    # Port Forwarding
    _frida_server = None
    _port_forward_ssh, _port_forward_agent = None, None
    # App specific
    _applist, _ios_version = None, None
    # Reference to External Objects
    ssh, agent = None, None
    app, installer = None, None
    local_op, remote_op = None, None
    printer = None
    # On-Device Paths
    TEMP_FOLDER = Constants.DEVICE_PATH_TEMP_FOLDER
    DEVICE_TOOLS = Constants.DEVICE_TOOLS

    # ==================================================================================================================
    # INIT
    # ==================================================================================================================
    def __init__(self, ip, port, agent_port, username, password, pub_key_auth, tools):
        # Setup params
        self._ip = ip
        self._port = port
        self._agent_port = agent_port
        self._username = username
        self._password = password
        self._pub_key_auth = bool(pub_key_auth)
        self._tools_local = tools
        # Init related objects
        self.app = App(self)
        self.local_op = LocalOperations()
        self.remote_op = RemoteOperations(self)
        self.printer = Printer()
        self.agent = NeedleAgent(self)

    # ==================================================================================================================
    # UTILS - USB
    # ==================================================================================================================
    def _portforward_usb_start(self):
        """Setup USB port forwarding with TCPRelay."""
        # Check if the user chose a valid port
        if str(self._port) == '22':
            raise Exception('Chosen port must be different from 22 in order to use USB over SSH')
        # Setup the forwarding
        self.printer.debug('Setting up USB port forwarding on port %s' % self._port)
        cmd = '{app} -t 22:{port}'.format(app=self._tools_local['TCPRELAY'], port=self._port)
        self._port_forward_ssh = self.local_op.command_subproc_start(cmd)

    def _portforward_usb_stop(self):
        """Stop USB port forwarding."""
        self.printer.debug('Stopping USB port forwarding')
        self.local_op.command_subproc_stop(self._port_forward_ssh)

    # ==================================================================================================================
    # UTILS - SSH
    # ==================================================================================================================
    def _connect_ssh(self):
        """Open a new SSH connection using Paramiko."""
        try:
            self.printer.verbose("[SSH] Connecting ({}:{})...".format(self._ip, self._port))
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(self._ip, port=self._port, username=self._username, password=self._password,
                        allow_agent=self._pub_key_auth, look_for_keys=self._pub_key_auth)
            self.printer.notify("[SSH] Connected ({}:{})".format(self._ip, self._port))
            return ssh
        except paramiko.AuthenticationException as e:
            raise Exception('Authentication failed when connecting to %s. %s: %s' % (self._ip, type(e).__name__, e.message))
        except paramiko.SSHException as e:
            raise Exception('Connection dropped. Please check your connection with the device, '
                            'and reload the module. %s: %s' % (type(e).__name__, e.message))
        except Exception as e:
            raise Exception('Could not open a connection to %s. %s - %s' % (self._ip, type(e).__name__, e.message))

    def _disconnect_ssh(self):
        """Close the SSH connection, if available."""
        self.printer.verbose("[SSH] Disconnecting...")
        if self.ssh:
            self.ssh.close()

    @Retry()
    def _exec_command_ssh(self, cmd, internal):
        """Execute a shell command on the device, then parse/print output."""
        def hotfix_67():
            # TODO: replace with a more long-term fix
            import time
            timeout = 30
            endtime = time.time() + timeout
            while not stdout.channel.eof_received:
                time.sleep(1)
                if time.time() > endtime:
                    stdout.channel.close()
                    break

        # Paramiko Exec Command
        stdin, stdout, stderr = self.ssh.exec_command(cmd)
        hotfix_67()
        # Parse STDOUT/ERR
        out = stdout.readlines()
        err = stderr.readlines()
        if internal:
            # For processing, don't display output
            if err:
                # Show error and abort run
                err_str = ''.join(err)
                raise Exception(err_str)
        else:
            # Display output
            if out: map(lambda x: print('\t%s' % x, end=''), out)
            if err: map(lambda x: print('\t%s%s%s' % (Colors.R, x, Colors.N), end=''), err)
        return out, err

    # ==================================================================================================================
    # UTILS - AGENT
    # ==================================================================================================================
    def _portforward_agent_start(self):
        """Setup local port forward to enable communication with the Needle server running on the device."""
        self.printer.debug('{} Setting up port forwarding on port {}'.format(Constants.AGENT_TAG, self._agent_port))
        localhost = '127.0.0.1'
        self._port_forward_agent = SSHTunnelForwarder(
            (self._ip, int(self._port)),
            ssh_username=self._username,
            ssh_password=self._password,
            local_bind_address=(localhost, self._agent_port),
            remote_bind_address=(localhost, self._agent_port),
        )
        self._port_forward_agent.start()

    def _portforward_agent_stop(self):
        """Stop local port forwarding for Needle server."""
        self.printer.debug('{} Stopping port forwarding'.format(Constants.AGENT_TAG))
        if self._port_forward_agent:
            self._port_forward_agent.stop()

    def _connect_agent(self):
        self.agent.connect()
        # Ensure the tunnel has been established (especially after auto-reconnecting)
        self.agent.exec_command_agent(Constants.AGENT_CMD_OS_VERSION)

    def _disconnect_agent(self):
        self.agent.disconnect()

    # ==================================================================================================================
    # FRIDA PORT FORWARDING
    # ==================================================================================================================
    def _portforward_frida_start(self):
        """Setup local port forward to enable communication with the Frida server running on the device."""
        self.printer.debug('{} Setting up port forwarding on port {}'.format("[FRIDA]", Constants.FRIDA_PORT))
        localhost = '127.0.0.1'
        self._frida_server = SSHTunnelForwarder(
            (self._ip, int(self._port)),
            ssh_username=self._username,
            ssh_password=self._password,
            local_bind_address=(localhost, Constants.FRIDA_PORT),
            remote_bind_address=(localhost, Constants.FRIDA_PORT),
        )
        self._frida_server.start()

    def _portforward_frida_stop(self):
        """Stop local port forwarding for Frida server."""
        self.printer.debug('{} Stopping port forwarding'.format("FRIDA"))
        if self._frida_server:
            self._frida_server.stop()

    # ==================================================================================================================
    # UTILS - OS
    # ==================================================================================================================
    def _list_apps(self, hide_system_apps=False):
        """Retrieve all the 3rd party apps installed on the device."""
        agent_list = self.agent.exec_command_agent(Constants.AGENT_CMD_LIST_APPS)
        self._applist = Utils.string_to_json(agent_list)
        if hide_system_apps:
            self._applist = {k: v for k, v in self._applist.iteritems() if v["BundleType"] == "User"}

    def select_target_app(self):
        """List all apps installed and let the user choose which one to target."""
        # Show menu to user
        self.printer.notify('Apps found:')
        app_name = choose_from_list(self._applist.keys())
        return app_name

    # ==================================================================================================================
    # EXPOSED COMMANDS
    # ==================================================================================================================
    def is_usb(self):
        """Returns true if using SSH over USB."""
        return self._ip == '127.0.0.1' or self._ip == 'localhost'

    def connect(self):
        """Connect to the device (both SSH and AGENT)."""
        # Using USB, setup port forwarding first
        if self.is_usb():
            self._portforward_usb_start()
            self._portforward_agent_start()
        # Setup channels
        self._connect_agent()
        self.ssh = self._connect_ssh()

    def disconnect(self):
        """Disconnect from the device (both SSH and AGENT)."""
        # Close channels
        self._disconnect_ssh()
        self._disconnect_agent()
        # Using USB, stop port forwarding first
        if self._port_forward_ssh:
            self._portforward_usb_stop()
            self._portforward_agent_stop()

    def setup(self):
        """Create temp folder, and check if all tools are available"""
        # Setup temp folder
        self.printer.debug("Creating temp folder: %s" % self.TEMP_FOLDER)
        self.remote_op.dir_create(self.TEMP_FOLDER)
        # Detect OS version
        if not self._ios_version:
            self._ios_version = self.agent.exec_command_agent(Constants.AGENT_CMD_OS_VERSION).strip()

    def cleanup(self):
        """Remove temp folder from device."""
        self.printer.debug("Cleaning up remote temp folder: %s" % self.TEMP_FOLDER)
        self.remote_op.dir_delete(self.TEMP_FOLDER)

    def shell(self):
        """Spawn a system shell on the device."""
        cmd = 'sshpass -p "{password}" ssh {hostverification} -p {port} {username}@{ip}'.format(password=self._password,
                                                                                                hostverification=Constants.DISABLE_HOST_VERIFICATION,
                                                                                                port=self._port,
                                                                                                username=self._username,
                                                                                                ip=self._ip)
        self.local_op.command_interactive(cmd)

    def pull(self, src, dst):
        """Pull a file from the device."""
        self.printer.info("Pulling: %s -> %s" % (src, dst))
        self.remote_op.download(src, dst)

    def push(self, src, dst):
        """Push a file on the device."""
        self.printer.info("Pushing: %s -> %s" % (src, dst))
        self.remote_op.upload(src, dst)