activityGuard:Android Activity四大组件混淆

1,755 阅读4分钟

activityGuard:Android activityObfuscator

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

Github 项目地址github.com/denglongfei…

目的

  • 防止逆向: 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.png

通过分析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混淆的根节点。

1734533904851.jpg

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

1734533827680.jpg

image.png

保存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_修改后效果

image.png

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

image.png

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.png

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

image.png

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

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

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

image.png

通过自定义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任务生成文件,从而修改混淆资源中的类名称。

image.png

使用说明

插件基于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

image.png image.png

aab

image.png

image.png