Ejemplo n.º 1
0
 def screenshot_thread():
     prev_image = None
     max_width = max_height = 0
     while True:
         data = recv_data(24)
         if not data: return
         timestamp, left, top, width, height, data_len = struct.unpack('I' * 6, data)
         if data_len > 0:
             data = recv_data(data_len)
             assert(len(data) == data_len)
             fp = BytesIO(data)
             image = Image.open(fp)
             # image.verify()
             w, h = image.size
             if w > max_width: max_width = w
             if h > max_height: max_height = h
             if w < max_width or h < max_height:
                 # 此时prev_image一定不为空
                 try:
                     prev_image.paste(image, (left, top, left + width, top + height))
                 except Exception as e:
                     err_msg = 'compose image [%s]%r failed: %s' % (data_len, (left, top, width, height), e)
                     raise RuntimeError(err_msg)
             else:
                 prev_image = image
         for callback in self._screenshot_callbacks:
             try:
                 callback(copy.deepcopy(prev_image))
             except:
                 logger.exception('run callback %s failed' % callback.__name__)
Ejemplo n.º 2
0
 def _start(self, clear_state=True, kill_process=True, start_extra_params={}):
     '''启动Android微信
     '''
     
     if kill_process == True:
         self.device.kill_process(self.package_name)  # 杀死已有进程
         
     if clear_state == True:
         # 清登录态,不清数据
         logger.warn('Clearing user state')
         self.run_shell_cmd('rm /data/data/%s/shared_prefs/%s*.xml' % (self.package_name, self.package_name))
         self.run_shell_cmd('rm /data/data/%s/MicroMsg/*.cfg' % (self.package_name))
         self.run_shell_cmd('rm /data/data/%s/shared_prefs/system_config_prefs.xml' % (self.package_name))
         
     # try:
     #     # 目前还不支持xwalk内核,先强制不使用该内核
     #     self.run_shell_cmd('rm -r /data/data/%s/app_xwalkconfig' % (self.package_name))
     #     self.run_shell_cmd('touch /data/data/%s/app_xwalkconfig' % (self.package_name))
     #     self.run_shell_cmd('chmod 777 /data/data/%s/app_xwalkconfig' % (self.package_name))
     #     self.run_shell_cmd('rm -r /data/data/%s/app_xwalk_*' % (self.package_name))
     # except:
     #     logger.exception('Disable xwalk failed')
         
     try:
         self.device.start_activity(self.startup_activity, extra=start_extra_params) 
     except TimeoutError:
         logger.exception('Start Android Wechat Failed')
         self.device.clear_data(self.package_name)
         self.device.start_activity(self.startup_activity, extra=start_extra_params)
Ejemplo n.º 3
0
    def refresh_media_store(self, file_path=""):
        """刷新图库,显示新拷贝到手机的图片
        """
        from qt4a.androiddriver.adb import TimeoutError

        command = ""
        if not file_path:
            if (hasattr(self, "_last_refresh_all")
                    and time.time() - self._last_refresh_all < 2 * 60):
                logger.warn("[DeviceDriver] 120S内不允许全盘重复刷新")
                return
            sdcard_path = self.get_external_sdcard_path()
            command = (
                "am broadcast -a android.intent.action.MEDIA_MOUNTED --ez read-only false -d file://%s"
                % sdcard_path)
            self._last_refresh_all = time.time()
        else:
            if file_path.startswith("/sdcard/"):
                file_path = self.get_external_sdcard_path() + file_path[7:]
            command = (
                "am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://%s"
                % file_path)
        try:
            self.adb.run_shell_cmd(command, self.adb.is_rooted())
        except TimeoutError:
            logger.exception("refresh_media_store failed")
            return False
        # self.adb.run_shell_cmd('am broadcast -a android.intent.action.BOOT_COMPLETED')
        if file_path and self.adb.get_sdk_version() >= 16:
            return self._wait_for_media_available(file_path)
Ejemplo n.º 4
0
 def monitor_thread(self):
     '''监控线程
     '''
     interval = 1
     while self._monitor_run:
         for task, task_end_time in self._monitor_task_list:
             time_now = time.time()
             if task_end_time == 0 or (task_end_time > 0 and time_now < task_end_time):
                 try:
                     task()
                     time.sleep(0.1)
                 except:
                     logger.exception('run task %s failed' % task.__name__)
                     self._resume_main_thread()  # 防止出错后主线程阻塞一段时间
         time.sleep(interval)
Ejemplo n.º 5
0
 def monitor_thread(self):
     '''监控线程
     '''
     interval = 1
     while self._monitor_run:
         for task in self._monitor_task_list:
             time_now = time.time()
             if task['end_time'] == 0 or (task['end_time'] > 0 and time_now < task['end_time']):
                 if time.time() - task['last_exec_time'] >= task['interval']:
                     try:
                         task['task']()
                     except:
                         logger.exception('run task %s failed' % task['task'].__name__)
                         self._resume_main_thread()  # 防止出错后主线程阻塞一段时间
                     task['last_exec_time'] = time.time()
         time.sleep(interval)
