补齐Android技能树 - 从害怕到玩转Android代码混淆(下)

简介: 本节稍微深入点探索下Android中的代码混淆~

0x6、R8又干了啥?


不知道,细心你的有没有发现,D8竟在r8的包里:


网络异常,图片无法展示
|


接着在 TaskManager.java 搜下D8,经过各种跳转,来到源头:createPostCompilationTasks(),可以看到在创建D8相关的Transform前还做了一些其他的操作~


网络异常,图片无法展示
|


① R8 - 执行前


从注释不难看出,这些Tasks就是用来把.class转dex文件的,还加了一些可选步骤如混淆、jacoco(代码覆盖率工具),还创建了一个 TransformManager 实例,用来管理各种TransformManager。继续:


网络异常,图片无法展示
|


这里引起了我的好奇,可能创建脱糖Task?脱糖不是在D8里进行的吗?跟一下代码:


网络异常,图片无法展示
|


网络异常,图片无法展示
|


这里的DESUGAR,猜测是旧版本的D8兼容,AS 3.0引入的,而现在默认是D8,所以这里其实不会创建脱糖Task。继续:


网络异常,图片无法展示
|


获取外部扩展,合并Java资源,对合并算法感兴趣的可以点进去 MergeJavaResourcesTransform.transform() 看下,这里不展开讲~


网络异常,图片无法展示
|


继续往下走:


网络异常,图片无法展示
|


② R8 - Java代码压缩


再往下走,就碰到关键词R8了:


网络异常,图片无法展示
|


这里有下述三个maybe:


  • maybeCreateJavaCodeShrinkerTransform → Java代码压缩
  • maybeCreateResourcesShrinkerTransform → 资源压缩
  • maybeCreateDexSplitterTransform → dex分割


先看第一个:


网络异常,图片无法展示
|


网络异常,图片无法展示
|


这里区分PROGUARD和R8,创建不同的混淆TransformTask,关注 createR8Transform() 核心代码如下:


// 前面获取dex的文件列表、混淆列表等,初始化R8Transform实例时传入
R8Transform transform =
        new R8Transform(
                variantScope,
                userMainDexListFiles,
                userMainDexListProguardRules,
                inputProguardMapping,
                variantScope.getOutputProguardMappingFile());
// 处理混淆规则,callback用于在混淆后执行后续操作
return applyProguardRules(
        variantScope,
        inputProguardMapping,
        variantScope.getOutputProguardMappingFile(),
        testedVariantData,
        transform,
        callback);


跟下 applyProguardRules(),关注下述核心代码(前面的是和测试相关的):


// This is a "normal" variant in an app/library.
applyProguardConfigForNonTest(transform, variantScope);


跟下此方法:


网络异常,图片无法展示
|


补充其他混淆规则的来源,如AAPT生成的混淆文件,判断如果是AAR的话,直接keep。


网络异常,图片无法展示
|


跟下 R8Transform.transform(),又是参数,最后调用下述方法:


网络异常,图片无法展示
|


最后在 r8Tool.kt 中定位到了此方法,核心代码如下:


// 初始化了一个r8CommandBuilder实例
val r8CommandBuilder = CompatProguardCommandBuilder(!useFullR8, D8DiagnosticsHandler(messageReceiver))
// 然后调用一系列方法,如混淆相关
addMainDexRules()
setMainDexListConsumer()
addProguardConfigurationFiles()
addProguardConfiguration()
setProguardMapOutputPath()
// 配置相关:是否禁用缩小、摇树、脱糖、编译模式
setDisableMinification(toolConfig.disableMinification)
setDisableTreeShaking(toolConfig.disableTreeShaking)
setDisableDesugaring(toolConfig.disableDesugaring)
setMode(compilationMode)
setProgramConsumer(programConsumer)
...
// 初始化r8ProgramResourceProvider实例,用来给R8提供所有资源
val r8ProgramResourceProvider = R8ProgramResourceProvider()
// 各种传参设置
// 最后调用R8.run()
ClassFileProviderFactory(libraries).use { libClasspath ->
    r8CommandBuilder.addLibraryResourceProvider(libClasspath.orderedProvider)
    R8.run(r8CommandBuilder.build())
}


具体的逻辑,可以追溯到 R8.class → run(),做了这些事:


  • 代码删除:通过语法树静态分析技术、发现并删除未使用的代码,如未实例化的Class等;
  • 代码优化:对运行时代码进行优化,删除死代码、未使用的参数,选择性内联、类合并等;
  • 代码混淆:优化标识符名字,减少代码量,会判断混淆规则中是否允许修改标识符名字;
  • 行号重新映射 等。


③ R8 - 资源压缩


接着跟下第二个 maybeCreateResourcesShrinkerTransform()


网络异常,图片无法展示
|


跟下 ShrinkResourcesTransform.transform()


网络异常,图片无法展示
|


跟下 SplitterRunnable.run(),看下具体是怎么压缩资源的,核心代码如下:


// ① 创建资源优化记录文件
File reportFile = null;
if (params.mappingFile != null) {
    File logDir = params.mappingFile.getParentFile();
    if (logDir != null) {
        reportFile = new File(logDir, "resources.txt");
    }
}
// ② 分析资源及使用情况
analyzer.analyze();
// ③ 重写.ap_文件(上面AAPT2生成的),去掉没有用到的资源,实际上是没有删除本地资源的!
analyzer.rewriteResourceZip(params.uncompressedResourceFile, params.compressedResourceFile);
// ③ 导出统计数据


