[英]How to obfuscate with ProGuard but keep names readable while testing?
我的應用程序處於預發布階段,我開始編譯發布版本assembleRelease
而不是assembleDebug
。 然而,混淆會破壞事物,並且很難破譯什么是什么。 調試幾乎是不可能的,即使保留了行號,變量類也是不可讀的。 雖然發布版本不穩定,但我想讓混淆不那么痛苦,但它仍然應該表現得像完全混淆一樣。
通常 ProGuarded 版本將名稱從
net.twisterrob.app.pack.MyClass
到
b.a.c.b.a
如果反射和 Android 布局/菜單資源遇到我們沒有保留名稱的類,它們可能會中斷。
能夠混淆代碼對預發布測試真的很有幫助,但“沒有那么多” ,比如從
net.twisterrob.app.pack.MyClass
到
net.twisterrob.app.pack.myclass // or n.t.a.p.MC or anything in between :)
proguard -dontobfuscate
當然有幫助,但它會使所有損壞的東西再次工作,因為類名是正確的。
我正在尋找的東西將打破完全混淆會破壞的東西,但同時很容易弄清楚不使用 mapping.txt 是什么,因為名稱保持人類可讀。
我環顧四周http://proguard.sourceforge.net/manual/usage.html#obfuscationoptions但-*dictionary
選項似乎沒有這樣做。
我可以自己生成一個重命名文件(它只是運行所有類並給它們一個toLowerCase
或其他東西):
net.twisterrob.app.pack.MyClassA -> myclassa
net.twisterrob.app.pack.MyClassB -> myclassb
問題是我如何將這樣的文件提供給 ProGuard 以及格式是什么?
所以看起來我已經設法跳過了我鏈接的部分中的選項-applymapping
。
跳轉到實現/詳細信息部分並將這兩個 Gradle/Groovy 代碼塊復制到您的 Android 子項目的build.gradle
文件中。
mapping.txt 的格式非常簡單:
full.pack.age.Class -> obf.usc.ate.d:
Type mField -> mObfusc
#:#:Type method(Arg,s) -> methObfusc
kept.Class -> kept.Class:
Type mKept -> mKept
#:#:Type kept() -> kept
收縮的類和成員根本沒有列出。 所以所有可用的信息,如果我可以生成相同的信息或轉換它,那么成功的機會就很大。
我試圖根據傳遞給 proguard ( -injars
) 的當前類路徑生成輸入 mapping.txt 。 我在URLClassLoader
加載了所有類,其中包含所有程序 jar 和 libraryjar(例如解析超類)。 然后遍歷每個類和每個聲明的成員並輸出我希望使用的名稱。
這有一個大問題:這個解決方案包含應用程序中每個可重命名的東西的混淆名稱。 這里的問題是-applymapping
從字面上-applymapping
並嘗試應用輸入映射文件中的所有映射,忽略-keep
規則,導致有關重命名沖突的警告。 所以我放棄了這條路,因為我不想復制proguard config,也不想自己實現proguard config parser。
proguardRelease
兩次 [失敗]基於上述失敗,我想到了另一種解決方案,它可以利用所有配置並保持存在。 流程如下:
proguardRelease
完成它的工作mapping.txt
mapping.txt
轉換為新文件proguardRelease
gradle 任務並使用轉換后的映射運行它問題是復制整個任務真的很復雜,包括inputs
、 outputs
、 doLast
、 doFirst
、 @TaskAction
等......無論如何我實際上是從這條路線開始的,但它很快就加入了第三個解決方案。
proguardRelease
的輸出 [成功] 在嘗試復制整個任務並分析 proguard/android 插件代碼時,我意識到再次模擬proguardRelease
正在做的事情會容易得多。 這是最終的流程:
proguardRelease
完成它的工作mapping.txt
mapping.txt
轉換為新文件結果是我想要的:
(例如模式是<package>.__<class>__.__<field>__
,類和字段名稱倒置)
java.lang.NullPointerException: Cannot find actionView! Is it declared in XML and kept in proguard?
at net.twisterrob.android.utils.tools.__aNDROIDtOOLS__.__PREPAREsEARCH__(AndroidTools.java:533)
at net.twisterrob.inventory.android.activity.MainActivity.onCreateOptionsMenu(MainActivity.java:181)
at android.app.Activity.onCreatePanelMenu(Activity.java:2625)
at android.support.v4.app.__fRAGMENTaCTIVITY__.onCreatePanelMenu(FragmentActivity.java:277)
at android.support.v7.internal.view.__wINDOWcALLBACKwRAPPER__.onCreatePanelMenu(WindowCallbackWrapper.java:84)
at android.support.v7.app.__aPPcOMPATdELEGATEiMPLbASE$aPPcOMPATwINDOWcALLBACK__.onCreatePanelMenu(AppCompatDelegateImplBase.java:251)
at android.support.v7.app.__aPPcOMPATdELEGATEiMPLv7__.__PREPAREpANEL__(AppCompatDelegateImplV7.java:1089)
at android.support.v7.app.__aPPcOMPATdELEGATEiMPLv7__.__DOiNVALIDATEpANELmENU__(AppCompatDelegateImplV7.java:1374)
at android.support.v7.app.__aPPcOMPATdELEGATEiMPLv7__.__ACCESS$100__(AppCompatDelegateImplV7.java:89)
at android.support.v7.app.__aPPcOMPATdELEGATEiMPLv7$1__.run(AppCompatDelegateImplV7.java:123)
at android.os.Handler.handleCallback(Handler.java:733)
或者注意這里的下划線:
我試圖讓它盡可能簡單,同時保持最大的靈活性。 我稱之為 unfuscation,因為它正在取消適當的混淆,但仍然被認為是在例如反射方面的混淆。
我實施了一些守衛,因為第二輪做了一些假設。 顯然,如果沒有混淆,就不需要 unfuscated。 此外,如果關閉調試,則 unfuscate(並且可能會意外釋放)幾乎毫無意義,因為 unfuscation 在 IDE 中最有幫助。 如果應用程序經過測試和混淆,則 AndroidProguardTask 的內部正在使用映射文件,我現在不想處理它。
所以我繼續創建了一個 unfuscate 任務,它進行轉換並運行 proguard。 可悲的是 proguard 配置沒有暴露在proguard.gradle.ProguardTask
,但是什么時候阻止了任何人?! :)
有一個缺點,它需要雙倍的時間來保護它,如果你真的需要調試它,我想這是值得的。
這是 Gradle 的 android 掛鈎代碼:
afterEvaluate {
project.android.applicationVariants.all { com.android.build.gradle.api.ApplicationVariant variant ->
Task obfuscateTask = variant.obfuscation
def skipReason = [ ];
if (obfuscateTask == null) { skipReason += "not obfuscated" }
if (!variant.buildType.debuggable) { skipReason += "not debuggable" }
if (variant.testVariant != null) { skipReason += "tested" }
if (!skipReason.isEmpty()) {
logger.info("Skipping unfuscation of {} because it is {}", variant.name, skipReason);
return;
}
File mapping = variant.mappingFile
File newMapping = new File(mapping.parentFile, "unmapping.txt")
Task unfuscateTask = project.task("${obfuscateTask.name}Unfuscate") {
inputs.file mapping
outputs.file newMapping
outputs.upToDateWhen { mapping.lastModified() <= newMapping.lastModified() }
doLast {
java.lang.reflect.Field configField =
proguard.gradle.ProGuardTask.class.getDeclaredField("configuration")
configField.accessible = true
proguard.Configuration config = configField.get(obfuscateTask) as proguard.Configuration
if (!config.obfuscate) return; // nothing to unfuscate when -dontobfuscate
java.nio.file.Files.copy(mapping.toPath(), new File(mapping.parentFile, "mapping.txt.bck").toPath(),
java.nio.file.StandardCopyOption.REPLACE_EXISTING)
logger.info("Writing new mapping file: {}", newMapping)
new Mapping(mapping).remap(newMapping)
logger.info("Re-executing {} with new mapping...", obfuscateTask.name)
config.applyMapping = newMapping // use our re-written mapping file
//config.note = [ '**' ] // -dontnote **, it was noted in the first run
LoggingManager loggingManager = getLogging();
// lower level of logging to prevent duplicate output
loggingManager.captureStandardOutput(LogLevel.WARN);
loggingManager.captureStandardError(LogLevel.WARN);
new proguard.ProGuard(config).execute();
}
}
unfuscateTask.dependsOn obfuscateTask
variant.dex.dependsOn unfuscateTask
}
}
整體的另一部分是轉化。 我設法快速編寫了一個全匹配的正則表達式模式,所以它非常簡單。 您可以放心地忽略類結構和重新映射方法。 關鍵是processLine
,它為每一行調用。 該行被拆分為部分,混淆名稱前后的文本保持原樣(兩個substring
),名稱在中間更改。 更改為unfuscate
return
語句以滿足您的需要。
class Mapping {
private static java.util.regex.Pattern MAPPING_PATTERN =
~/^(?<member> )?(?<location>\d+:\d+:)?(?:(?<type>.*?) )?(?<name>.*?)(?:\((?<args>.*?)\))?(?: -> )(?<obfuscated>.*?)(?<class>:?)$/;
private static int MAPPING_PATTERN_OBFUSCATED_INDEX = 6;
private final File source
public Mapping(File source) {
this.source = source
}
public void remap(File target) {
target.withWriter { source.eachLine Mapping.&processLine.curry(it) }
}
private static void processLine(Writer out, String line, int num) {
java.util.regex.Matcher m = MAPPING_PATTERN.matcher(line)
if (!m.find()) {
throw new IllegalArgumentException("Line #${num} is not recognized: ${line}")
}
try {
def originalName = m.group("name")
def obfuscatedName = m.group("obfuscated")
def newName = originalName.equals(obfuscatedName) ? obfuscatedName : unfuscate(originalName, obfuscatedName)
out.write(line.substring(0, m.start(MAPPING_PATTERN_OBFUSCATED_INDEX)))
out.write(newName)
out.write(line.substring(m.end(MAPPING_PATTERN_OBFUSCATED_INDEX)))
out.write('\n')
} catch (Exception ex) {
StringBuilder sb = new StringBuilder("Line #${num} failed: ${line}\n");
0.upto(m.groupCount()) { sb.append("Group #${it}: '${m.group(it)}'\n") }
throw new IllegalArgumentException(sb.toString(), ex)
}
}
private static String unfuscate(String name, String obfuscated) {
int lastDot = name.lastIndexOf('.') + 1;
String pkgWithDot = 0 < lastDot ? name.substring(0, lastDot) : "";
name = 0 < lastDot ? name.substring(lastDot) : name;
// reassemble the names with something readable, but still breaking changes
// pkgWithDot will be empty for fields and methods
return pkgWithDot + '_' + name;
}
}
您應該能夠對包名稱應用轉換,但我沒有對此進行測試。
// android.support.v4.a.a, that is the original obfuscated one
return obfuscated;
// android.support.v4.app._Fragment
return pkgWithDot + '_' + name;
// android.support.v4.app.Fragment_a17d4670
return pkgWithDot + name + '_' + Integer.toHexString(name.hashCode());
// android.support.v4.app.Fragment_a
return pkgWithDot + name + '_' + afterLastDot(obfuscated)
// android.support.v4.app.fRAGMENT
return pkgWithDot + org.apache.commons.lang.StringUtils.swapCase(name);
// needs the following in build.gradle:
buildscript {
repositories { jcenter() }
dependencies { classpath 'commons-lang:commons-lang:2.6' }
}
// android.support.v4.app.fragment
return pkgWithDot + name.toLowerCase();
警告:不可逆的轉換容易出錯。 考慮以下:
class X {
private static final Factory FACTORY = ...;
...
public interface Factory {
}
}
// notice how both `X.Factory` and `X.FACTORY` become `X.factory` which is not allowed.
當然,上述所有轉換都可以以一種或另一種方式被欺騙,但不太可能使用不常見的前置后綴和文本轉換。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.