Ejemplo n.º 6
0
    def push_file(self, src_path, dst_path):
        '''向手机中拷贝文件
        
        :param src_path: PC上的源路径
        :type src_path:  string
        :param dst_path: 手机上的目标路径
        :type dst_path:  string
        '''

        if not os.path.exists(src_path):
            raise RuntimeError('File: %s not exist' % src_path)
        file_size = os.path.getsize(src_path)
        is_zip = False
        if file_size >= 5 * 1024 * 1024:
            is_zip = True
            zip_file_path = src_path + '.zip'
            zip_file = zipfile.ZipFile(zip_file_path, 'w',
                                       zipfile.ZIP_DEFLATED)
            if dst_path[-1] == '/':
                # filename not specified
                filename = os.path.split(src_path)[-1]
            else:
                filename = dst_path.split('/')[-1]
            zip_file.write(src_path, filename)
            zip_file.close()
            src_path = zip_file_path
            dst_path += '.zip'
        ret = self.adb.push_file(src_path, dst_path)
        if is_zip:
            os.remove(src_path)
            if not self._device_driver.unzip_file(
                    dst_path, dst_path[:dst_path.rfind('/')]):
                logger.warn('unzip file %s failed' % dst_path)
                ret = self.adb.push_file(src_path[:-4], dst_path[:-4])
            elif dst_path.startswith('/data/'):
                self.adb.chmod(dst_path[:-4], '744')
            self.delete_file(dst_path)
            dst_path = dst_path[:-4]
        try:
            self.run_shell_cmd('touch "%s"' % dst_path)  # 修改文件修改时间
        except:
            logger.exception('touch file %s error' % dst_path)
        return ret
Ejemplo n.º 7
0
    def send_command(self, cmd_type, **kwds):
        '''send command
        '''
        packet = {}
        packet['Cmd'] = cmd_type
        packet['Seq'] = self.seq
        for key in kwds.keys():
            packet[key] = kwds[key]
        data = json.dumps(packet) + "\n"
        if six.PY2 and isinstance(data, unicode):
            data = data.encode('utf8')

        time0 = time_clock()
        self._lock.acquire()
        time1 = time_clock()
        delta = time1 - time0
        if self._enable_log and delta >= 0.05:
            logger.info('send wait %s S' % delta)
        if self._enable_log: logger.debug('send: %s' % (data[:512].strip()))

        time0 = time_clock()
        try:
            result = self.send(data)
        except Exception as e:
            # 避免因异常导致死锁
            logger.exception('send %r error: %s' % (data, e))
            result = None
        self._lock.release()  # 解锁
        if not result: return None

        time1 = time_clock()
        try:
            rsp = json.loads(result)
        except:
            logger.error('json error: %r' % (result))
            raise
        else:
            if self._enable_log:
                delta = int(1000 * (time1 - time0))
                if 'HandleTime' in rsp: delta -= rsp['HandleTime']
                logger.debug('recv: [%d]%s\n' % (delta, result[:512].strip()))
            return rsp
Ejemplo n.º 8
0
 def hello(self):
     '''确认Server身份
     '''
     for _ in range(3):
         try:
             result = self.send_command(EnumCommand.CmdHello)
         except SocketError:
             time.sleep(1)
             continue
         except AndroidSpyError:
             logger.exception('init error')
             time.sleep(2)
             continue
         # self._enable_debug()
         if not 'Result' in result:
             raise RuntimeError('Server error')
         items = result['Result'].split(':')
         if len(items) > 0 and items[-1].isdigit():
             if self._process['id'] and int(items[-1]) != self._process['id']:
                 raise RuntimeError('Server pid not match %s' % result['Result'])
         return result['Result']
Ejemplo n.º 9
0
 def hello(self):
     """确认Server身份
     """
     for _ in range(3):
         try:
             result = self.send_command(EnumCommand.CmdHello)
         except SocketError:
             time.sleep(1)
             continue
         except AndroidSpyError:
             logger.exception("init error")
             time.sleep(2)
             continue
         # self._enable_debug()
         if not "Result" in result:
             raise RuntimeError("Server error")
         items = result["Result"].split(":")
         if len(items) > 0 and items[-1].isdigit():
             if self._process["id"] and int(
                     items[-1]) != self._process["id"]:
                 raise RuntimeError("Server pid not match %s" %
                                    result["Result"])
         return result["Result"]