解压AAPT生成的.ap_文件,然后判断是是否为未使用资源,是的话移除,感兴趣可以跟下 rewriteResourceZip()


④ R8 - 调D8拆Dex


只有使用了 Multidex 才会走这里,跟下代码:


网络异常,图片无法展示
|


跟下 DexSplitterTransform.transform()


网络异常,图片无法展示
|


跟下 DexSplitterTool.Builder


网络异常,图片无法展示
|


跟下 DexSplitter.run()


网络异常,图片无法展示
|


跟下 DexSplitterHelper.run()


网络异常,图片无法展示
|


所以最后还是用的D8打Dex,总算把大概的过程过完了~


0x7、自定义混淆字典


之前在反编译人家的APP时看到标识符竟然不是abcd,而是中文和特殊字符,怎么做到的呢?其实不难,自定义一个混淆字典就好,在app的proguard-rules的同级目录创建一个文件,比如 dictionary,内容示例如下:


×
÷
...太长省略


接着在 proguard-rules 添加下述配置:


-obfuscationdictionary ./dictionary
-classobfuscationdictionary ./dictionary
-packageobfuscationdictionary ./dictionary


再接着 gradle assemble 打下包,用jadx打开生成的Release APK康康:


网络异常,图片无法展示
|


0x8、模块化混淆


混淆的开启由app模块控制,与子模块无关


建议 在app模块设置公共混淆规则,子模块设置专属混淆规则,子模块区分project和aar:


# Project类型,配置方法同app模块
buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}
# AAR类型
android {
    defaultConfig {
        consumerProguardFiles 'lib-proguard-rules.txt'
    }
    ...
}


当然,你想让混淆规则都由app模块控制也是可以的,移除模块时记得删掉对应的混淆就好~


小结


(gàn),看源码到吐血,Gradle相关的东西真的是无底洞啊,学完一个又一个,还有著名的ASM字节码插桩,基于APK构建过程Task的HOOK的各种开源性能优化/检测工具等。


参考文献:



相关文章
|
25天前
|
安全 Java 网络安全
Android远程连接和登录FTPS服务代码(commons.net库)
Android远程连接和登录FTPS服务代码(commons.net库)
22 1
|
1月前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异:从代码到用户体验
【10月更文挑战第5天】在移动应用开发的广阔天地中,安卓和iOS两大平台各占半壁江山。它们在技术架构、开发环境及用户体验上有着根本的不同。本文通过比较这两种平台的开发过程,揭示背后的设计理念和技术选择如何影响最终产品。我们将深入探讨各自平台的代码示例,理解开发者面临的挑战,以及这些差异如何塑造用户的日常体验。
|
2月前
|
存储 Java Android开发
🔥Android开发大神揭秘:从菜鸟到高手,你的代码为何总是慢人一步?💻
在Android开发中,每位开发者都渴望应用响应迅速、体验流畅。然而,代码执行缓慢却是常见问题。本文将跟随一位大神的脚步,剖析三大典型案例:主线程阻塞导致卡顿、内存泄漏引发性能下降及不合理布局引起的渲染问题,并提供优化方案。通过学习这些技巧,你将能够显著提升应用性能,从新手蜕变为高手。
28 2
|
3月前
|
JSON JavaScript 前端开发
Android调用Vue中的JavaScript代码
Android调用Vue中的JavaScript代码
35 3
|
3月前
|
安全 Java 网络安全
Android远程连接和登录FTPS服务代码(commons.net库)
很多文章都介绍了FTPClient如何连接ftp服务器,但却很少有人说如何连接一台开了SSL认证的ftp服务器,现在代码来了。
100 2
|
4月前
|
存储 Java Android开发
🔥Android开发大神揭秘:从菜鸟到高手,你的代码为何总是慢人一步?💻
【7月更文挑战第28天】在Android开发中,每位开发者都追求极致的用户体验。然而,“代码执行慢”的问题时常困扰着开发者。通过案例分析,我们可探索从新手到高手的成长路径。
39 3
|
3月前
|
Java Android开发
Android项目架构设计问题之要提升代码的可读性和管理性如何解决
Android项目架构设计问题之要提升代码的可读性和管理性如何解决
38 0
|
4月前
|
API Android开发
Android 监听Notification 被清除实例代码
Android 监听Notification 被清除实例代码
|
5月前
|
JavaScript 前端开发 Android开发
kotlin安卓在Jetpack Compose 框架下使用webview , 网页中的JavaScript代码如何与native交互
在Jetpack Compose中使用Kotlin创建Webview组件,设置JavaScript交互:`@Composable`函数`ComposableWebView`加载网页并启用JavaScript。通过`addJavascriptInterface`添加`WebAppInterface`类,允许JavaScript调用Android方法如播放音频。当页面加载完成时,执行`onWebViewReady`回调。
|
4月前
|
Web App开发 JavaScript 前端开发
Android端使用WebView注入一段js代码实现js调用android
Android端使用WebView注入一段js代码实现js调用android
108 0