普遍的,不论大小Android应用都会配置 proguard
在release 编译的时候混淆自己的代码:
1 2 3 4 5 6 7 8
| android { buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
|
但无论 Proguard
还是 R8
,他们的混淆字典默认都太简单了(too simple),只是 abcdefg
而已,反编译后还是很容易阅读的,如下所示:
1 2 3 4 5 6 7 8
| final class b { Object a; aes b; ada c;
b() { } }
|
所幸,Proguard 支持自定义字典:
1 2 3
| -obfuscationdictionary dict.txt -classobfuscationdictionary dict.txt -packageobfuscationdictionary dict.txt
|
如果,有那么一个字典,里面都是形似“乱码”的字符,看起来不仅费眼睛,甚至电脑字体还没收录更佳(会显示成一个方框)。
万能的github 上还真有符合要求的。但是直接生成好的字典文件一直用也是有隐患的,举个例子,两个版本之间类、方法的个数差别不大,最终的混淆结果其实是很相似的,对比 mapping 之后,有可能一个方法前一个版本叫 aa,现在叫 ab 了。
而且翻看了部分实现方案,要么是字典文件里词汇量不够大,要么生成代码实现可能有其它bug。故而干脆自己撸起袖子几行代码搞定。
首先给 app build.gradle
加一个生成字典的任务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| task genDict { outputs.file('build/tmp/dict.txt') doLast { def r = new Random() def start = r.nextInt(1000) + 0x0100 def end = start + 0x4000 def chars = (start..end) .findAll { Character.isValidCodePoint(it) && Character.isJavaIdentifierPart(it) } .collect { String.valueOf(Character.toChars(it)) } int max = chars.size() def startChars = [] def dict = [] for (int i = 0; i < max; i++) { char c = chars.get(i).charAt(0) if (Character.isJavaIdentifierStart(c)) { startChars << String.valueOf(c) } } def startSize = startChars.size() Collections.shuffle(chars, r) Collections.shuffle(startChars, r) for (int i = 0; i < max; i++) { def m = r.nextInt(startSize - 3) def n = m + 3 (m..n).each { j -> dict << (startChars.get(j) + chars.get(i)) } }
def f = outputs.files.getSingleFile() f.getParentFile().mkdirs() f.withWriter("UTF-8") { it.write(startChars.join(System.lineSeparator())) it.write(dict.join(System.lineSeparator())) } } }
|
手动执行./gradlew genDict
,看看字典,都是这种字符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ሩ ꐲ ꊞ 볚 튣 춘 됎 ꉛ چ 뼏 놃 돂 뗪 췻 훐 ꀘ Ī 띪 ꎎ
|
自动生成proguard字典也很简单:
1 2 3 4 5 6 7 8 9
| afterEvaluate { android.applicationVariants.all { variant -> if (variant.name.endsWith('Release')) variant.javaCompileProvider.configure { dependsOn 'genDict' } } }
|
最后别忘了在 proguard rules 里配置字典文件
1 2 3
| -obfuscationdictionary build/tmp/dict.txt -classobfuscationdictionary build/tmp/dict.txt -packageobfuscationdictionary build/tmp/dict.txt
|
混淆后效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Ƞ extends 칁<홳> { public final 驀 치;
public Ƞ(驀 驀) { this.치 = 驀; }
public void 치(ꃑ ꃑ) { }
public void 치(홳 홳) { this.치.逥 = true; } }
|
对了,android gradle plugin 的版本是 3.4.1,gradle 版本 5.1.1,如果你用的和我不一样,前面所提到的代码(genDict)可能需要稍作修改。