Ejemplo n.º 10
0
    def _init_driver(self):
        """初始化测试桩
        """
        self._client = self._create_client()
        if self._client != None:
            # 字段赋值
            self._process["name"] = self._process_name
            self._process["id"] = 0  # process id may change
            if self.hello() != None:
                self._process["id"] = self._adb.get_pid(self._process_name)
                return

        timeout = 20
        time0 = time.time()
        proc_exist = False
        while time.time() - time0 < timeout:
            if not proc_exist:
                pid = self._adb.get_pid(self._process_name)
                if pid > 0:
                    proc_exist = True
                    self._process["name"] = self._process_name
                    self._process["id"] = pid
                    break

            time.sleep(1)

        if not proc_exist:
            raise RuntimeError("进程:%s 在%d秒内没有出现" %
                               (self._process_name, timeout))

        inject_file = "inject"
        if self._adb.is_app_process64(
                pid if self._adb.is_rooted() else self._process_name):
            # 64 bit process
            inject_file += "64"
        timeout = 30

        try:
            if self._adb.is_art():
                # Android 5.0上发现注入容易导致进程退出
                self._wait_for_cpu_low(20, 10)

            time0 = time.time()
            cmdline = "%s/%s %s" % (
                self._get_driver_root_path(),
                inject_file,
                self._process_name,
            )
            while time.time() - time0 < timeout:
                if self._adb.is_rooted():
                    ret = self._adb.run_shell_cmd(cmdline,
                                                  True,
                                                  timeout=120,
                                                  retry_count=1)
                else:
                    ret = self._adb.run_as(self._process_name,
                                           cmdline,
                                           timeout=120,
                                           retry_count=1)
                logger.debug("inject result: %s" % ret)
                if "not found" in ret:
                    raise QT4ADriverNotInstalled(
                        "QT4A driver damaged, please reinstall QT4A driver")
                if "Inject Success" in ret:
                    break
                elif "Operation not permitted" in ret:
                    # 可能是进程处于Trace状态
                    pid = self._adb.get_pid(self._process_name)
                    status = self._adb.get_process_status(pid)
                    tracer_pid = int(status["TracerPid"])
                    if tracer_pid > 0:
                        if int(status["PPid"]) == tracer_pid:
                            # 使用TRACEME方式防注入
                            raise Exception("应用使用了防注入逻辑,注入失败")
                        logger.warn("TracerPid is %d" % tracer_pid)
                        self._adb.kill_process(tracer_pid)
                elif "Function not implemented" in ret:
                    raise Exception(
                        "Please install repacked app on this device")
                time.sleep(1)

        except RuntimeError as e:
            logger.exception("%s\n%s" % (e, self._adb.run_shell_cmd("ps")))
            if self._adb.is_rooted():
                logger.info(self._adb.dump_stack(self._process_name))
            raise e
        timeout = 10
        time0 = time.time()
        while time.time() - time0 < timeout:
            if self._client == None:
                self._client = self._create_client()
            if self._client != None and self.hello() != None:
                return
            time.sleep(0.1)
        raise RuntimeError("连接测试桩超时")
