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__)
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)
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)
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)
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)
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
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
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']
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"]
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("连接测试桩超时")
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")
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
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')
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('连接测试桩超时')
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