今年早些时候在反垄断审判中战胜谷歌后,美国司法部(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的打包流程可以发现最终输入的资源文件是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()))
}
}
}
}
最后根据使用到的类名然后替换修改存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时,主要流程在于是否配置了splits。当没有配置时,最后打包成apk的资源使用了bundleReleaseResources生成的文件为bundled-res.ap_,和打包aab使用的相同资源,而自定义activityGuardBundleResTask任务已经进行了修改替换。当配置了splits时,打包为apk会直接使用processReleaseResources生成的.ap_。通过自定义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
// }
}