Ejemplo n.º 11
0
def copy_android_driver(device_id_or_adb,
                        force=False,
                        root_path=None,
                        enable_acc=True):
    """测试前的测试桩拷贝
    """
    from qt4a.androiddriver.adb import ADB

    if isinstance(device_id_or_adb, ADB):
        adb = device_id_or_adb
    else:
        adb = ADB.open_device(device_id_or_adb)

    if not root_path:
        current_path = os.path.abspath(__file__)
        if not os.path.exists(current_path) and ".egg" in current_path:
            # in egg
            egg_path = current_path
            while not os.path.exists(egg_path):
                egg_path = os.path.dirname(egg_path)
            assert egg_path.endswith(".egg")
            root_path = os.path.join(tempfile.mkdtemp(), "tools")
            extract_from_zipfile(egg_path, "qt4a/androiddriver/tools",
                                 root_path)
        else:
            root_path = os.path.join(
                os.path.dirname(os.path.abspath(__file__)), "tools")
    dst_path = "/data/local/tmp/qt4a/"

    current_version_file = os.path.join(root_path, "version.txt")
    f = open(current_version_file, "r")
    current_version = int(f.read())
    f.close()

    if not force:
        version_file = dst_path + "version.txt"
        version = adb.run_shell_cmd("cat %s" % version_file)

        if (version and not "No such file or directory" in version
                and current_version <= int(version)):
            install_qt4a_helper(adb, root_path)  # 避免QT4A助手被意外删除的情况
            # 不需要拷贝测试桩
            logger.warn("忽略本次测试桩拷贝:当前版本为%s,设备中版本为%s" %
                        (current_version, int(version)))
            return

    try:
        adb.chmod(dst_path[:-1], "777")
    except:
        pass

    rooted = adb.is_rooted()

    cpu_abi = adb.get_cpu_abi()
    print("Current CPU arch: %s" % cpu_abi)
    # use_pie = False
    # if adb.get_sdk_version() >= 21 and cpu_abi != "arm64-v8a":
    #     use_pie = True

    file_list = [
        os.path.join(cpu_abi, "droid_inject"),
        os.path.join(cpu_abi, "libdexloader.so"),
        os.path.join(cpu_abi, "screenkit"),
        "inject",
        "AndroidSpy.jar",
        "SpyHelper.jar",
        "SpyHelper.sh",
    ]
    if cpu_abi == "arm64-v8a":
        file_list.append(os.path.join(cpu_abi, "droid_inject64"))
        file_list.append(os.path.join(cpu_abi, "libdexloader64.so"))
        file_list.append("inject64")
    if adb.get_sdk_version() >= 21:
        file_list.append(os.path.join(cpu_abi, "libandroidhook_art.so"))

    if rooted and adb.is_selinux_opened():
        # 此时如果还是开启状态说明关闭selinux没有生效,主要是三星手机上面
        adb.run_shell_cmd("rm -r %s" % dst_path, True)
        # adb.run_shell_cmd('chcon u:object_r:shell_data_file:s0 %slibdexloader.so' % dst_path, True)  # 恢复文件context,否则拷贝失败
        # adb.run_shell_cmd('chcon u:object_r:shell_data_file:s0 %slibandroidhook.so' % dst_path, True)

    for file in file_list:
        file_path = os.path.join(root_path, file)
        # if use_pie and not "." in file and os.path.exists(file_path + "_pie"):
        #     file_path += "_pie"
        if not os.path.exists(file_path):
            continue
        save_name = os.path.split(file)[-1]
        if save_name.endswith("_art.so"):
            save_name = save_name.replace("_art", "")
        adb.push_file(file_path, dst_path + save_name)

    adb.chmod("%sdroid_inject" % dst_path, 755)
    adb.chmod("%sinject" % dst_path, 755)
    adb.chmod("%sscreenkit" % dst_path, 755)
    adb.run_shell_cmd("ln -s %sscreenkit %sscreenshot" % (dst_path, dst_path))

    if cpu_abi == "arm64-v8a":
        adb.chmod("%sdroid_inject64" % dst_path, 755)
        adb.chmod("%sinject64" % dst_path, 755)

    try:
        print(adb.run_shell_cmd("rm -R %scache" % dst_path,
                                rooted))  # 删除目录 rm -rf
    except RuntimeError as e:
        logger.warn("%s" % e)
    # logger.info(adb.run_shell_cmd('mkdir %scache' % (dst_path), True)) #必须使用root权限,不然生成odex文件会失败

    adb.mkdir("%scache" % (dst_path), 777)

    install_qt4a_helper(adb, root_path)

    version_file_path = os.path.join(root_path, "version.txt")
    dst_version_file_path = dst_path + os.path.split(version_file_path)[-1]
    adb.push_file(version_file_path, dst_version_file_path + ".tmp")  # 拷贝版本文件

    if rooted and adb.is_selinux_opened():
        # 此时如果还是开启状态说明关闭selinux没有生效,主要是三星手机上面
        # 获取sdcars context
        if adb.get_sdk_version() >= 23:
            sdcard_path = adb.get_sdcard_path()
            result = adb.run_shell_cmd("ls -Z %s" % sdcard_path)
            # u:object_r:media_rw_data_file:s0 u:object_r:rootfs:s0
            pattern = re.compile(r"\s+(u:object_r:.+:s0)\s+")
            ret = pattern.search(result)
            if not ret:
                raise RuntimeError("get sdcard context failed: %s" % result)
            context = ret.group(1)
            logger.info("sdcard context is %s" % context)
            adb.run_shell_cmd("chcon %s %s" % (context, dst_path),
                              True)  # make app access
            adb.run_shell_cmd(
                "chcon u:object_r:app_data_file:s0 %sSpyHelper.jar" % dst_path,
                True)
            adb.run_shell_cmd(
                "chcon u:object_r:app_data_file:s0 %sSpyHelper.sh" % dst_path,
                True)
            # 不修改文件context无法加载so
            adb.run_shell_cmd(
                "chcon u:object_r:system_file:s0 %slibdexloader.so" % dst_path,
                True)
            adb.run_shell_cmd(
                "chcon u:object_r:app_data_file:s0 %slibandroidhook.so" %
                dst_path, True)
            adb.run_shell_cmd(
                "chcon %s %sAndroidSpy.jar" % (context, dst_path), True)
            adb.run_shell_cmd("chcon %s %scache" % (context, dst_path), True)
        else:
            # 不修改文件context无法加载so
            adb.run_shell_cmd(
                "chcon u:object_r:app_data_file:s0 %slibdexloader.so" %
                dst_path, True)
            adb.run_shell_cmd(
                "chcon u:object_r:app_data_file:s0 %slibandroidhook.so" %
                dst_path, True)
            adb.run_shell_cmd(
                "chcon u:object_r:app_data_file:s0 %scache" % dst_path, True)

    if rooted:
        if adb.get_sdk_version() < 24:
            # 7.0以上发现生成的dex与运行时生成的dex有差别,可能导致crash
            logger.info(
                adb.run_shell_cmd(
                    "sh %sSpyHelper.sh loadDex %sAndroidSpy.jar %scache" %
                    (dst_path, dst_path, dst_path),
                    rooted,
                ))
            adb.chmod("%scache/AndroidSpy.dex" % dst_path, 666)
    else:
        if not "usage:" in adb.run_shell_cmd("sh %sSpyHelper.sh" % dst_path):
            adb.mkdir("%scache/dalvik-cache" % dst_path, 777)

    if rooted and adb.is_selinux_opened() and adb.get_sdk_version() >= 23:
        # 提升权限
        try:
            adb.list_dir("/system/bin/app_process32")
        except RuntimeError:
            adb.copy_file("/system/bin/app_process",
                          "%sapp_process" % dst_path)
        else:
            adb.copy_file("/system/bin/app_process32",
                          "%sapp_process" % dst_path)
        adb.chmod("%sapp_process" % dst_path, 755)
        adb.run_shell_cmd(
            "chcon u:object_r:system_file:s0 %sapp_process" % dst_path, True)

    adb.run_shell_cmd(
        "mv %s %s" % (dst_version_file_path + ".tmp", dst_version_file_path),
        rooted)

    # 同步手机时间
    device_driver = DeviceDriver(adb)
    try:
        input_method = "com.test.androidspy/.service.QT4AKeyboardService"
        device_driver.modify_system_setting("secure", "enabled_input_methods",
                                            input_method)
        device_driver.modify_system_setting("secure", "default_input_method",
                                            input_method)
        if enable_acc:
            device_driver.modify_system_setting(
                "secure",
                "enabled_accessibility_services",
                "com.test.androidspy/com.test.androidspy.service.QT4AAccessibilityService",
            )
            device_driver.modify_system_setting("secure",
                                                "accessibility_enabled", 1)
    except:
        logger.exception("set default input method failed")
    try:
        device_driver.modify_system_setting("system", "time_12_24", 24)
        device_driver.modify_system_setting("system", "screen_off_timeout",
                                            600 * 1000)
    except:
        logger.exception("set system time failed")
