大家好! 我叫Dmitry Fisko,我正在开发Yandex移动广告SDK。 我们的库旨在通过Android和iOS平台上的移动应用程序获利。 今天,我想向您介绍我们如何简化对Android应用程序中复杂的SDK集成错误的分析。 也许我们的经验将对您有所帮助。
我们的用户(移动应用程序的开发人员)并不总是阅读文档,因此有时他们会想出使用SDK的复杂方式。 不正确的集成会降低广告的效果,从而减少开发人员的收入。 为了帮助开发人员更好地利用应用程序获利,我们创建了一个主动监控系统,该系统可以分析移动应用程序的性能。 如果我们通过监视来了解问题,那么我们会与开发人员联系并帮助他们找到问题的根源并加以解决。
不幸的是,并不是所有的SDK集成错误都可以通过监视来识别。 如果出现这种情况,我们请合作伙伴澄清集成的细节。 然后,我们尝试确定问题的原因并帮助解决它们。 如果甚至这些信息不足以确定错误原因,我们也会要求合作伙伴允许对应用程序进行反向工程。 获得许可后,我们开始将应用程序中的广告SDK视为黑匣子。 我们通过代理查看网络活动,通过布局检查器检查广告视图的显示等。
从Android 7.0开始查看应用程序的网络活动是有问题的,因为默认情况下系统不信任用户设置的证书。 需要证书安装才能通过代理查看应用程序的SSL通信。 它可以通过在7.0之前的Android版本上启动应用程序,或通过例如通过Apktool向应用程序添加network_security_config来解决该问题。 您可以通过在模拟器或设备上运行Layout Inspector实用程序来查看广告视图的显示。 在这种情况下,您需要通过Apktool添加
debuggable = true属性来修改AndroidManifest.xml文件。
如果黑匣子方法还不够,并且问题无法重现,则可以查看应用程序的逻辑。 为此,您可以使用APK反编译实用程序,例如
JADX ,
Bytecode Viewer 。 但是这种方法通常会花费太多时间,并且并不总是会导致结果。 因此,为了快速了解应用程序如何从内部使用SDK,我们制作了一个脚本,将新的SDK实现替换为已构建的应用程序。
在应用程序中安装新的SDK实现
通过更改SDK代码,您可以通过修改后的SDK版本的类将任意代码嵌入到应用程序中,例如,启用其他日志记录模式。 操作算法如下。 剧本:
- 在smali中反汇编应用程序DEX文件;
- 将新版SDK的JAR文件转换为smali;
- 替换smali应用程序文件中的smali SDK文件的实现;
- 使用新版本的SDK重新组装应用程序。
D用smali组装应用程序DEX文件
您需要分解应用程序,将其分成较小的单元,以便可以更改SDK类的代码而无需更改应用程序代码。 如何处理组装好的应用程序? 将DEX分解成smali文件。
在Android中,应用程序将代码存储在DEX文件中。 您可以通过unzip实用程序从应用程序中提取它们,因为APK是具有结构化内容的常规存档。 与JAR相比,DEX文件具有二进制格式,用于更密集的代码打包。 由于DEX的二进制性质,它是不人道的,因此更改DEX本身是不合理的。 首先想到的是在Java中反编译DEX。 这样的转换是可能的,但是它是不平凡的,并且在代码功能丧失的情况下发生。 因此,我们将使用翻译成smali代码。 转换为smali可以使您以人类可读的形式准确地从DEX传输指令,并有可能随后转换为可用代码。
调用
smali实用程序会将DEX转换为smali代码中的一组类。 在这种情况下,将保留子包对类的初始排列。
将新的SDK转换为smali
我们将准备替换的SDK版本。 为了保证问题的可重复性,我们将基于已集成到应用程序中的同一版本创建一个启用了日志记录的SDK版本。 在应用程序中查找Yandex Mobile Ads SDK的已连接版本的最简单方法之一是通过Android Studio中的
Apk Analizer在MobileAds.getLibraryVersion()类中查看方法的内容。 找到广告SDK的使用版本后,我们切换到此版本的分支,并收集库版本和其他日志记录。 结果,我们得到一个AAR文件。 它包含资源和库代码。 在AAR文件中,我们只对JAR文件中的代码感兴趣,因为我们的SDK中没有外部资源:所有资源都直接内联到代码中或来自后端。 资源文件的缺乏简化了SDK到IDE的集成,而无需现代构建系统的支持。
要将应用程序中的SDK版本更改为新版本,我们将AAR设置为与反汇编应用程序相同的状态,也就是说,从AAR中获得了一组smali文件。 转换沿链进行:AAR→JAR→DEX→SMALI:
- 使用解压缩实用程序从AAR中提取带有代码的JAR;
- 我们通过Android SDK工具中的dxdump实用工具将JAR转换为DEX;
- 我们使用smali实用工具(以DEX文件作为参数)以smali代码获取文件。
SDK实作
收到带有日志的应用程序和SDK的smali文件后,我们用新的SDK替换了SDK的实现。 然后,我们重建应用程序。 当向smali广播时,结果类通过子包维护其位置。 因此,如果知道SDK类所在的包,则很容易将库类与应用程序类区分开。 SDK类可以分布在多个DEX中。 因此,对于具有一个或多个DEX文件的应用程序,替换SDK实现的算法不同。
以及用一个DEX替换应用程序中的SDK实现的算法
在单个DEX应用程序中,我们只需在所有应用程序类的顶部复制新的SDK smali类,并生成修改后的DEX。 您可以使用baksmali实用程序生成DEX文件。 带有smali代码的类的包文件的目录将馈送到实用程序输入。 通过baksmali组合了应用程序的smali文件和新的SDK之后,我们得到了具有更改后的SDK逻辑的修改后的DEX文件。
以及用MultiDex替换应用程序中的SDK实现的算法
对于具有MultiDex的应用程序,将单独的SDK添加到新的DEX中,然后从该应用程序的其余DEX文件中删除该SDK的先前版本。 向各个DEX添加新版本的SDK可以避免对DEX格式中方法数量的限制。 如果正确命名,MultiDex将使用SDK代码自动加载添加的DEX文件。 MultiDex使用文件末尾的索引依次搜索DEX文件:首先是dex1,然后是dex2-依此类推,如果使用增量索引命名文件,则MultiDex将自动将其加载到虚拟机中。 因此,通过baksmali,我们将基于先前收到的应用程序smali文件生成DEX文件,但具有旧SDK版本的已删除类。 并使用SDK的修改版本收集其他DEX文件,以增加DEX文件名称中的索引。
使用新版本的SDK重建应用程序
我们获得了带有修改版SDK的应用程序DEX文件。 事情很小:我们将用修改后的DEX文件替换应用程序最初解压缩的APK文件中的DEX文件。 通过调用zip命令,我们可以得到APK的最终版本,该版本仍有待签名。 我们将通过
apksigner使用调试密钥进行
签名,以便可以将应用程序安装在设备上。 具有修改后的SDK逻辑的应用程序已准备就绪。
节省氮
该算法适用于大多数情况,但有时无法替代应用程序中的SDK实现。 原因如下:
- 混淆ProGuard。 库的使用者文件中的规则阻止ProGuard移动SDK类。 但是,如果开发人员取消了这些指令,则部分库类可以更改其程序包。 在这种情况下,该算法将无法工作,因为脚本无法找到旧SDK的类的位置。
- DEX格式限制。 如果应用程序中的所有代码都存储在一个DEX文件中,则几乎完全耗尽了所用方法的限制。 如果将应用程序中的SDK版本替换为带有附加日志记录的版本,则方法数量将增加。 超过限制。 在DEX格式中,限制为2 ^ 16。
- 保护您的应用程序不受更改。 该应用程序具有内置机制来抵抗修改。 例如,通过应用程序签名验证。 通过更改APK,我们相应地更改了其签名。 当应用程序启动时,它将根据引用检查签名并引发异常。 如果将其放置在本机部件中,则删除该检查尤为困难。
和托加斯
我们使用一个简单的bash脚本自动化了本文中描述的步骤。 该脚本有缺陷,但是在将SDK集成到合作伙伴应用程序中时,它可以大大加快对复杂问题的分析。 虽然,我们很少使用这种方法,因为我们经常在早期阶段找到解决方案。
从转换为smali,您可以获得其他好处:smali文件可让您在没有源代码的情况下调试应用程序。 要开始调试,您需要在Android Studio中基于应用程序的smali文件生成一个项目,并将调试器附加到感兴趣的过程。 更多详细信息在
这篇文章中 。