专栏名称: 郭霖
Android技术分享平台,每天都有优质技术文章推送。你还可以向公众号投稿,将自己总结的技术心得分享给大家。
目录
相关文章推荐
鸿洋  ·  一波深入的Android 性能优化 ·  昨天  
郭霖  ·  activityGuard:Android ... ·  2 天前  
鸿洋  ·  Android anr排查之sp卡顿 ·  3 天前  
郭霖  ·  Android 16首个开发者预览版到来 ·  4 天前  
鸿洋  ·  再学安卓 - init进程 ·  4 天前  
51好读  ›  专栏  ›  郭霖

activityGuard:Android 四大组件混淆

郭霖  · 公众号  · android  · 2024-12-25 08:00

正文



/   今日科技快讯   /

今年早些时候在反垄断审判中战胜谷歌后,美国司法部(DOJ)近期提出了一系列针对谷歌搜索业务的全面改革方案。司法部要求谷歌出售其 Chrome 浏览器,与其他搜索引擎共享搜索结果,并避免与苹果等公司达成默认搜索位置的独家协议。司法部甚至保留了强制出售安卓操作系统的可能性。

/   作者简介   /

本篇文章来自denglongfei的投稿,文章主要分享了如何对组件,自定义view等进行混淆,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章。

denglongfei的博客地址:

https://juejin.cn/user/4265760848352311/posts

/   前言   /

activityGuard是一种针对四大组件进行混淆的解决方案,能够在打包时对apk和aab中的Activity、Service、Application和自定义的view进行名称混淆以提升应用的安全性。

/   目的   /

  • 防止逆向:Android四大组件的类名直接暴露在AndroidManifest.xml和代码中,容易被反编译后根据名称了解应用逻辑。
  • 增强安全性:混淆名称增加了攻击者定位关键组件的难度,降低被针对性攻击的风险。
  • 马甲包:降低aab包查重率,避免上架Google Play因查重率过高,导致下架或封号问题

/   原理分析   /

Android 四大组件在打包过程中不能够R8被混淆,因为组件在AndroidManifest.xml以明文形式存在Android系统通过反射创建相关类来启动。所以我们需要在R8执行前修改AndroidManifest.xml和layout布局中的类名,并把新的名称keep的R8的混淆规则中(R8混淆执行时是以keep住的类为节点,如果没引用的类会被移除掉)。最后在通过asm字节码修改类名,就能够实现对四大组件和自定义view实现混淆名称了。

activityGuard通过自定义Gradle任务在打包过程中修改替换AndroidManifest.xml和layout中的类名和class的类名来实现对Android四大组件的混淆。

AAB混淆

aab打包混淆流程图如下:


通过分析aab的打包流程可以发现最终输入的资源文件是linked_res_for_bundle和R8执行后的DEX。

通过自定义activityGuardBundleResTask修改资源和activityGuardClassTask修改类来实现对aab的混淆。

activityGuardBundleResTask任务

activityGuardBundleResTask主要是通过插入到bundleReleaseResources任务后面对aapt_rules.txt和linked_res_for_bundle的修改替换,并生成混淆map文件用于后面类名修改。

aapt_rules.txt是由aapt2 link资源后生成的ProGuard规则文件,里面会keep在AndroidManifest.xml和layout布局中用到到类作为R8混淆的根节点。


bundleReleaseResources生成的文件为bundled-res.ap_,本质是只包含资源的aab文件。也是最后aab打包的资源文件。



保存aapt_rules.txt的中的类名,然后遍历修改bundled-res.ap_中的AndroidManifest和布局xml的类名。

  val bundleZip = ZipFile(bundleResFiles.get().asFile)
        bundleZip.entries().asSequence().forEach { zipEntry ->
            val path = zipEntry.name
            when {
                path == "resources.pb" -> {
                    val resourceTableByte = readByte(bundleZip, path)
                    createDirAndFile(dirName, path).outputStream().use { out ->
                        out.write(resourceTableByte)
                    }
                }

                path.startsWith("res/layout") -> {
                    val xmlNode = changeLayoutXmlName(bundleZip, path.toString(), classMapping)
                    createDirAndFile(dirName, path.toString()).outputStream()
                        .use { xmlNode.writeTo(it) }
                }

                path == "AndroidManifest.xml" -> {
                    val xmlNode = Resources.XmlNode.parseFrom(readByte(bundleZip, path))
                    var newXmlNode = xmlNode
                    mapOf(
                        "activity" to "name",
                        "service" to "name",
                        "application" to "name",
                        "provider" to "name",
                    ).forEach {
                        newXmlNode =
                            changeXmlNodeAttribute(newXmlNode, it.key, it.value, classMapping)
                    }
                    createDirAndFile(dirName, path).outputStream()
                        .use { newXmlNode.writeTo(it) }
                }

                else -> {
                    createDirAndFile(dirName, path.toString()).outputStream().use { out ->
                        out.write(readByte(bundleZip, path.toString()))
                    }
                }
            }
        }