Ejemplo n.º 12
0
    def call(self, cmd, *args, **kwds):
        '''调用命令字
        '''
        cmd = cmd.replace('-', '_')
        if cmd == 'forward' and args[1] == '--remove':
            method = getattr(self, 'remove_forward')
            args = list(args)
            args.pop(1)  # remove --remove args
        else:
            method = getattr(self, cmd)
        # print (args)
        sync = True
        if 'sync' in kwds: sync = kwds.pop('sync')
        if 'timeout' in kwds and not cmd in ('shell', 'install', 'uninstall',
                                             'wait_for_device', 'reboot'):
            kwds.pop('timeout')
        if sync:
            ret = None
            retry_count = kwds.pop('retry_count')
            i = 0
            socket_error_count = 0
            while i < retry_count:
                try:
                    self._lock.acquire()
                    ret = method(*args, **kwds)
                    break
                except socket.error as e:
                    logger.exception(u'执行%s %s error' % (cmd, ' '.join(args)))
                    socket_error_count += 1
                    if socket_error_count <= 10: i -= 1
                    time.sleep(1)
                except AdbError as e:
                    err_msg = str(e)
                    if 'device not found' in err_msg:
                        return '', 'error: device not found'
                    elif 'cannot bind to socket' in err_msg:
                        return '', err_msg
                    elif 'cannot remove listener' in err_msg:
                        return '', err_msg
                    elif 'device offline' in err_msg:
                        return '', 'error: device offline'
                    elif 'Bad response' in err_msg or 'Device or resource busy' in err_msg or 'closed' in err_msg:  # wetest设备有时候会返回closed错误
                        # 需要重试
                        logger.exception('Run %s%s %r' %
                                         (cmd, ' '.join(args), e))
                    else:
                        raise RuntimeError(u'执行%s %s 命令失败:%s' %
                                           (cmd, ' '.join(args), e))
                    time.sleep(1)
                    if i >= retry_count - 1: raise e
                except RuntimeError as e:
                    logger.exception(u'执行%s%s %r' % (cmd, ' '.join(args), e))
                    if 'device not found' in str(e):
                        self.wait_for_device(args[0],
                                             retry_count=1,
                                             timeout=300)
                        self._sock = None
                        return self.call(cmd, *args, **kwds)
                finally:
                    i += 1
                    if self._sock != None:
                        self._sock.close()
                        self._sock = None
                    self._lock.release()

            if ret == None:
                raise TimeoutError(u'Run cmd %s %s failed' %
                                   (cmd, ' '.join(args)))

            if isinstance(ret, (six.string_types, six.binary_type)):
                return ret, ''
            else:
                return ret
        else:
            self._transport(args[0])  # 异步操作的必然需要发送序列号
            if cmd == 'shell':
                self._lock.acquire()
                self._send_command('shell:' + ' '.join(args[1:]))
                pipe = ADBPopen(self._sock)
                self._sock = None
                self._lock.release()
                return pipe
