QTA自动化测试 探讨 iOS 非越狱的动态插桩原理及自动化测试的应用
大家有多久没有对自己的 iPhone 越狱了,越狱意味着打开了一扇 iOS 的 root 大门,但同时也对稳定和安全 say goodbye 了。那么非越狱的 iOS 手机对于我们的自动化测试还能做些啥?今天和大家一起探讨关于 iOS 非越狱的动态插桩原理及其在自动化测试领域的应用。
一、前言
iOS 的动态插桩(iOS hook)技术在 iOS 越狱界已经是耳熟能详的话题,但是有一个问题——越狱后的手机不稳定,不稳定对于自动化测试来说就是致命的伤害,所以本文主要分析 iOS 在非越狱手机上的动态插桩技术及其自动化方面的应用。
二、QT4i 通用测试桩的介绍
QT4i是我们的 QTA 自动化测试提供的 ios 框架,下面我们先以 QT4i 的例子了解下动态插桩的实现原理。QT4i 框架提供了基于动态插桩原理实现的一个通用测试桩——QT4iSTub,对于需要访问被测 App 的进程内接口的测试场景,提供了一种新的测试思路,同时也提升和丰富了 App 可测性。下图是 QT4iSTub 的实现原理,通过 Python 层的 API 接口可以直接调用 APP 内部实现的 ObjectIve-c 方法。
三、iOS 的动态插桩原理分析
动态插桩是在没有目标 app 源码的前提下,通过一些技术手段实现对目标 app 的 ipa 安装包的修改,再将修改后的 app 安装到手机设备上,从而达到改变目标 app 的表现行为的目的。从这里的定义可以看出,iOS 动态插桩的步骤大致分为三个:编写 hook 方法的具体内容(改变目标 app 的行为)、注入目标 app(保证目标 app 启动时能加载 hook 的内容)、重签名目标 app(保证修改后的 app 能在非越狱的手机上能安装)。下面结合这三个步骤分析 iOS 动态插桩的原理。
1. 编写 hook 方法,改变目标 app 的行为
工欲善其事,必先利其器,开发 iOS 的动态桩,可以选择theos或者MonkeyDev作为开发工具,下面的例子以 theos 为例。
- 使用 theos 创建 iOS tweak 工程,这里解释一下何为 tweak。Tweak 实质上是 iOS 平台的动态库,以 dylib 这种形式存在,类似 Windows 下的 .dll,Linux 下的 .so,Mac 下的 .dylib/.tbd。与静态库相反,动态库在编译时并不会被拷贝到目标程序中,目标程序中只会存储指向动态库的引用,等到程序运行时,动态库才会被真正加载进来。
- 打开工程中的 Tweak.xm 文件,编写 hook 方法的具体内容,这里 theos 提供一套 Logos 命令(以% 开头)简化我们实现 hook 方法的过程。在这套 Logos 命令背后,theos 定义了一系列的宏和函数,底层利用 objective-c 的 runtime 特性来替换系统或者目标 app 的函数 (Method Swizzling),从而实现对目标 app 的 hook。下面的代码展示了一个简单功能,在目标 app 退到后台时打印一条日志记录。
%hook AppDelegate
// Hooking an instance method with an argument.
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(@"App entered background!!!");
%orig; // Call through to the original function with its original arguments.
}
%end
- 编译 Tweak 工程,生成 hook 后的动态库(dylib)。如果 Tweak 工程依赖第三方库,需要将第三方库放入 $THEOS/lib 目录。
2. 注入目标 app,保证目标启动时会加载 hook 的动态库
前面介绍 tweak 的时候已经说明,要在目标 app 运行时加载我们的 dylib,必须保证 app 中存储有指向我们的 dylib 的引用。所以,注入目标 app 的动作就是修改目标 app 的二进制文件。
我们先看下 iOS 中可执行文件的格式 Mach-O,如下图所示,包含三个部分:
- Mach-O 头部(mach header):描述了 Mach-O 的 cpu 架构、文件类型以及加载命令等信息;
- 加载命令(load command):描述了文件中数据的具体组织结构,不同的数据类型使用不同的加载命令表示;
- Data: Data 中的每个段(segment)的数据都保存在这里,段的概念与 ELF 文件中段的概念类似。每个段都有一个或多个 Section,它们存放了具体的数据与代码。
了解了 Mach-O 文件格式后,我们注入 app 的目标就很清晰了,也即在目标 app 的可执行文件的 Load Command 部分添加一个加载命令 LC_LOAD_DYLIB 指向我们的 dylib,使得目标 app 启动时可以加载我们的 dylib。这里推荐一个命令行工具insert_dylib方便我们完成修改动作。修改完后,查看目标 app 的 Mach-O 结构如下所示:
3. 重签名目标 app
对于非越狱的 iOS 设备来说,系统的签名校验机制是保证 app 安全的重要防线。然后,事物都有它的两面性,一方面为我们提供安全保障,另一方面却阻碍了我们的动态插桩(直接安装修改后的二进制文件会导致安装失败)。所以,这里的重签名显得尤为重要,一旦失败将前功尽弃。重要的事情说三遍:必须要用花钱买的个人开发者证书或者企业证书进行重签名!必须要用花钱买的个人开发者证书或者企业证书进行重签名!必须要用花钱买的个人开发者证书或者企业证书进行重签名!
下面简单说下重签名的步骤:
1) 解压目标 app 的安装包,将 Tweak 工程生成的 dylib 文件放入 Payload/app 名的目录下;
2) 重签名步骤 1) 中的 dylib;
3)删除原有签名文件,对整个安装包使用有效的证书重签名。
四、动态插桩在 iOS 自动化测试中的应用
目前主流的 iOS UI 自动化测试都是基于 apple 官方的 XCTest 框架实现的,受限于 XCTest 和被测 app 之间的通信是跨进程的方式,很多基于被测 app 内部信息的测试场景就无法覆盖了。而动态插桩的测试方案很好的弥补了 XCTest 的不足,因为动态插桩保证了测试进程和被测 app 是同一进程(也即进程内),可以方便采集被测 app 的内部信息,依据测试需要修改被测 app 的状态等,下面给出一些动态插桩在自动化测试中的典型应用。
1、app 的沙盒访问:读取沙盒信息,清理登录态
对比利用 itunes 私有协议访问沙盒,动态插桩方案更可靠稳定,访问效率更高;
2、访问系统相册:上传图片、比对图片
由于系统相册中的图片 iOS 系统进行了加密,直接无法访问和文件关联,但是动态插桩可以轻松实现;
3、动态切换 APP 内的启动参数,无需重新编译安装包,通过即可完成 APP 内的配置参数的切换;
4、获取自动化测试资源
例如:自动化用例需要一些比较大的视频文件,相比于在 mac 上下载后,通过 usb 传输到手机,让手机上 app 主动下载是效率更好的方式,插桩便可以实现;
5、动态修改当前窗口某控件的属性,用于国际化的语言排版展示等测试场景。
五、感兴趣的同学可以加入 QQ 群和公众号交流
如果你想要了解更多资讯,欢迎关注我们的微信公众号 我们会定时向大家推送团队同学分享的经验文章哦。