bundled-res.ap_修改后效果。


最后根据使用到的类名然后替换修改存aapt_rules.txt文件。最终hook混淆修改aab组件名称和自定义view名称。


activityGuardClassTask

activityGuardClassTask主要是根据activityGuardBundleResTask生成的map,通过asm字节码修改项目中的类名和所有引用到的地方。最后在把修改的class传递给R8任务执行。

val jarFile = JarFile(inputJar)
        jarFile.entries().iterator().forEach { jarEntry ->
            val entryName = jarEntry.name
            if (entryName.endsWith(".class")) {
                jarFile.getInputStream(jarEntry).use {
                    // 对类文件应用 ASM 处理
                    val classReader = ClassReader(it)
                    val classWriter = ClassWriter(ClassWriter.COMPUTE_MAXS)
                    val classVisitor = ClassNameClassVisitor(
                        Opcodes.ASM9,
                        classWriter,
                        ObfuscatorMapping(classMapping.get())
                    )
                    classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES)
                    jarOutput.writeEntity(jarEntry.name, classWriter.toByteArray())
                }
            } else {
                // 非类文件直接复制
                jarFile.getInputStream(jarEntry).use { inputStream ->
                    jarOutput.writeEntity(jarEntry.name, inputStream)
                }

            }
        }
        jarFile.close()


Apk混淆

apk打包混淆流程图如下:


当打包为apk时,主要流程在于是否配置了splits。


当没有配置时,最后打包成apk的资源使用了bundleReleaseResources生成的文件为bundled-res.ap_,和打包aab使用的相同资源,而自定义activityGuardBundleResTask任务已经进行了修改替换。

当配置了splits时,打包为apk会直接使用processReleaseResources生成的.ap_。


.ap文件本质是只有资源的apk文件。


通过自定义activityGuardApkResTask来修改apk资源文件中xml布局和AndroidManifest的类名。

activityGuardApkResTask任务

activityGuardApkResTask主要是根据生成的混淆map文件修改apk资源包xml中的类名。

 //apk to bundle
     getAaptDaemon(aapt2ServiceKey).use {
            it.convert(
                AaptConvertConfig(
                    inputFile = inputApkFile.asFile,
                    outputFile = temProtoFile.asFile,
                    convertToProtos = true
                ),
                LoggerWrapper(logger)
            )
        }
        //修改bundle
        obfuscatorRes(temProtoFile.asFile, classMapping.mapValues {
            ClassInfo(it.value, true)
        })
        //bundle to apk
        getAaptDaemon(aapt2ServiceKey).use {
            it.convert(
                AaptConvertConfig(
                    inputFile = temProtoFile.asFile,
                    outputFile = outputApkFile.asFile,
                    convertToProtos = false
                ),
                LoggerWrapper(logger)
            )
        }


因为activityGuardBundleResTask任务已经有修改aab中xml类名的方法,所有修改apk资源时我们先使用aapt2把apk资源转成aab资源,然后修改后在转回apk资源,最后替换processReleaseResources任务生成文件,从而修改混淆资源中的类名称。


/   使用说明   /

插件基于Gradle8.0,并且因为基于aapt2生成的aapt_rules.txt来混淆类名,所以项目需要开启  isMinifyEnabled = true。

每次混淆会在当前项目下生成对应的mapping.txt记录对应混淆类,插件默认会根据mapping.txt文件增量混淆名称,所以当需要不同混淆名时,可以删除mapping.txt文件或者自己实现对应生成规则方法(自己生成时记得确保唯一性)。

 buildscript {
    repositories {
        maven { url 'https://jitpack.io' }
    }
    dependencies {
        classpath "com.github.denglongfei:activityGuard:1.0.0"
    }
}


配置

 plugins {
    id("activityGuard")
}
//以下均为非必须
actGuard {
    //是否开启
    isEnable = true
    //不需要混淆的类
    whiteClassList = hashSetOf(
        "com.activityGuard.confuseapp.MainActivity1",
        "*.MainActivity2",
    )
    //自己实现混淆类名时
//    //目录混淆
//    obfuscatorDirFunction={ dirName->
//        dirName
//    }
//    //类名混淆
//    obfuscatorClassFunction= { className, dirName->
//        className
//    }
}


/   最终效果   /

apk

 

aab



推荐阅读:
我的新书,《第一行代码 第3版》已出版!
这可能是Android软键盘监听的最佳方案
Android 16首个开发者预览版到来

欢迎关注我的公众号
学习技术或投稿


长按上图,识别图中二维码即可关注