Ejemplo n.º 13
0
def copy_android_driver(device_id_or_adb,
                        force=False,
                        root_path=None,
                        enable_acc=True):
    '''测试前的测试桩拷贝
    '''
    from qt4a.androiddriver.adb import ADB
    from qt4a.androiddriver.util import AndroidPackage, version_cmp

    if isinstance(device_id_or_adb, ADB):
        adb = device_id_or_adb
    else:
        adb = ADB.open_device(device_id_or_adb)

    if not root_path:
        current_path = os.path.abspath(__file__)
        if not os.path.exists(current_path) and '.egg' in current_path:
            # in egg
            egg_path = current_path
            while not os.path.exists(egg_path):
                egg_path = os.path.dirname(egg_path)
            assert (egg_path.endswith('.egg'))
            root_path = os.path.join(tempfile.mkdtemp(), 'tools')
            extract_from_zipfile(egg_path, 'qt4a/androiddriver/tools',
                                 root_path)
        else:
            root_path = os.path.join(
                os.path.dirname(os.path.abspath(__file__)), 'tools')
    dst_path = '/data/local/tmp/qt4a/'

    current_version_file = os.path.join(root_path, 'version.txt')
    f = open(current_version_file, 'r')
    current_version = int(f.read())
    f.close()

    if not force:
        version_file = dst_path + 'version.txt'
        version = adb.run_shell_cmd('cat %s' % version_file)

        if version and not 'No such file or directory' in version and current_version <= int(
                version):
            # 不需要拷贝测试桩
            logger.warn('忽略本次测试桩拷贝:当前版本为%s,设备中版本为%s' %
                        (current_version, int(version)))
            return

    try:
        adb.chmod(dst_path[:-1], '777')
    except:
        pass

    rooted = adb.is_rooted()

    cpu_abi = adb.get_cpu_abi()
    print('Current CPU arch: %s' % cpu_abi)
    use_pie = False
    if adb.get_sdk_version() >= 21 and cpu_abi != 'arm64-v8a':
        use_pie = True

    file_list = [
        os.path.join(cpu_abi, 'droid_inject'),
        os.path.join(cpu_abi, 'libdexloader.so'),
        os.path.join(cpu_abi, 'screenkit'),
        os.path.join(cpu_abi, 'libandroidhook.so'), 'inject', 'AndroidSpy.jar',
        'SpyHelper.jar', 'SpyHelper.sh'
    ]

    if cpu_abi == 'arm64-v8a':
        file_list.append(os.path.join(cpu_abi, 'droid_inject64'))
        file_list.append(os.path.join(cpu_abi, 'libdexloader64.so'))
        file_list.append('inject64')
    if adb.get_sdk_version() >= 21:
        file_list.append(os.path.join(cpu_abi, 'libandroidhook_art.so'))

    if rooted and adb.is_selinux_opened():
        # 此时如果还是开启状态说明关闭selinux没有生效,主要是三星手机上面
        adb.run_shell_cmd('rm -r %s' % dst_path, True)
        # adb.run_shell_cmd('chcon u:object_r:shell_data_file:s0 %slibdexloader.so' % dst_path, True)  # 恢复文件context,否则拷贝失败
        # adb.run_shell_cmd('chcon u:object_r:shell_data_file:s0 %slibandroidhook.so' % dst_path, True)

    for file in file_list:
        file_path = os.path.join(root_path, file)
        if use_pie and not '.' in file and os.path.exists(file_path + '_pie'):
            file_path += '_pie'
        if not os.path.exists(file_path):
            continue
        save_name = os.path.split(file)[-1]
        if save_name.endswith('_art.so'):
            save_name = save_name.replace('_art', '')
        adb.push_file(file_path, dst_path + save_name)

    adb.chmod('%sdroid_inject' % dst_path, 755)
    adb.chmod('%sinject' % dst_path, 755)
    adb.chmod('%sscreenkit' % dst_path, 755)
    adb.run_shell_cmd('ln -s %sscreenkit %sscreenshot' % (dst_path, dst_path))

    if cpu_abi == 'arm64-v8a':
        adb.chmod('%sdroid_inject64' % dst_path, 755)
        adb.chmod('%sinject64' % dst_path, 755)

    try:
        print(adb.run_shell_cmd('rm -R %scache' % dst_path,
                                rooted))  # 删除目录 rm -rf
    except RuntimeError as e:
        logger.warn('%s' % e)
    # logger.info(adb.run_shell_cmd('mkdir %scache' % (dst_path), True)) #必须使用root权限,不然生成odex文件会失败

    adb.mkdir('%scache' % (dst_path), 777)

    qt4a_helper_package = 'com.test.androidspy'
    apk_path = os.path.join(root_path, 'QT4AHelper.apk')
    if adb.get_package_path(qt4a_helper_package):
        # 判断版本
        installed_version = adb.get_package_version(qt4a_helper_package)
        package = AndroidPackage(apk_path)
        install_version = package.version
        if version_cmp(install_version, installed_version) > 0:
            adb.install_apk(apk_path, True)
    else:
        adb.install_apk(apk_path)

    # adb.install_apk(os.path.join(root_path, 'QT4AMockApp.apk'), True)
    version_file_path = os.path.join(root_path, 'version.txt')
    dst_version_file_path = dst_path + os.path.split(version_file_path)[-1]
    adb.push_file(version_file_path, dst_version_file_path + '.tmp')  # 拷贝版本文件

    if rooted and adb.is_selinux_opened():
        # 此时如果还是开启状态说明关闭selinux没有生效,主要是三星手机上面
        # 获取sdcars context
        if adb.get_sdk_version() >= 23:
            import re
            sdcard_path = adb.get_sdcard_path()
            result = adb.run_shell_cmd('ls -Z %s' % sdcard_path)
            # u:object_r:media_rw_data_file:s0 u:object_r:rootfs:s0
            pattern = re.compile(r'\s+(u:object_r:.+:s0)\s+')
            ret = pattern.search(result)
            if not ret:
                raise RuntimeError('get sdcard context failed: %s' % result)
            context = ret.group(1)
            logger.info('sdcard context is %s' % context)
            adb.run_shell_cmd('chcon %s %s' % (context, dst_path),
                              True)  # make app access
            adb.run_shell_cmd(
                'chcon u:object_r:app_data_file:s0 %sSpyHelper.jar' % dst_path,
                True)
            adb.run_shell_cmd(
                'chcon u:object_r:app_data_file:s0 %sSpyHelper.sh' % dst_path,
                True)
            # 不修改文件context无法加载so
            adb.run_shell_cmd(
                'chcon u:object_r:system_file:s0 %slibdexloader.so' % dst_path,
                True)
            adb.run_shell_cmd(
                'chcon u:object_r:app_data_file:s0 %slibandroidhook.so' %
                dst_path, True)
            adb.run_shell_cmd(
                'chcon %s %sAndroidSpy.jar' % (context, dst_path), True)
            adb.run_shell_cmd('chcon %s %scache' % (context, dst_path), True)
        else:
            # 不修改文件context无法加载so
            adb.run_shell_cmd(
                'chcon u:object_r:app_data_file:s0 %slibdexloader.so' %
                dst_path, True)
            adb.run_shell_cmd(
                'chcon u:object_r:app_data_file:s0 %slibandroidhook.so' %
                dst_path, True)
            adb.run_shell_cmd(
                'chcon u:object_r:app_data_file:s0 %scache' % dst_path, True)

    if rooted:
        if adb.get_sdk_version() < 24:
            # 7.0以上发现生成的dex与运行时生成的dex有差别,可能导致crash
            logger.info(
                adb.run_shell_cmd(
                    'sh %sSpyHelper.sh loadDex %sAndroidSpy.jar %scache' %
                    (dst_path, dst_path, dst_path), rooted))
            adb.chmod('%scache/AndroidSpy.dex' % dst_path, 666)
    else:
        if not 'usage:' in adb.run_shell_cmd('sh %sSpyHelper.sh' % dst_path):
            adb.mkdir('%scache/dalvik-cache' % dst_path, 777)

    if rooted and adb.is_selinux_opened() and adb.get_sdk_version() >= 23:
        # 提升权限
        try:
            adb.list_dir('/system/bin/app_process32')
        except RuntimeError:
            adb.copy_file('/system/bin/app_process',
                          '%sapp_process' % dst_path)
        else:
            adb.copy_file('/system/bin/app_process32',
                          '%sapp_process' % dst_path)
        adb.chmod('%sapp_process' % dst_path, 755)
        adb.run_shell_cmd(
            'chcon u:object_r:system_file:s0 %sapp_process' % dst_path, True)

    adb.run_shell_cmd(
        'mv %s %s' % (dst_version_file_path + '.tmp', dst_version_file_path),
        rooted)

    # 同步手机时间
    device_driver = DeviceDriver(adb)
    try:
        input_method = 'com.test.androidspy/.service.QT4AKeyboardService'
        device_driver.modify_system_setting('secure', 'enabled_input_methods',
                                            input_method)
        device_driver.modify_system_setting('secure', 'default_input_method',
                                            input_method)
        if enable_acc:
            device_driver.modify_system_setting(
                'secure', 'enabled_accessibility_services',
                'com.test.androidspy/com.test.androidspy.service.QT4AAccessibilityService'
            )
            device_driver.modify_system_setting('secure',
                                                'accessibility_enabled', 1)
    except:
        logger.exception('set default input method failed')
    try:
        device_driver.modify_system_setting('system', 'time_12_24', 24)
        device_driver.modify_system_setting('system', 'screen_off_timeout',
                                            600 * 1000)
    except:
        logger.exception('set system time failed')
Ejemplo n.º 14
0
    def _init_driver(self):
        '''初始化测试桩
        '''
        self._client = self._create_client()
        if self._client != None:
            # 字段赋值
            self._process['name'] = self._process_name
            self._process['id'] = 0  # process id may change
            if self.hello() != None:
                self._process['id'] = self._adb.get_pid(self._process_name)
                return

        timeout = 20
        time0 = time.time()
        proc_exist = False
        while time.time() - time0 < timeout:
            if not proc_exist:
                pid = self._adb.get_pid(self._process_name)
                if pid > 0:
                    proc_exist = True
                    self._process['name'] = self._process_name
                    self._process['id'] = pid
                    break

            time.sleep(1)

        if not proc_exist:
            raise RuntimeError('进程:%s 在%d秒内没有出现' %
                               (self._process_name, timeout))

        inject_file = 'inject'
        if self._adb.is_app_process64(
                pid if self._adb.is_rooted() else self._process_name):
            # 64 bit process
            inject_file += '64'
        timeout = 30

        try:
            if self._adb.is_art():
                # Android 5.0上发现注入容易导致进程退出
                self._wait_for_cpu_low(20, 10)

            time0 = time.time()
            cmdline = '%s/%s %s' % (self._get_driver_root_path(), inject_file,
                                    self._process_name)
            while time.time() - time0 < timeout:
                if self._adb.is_rooted():
                    ret = self._adb.run_shell_cmd(cmdline,
                                                  True,
                                                  timeout=120,
                                                  retry_count=1)
                else:
                    ret = self._adb.run_as(self._process_name,
                                           cmdline,
                                           timeout=120,
                                           retry_count=1)
                logger.debug('inject result: %s' % ret)
                if 'not found' in ret:
                    raise QT4ADriverNotInstalled(
                        'QT4A driver damaged, please reinstall QT4A driver')
                if 'Inject Success' in ret:
                    break
                elif 'Operation not permitted' in ret:
                    # 可能是进程处于Trace状态
                    pid = self._adb.get_pid(self._process_name)
                    status = self._adb.get_process_status(pid)
                    tracer_pid = int(status['TracerPid'])
                    if tracer_pid > 0:
                        if int(status['PPid']) == tracer_pid:
                            # 使用TRACEME方式防注入
                            raise Exception('应用使用了防注入逻辑,注入失败')
                        logger.warn('TracerPid is %d' % tracer_pid)
                        self._adb.kill_process(tracer_pid)
                time.sleep(1)

        except RuntimeError as e:
            logger.exception('%s\n%s' % (e, self._adb.run_shell_cmd('ps')))
            if self._adb.is_rooted():
                logger.info(self._adb.dump_stack(self._process_name))
            raise e
        timeout = 10
        time0 = time.time()
        while time.time() - time0 < timeout:
            if self._client == None:
                self._client = self._create_client()
            if self._client != None and self.hello() != None:
                return
            time.sleep(0.1)
        raise RuntimeError('连接测试桩超时')
Ejemplo n.º 15
0
    def call(self, cmd, *args, **kwds):
        """调用命令字
        """
        cmd = cmd.replace("-", "_")
        if cmd == "forward" and args[1] == "--remove":
            method = getattr(self, "remove_forward")
            args = list(args)
            args.pop(1)  # remove --remove args
        else:
            method = getattr(self, cmd)
        # print (args)
        sync = True
        if "sync" in kwds:
            sync = kwds.pop("sync")
        if "timeout" in kwds and not cmd in (
                "shell",
                "install",
                "uninstall",
                "wait_for_device",
                "reboot",
        ):
            kwds.pop("timeout")
        if sync:
            ret = None
            retry_count = kwds.pop("retry_count")
            i = 0
            socket_error_count = 0
            while i < retry_count:
                try:
                    self._lock.acquire()
                    ret = method(*args, **kwds)
                    break
                except socket.error as e:
                    logger.exception("执行%s %s error" % (cmd, " ".join(args)))
                    socket_error_count += 1
                    if socket_error_count <= 10:
                        i -= 1
                    time.sleep(1)
                except AdbError as e:
                    err_msg = str(e)
                    if "device not found" in err_msg:
                        return "", "error: device not found"
                    elif "cannot bind to socket" in err_msg:
                        return "", err_msg
                    elif "cannot remove listener" in err_msg:
                        return "", err_msg
                    elif "device offline" in err_msg:
                        return "", "error: device offline"
                    elif "Permission denied" in err_msg:
                        return "", "error: %s" % err_msg
                    elif ("Bad response" in err_msg
                          or "Device or resource busy" in err_msg
                          or "closed" in err_msg):  # wetest设备有时候会返回closed错误
                        # 需要重试
                        logger.exception("Run %s%s %r" %
                                         (cmd, " ".join(args), e))
                    else:
                        raise RuntimeError("执行%s %s 命令失败:%s" %
                                           (cmd, " ".join(args), e))
                    time.sleep(1)
                    if i >= retry_count - 1:
                        raise e
                except RuntimeError as e:
                    logger.exception("执行%s%s %r" % (cmd, " ".join(args), e))
                    if "device not found" in str(e):
                        self.wait_for_device(args[0],
                                             retry_count=1,
                                             timeout=300)
                        self._sock = None
                        return self.call(cmd, *args, **kwds)
                finally:
                    i += 1
                    if self._sock != None:
                        self._sock.close()
                        self._sock = None
                    self._lock.release()

            if ret == None:
                raise TimeoutError("Run cmd %s %s failed" %
                                   (cmd, " ".join(args)))

            if isinstance(ret, (six.string_types, six.binary_type)):
                return ret, ""
            else:
                return ret
        else:
            self._transport(args[0])  # 异步操作的必然需要发送序列号
            if cmd == "shell":
                self._lock.acquire()
                self._send_command("shell:" + " ".join(args[1:]))
                pipe = ADBPopen(self._sock)
                self._sock = None
                self._lock.release()
                return pipe