android app進行代碼混淆執行個體詳解
android app進行代碼混淆執行個體詳解
接到一個新的任務,對現有項目進行代碼混淆。之前對混淆有過一些瞭解,但是不夠詳細和完整,知道有些東西混淆起來還是比較棘手的。不過幸好目前的項目不是太複雜(針對混淆這塊來說),提前完成~~現總結之。
第一部分
介紹下操作流程(eclipse):
1、開啟混淆器:找到項目根目錄下的project.properties檔案,將“#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt”這行前的“#”刪除即可;
2、修改混淆設定檔:找到項目根目錄下的proguard-project.txt檔案,修改其中代碼,這部分是最關鍵;
3、儲存相關檔案供以後出錯時使用:主要有匯出的apk檔案、項目根目錄下的proguard目錄下的檔案(主要的是mapping.txt)和項目源碼;
4、項目運行過程出錯處理:根據錯誤資訊和第3步中儲存的mapping定位錯誤位置。
知道這些之後,我們對其進行展開。開啟eclipse然後建立一個項目,預設會建立proguard-project.txt和project.properties。編寫我們的代碼,然後將proguard-project.txt的“#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt”這行前的“#”刪除,最後匯出即可實現對代碼的混淆,即使我們沒有去編寫proguard-project.txt中的內容。下面是我的測試代碼:
?
| 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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
public class MainActivity extends Activity { private String mName; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mName = ttdevs; getString(mName); setName(mName); showDialog(); // testError(); } public String getString(String name) { return hello + name; } public void setName(String name) { System.out.println(I'm + name); } private void showDialog() { new Handler().postDelayed(new Runnable() { @Override public void run() { ScoreAlertDialog.showDialog(MainActivity.this); } }, 2000); } public static class ScoreAlertDialog { public static void showDialog(final Activity activity) { if (activity.isFinishing()) { return; } try { AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(alert_title); builder.setNegativeButton(cancel, null); builder.setPositiveButton(submit, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { try { Toast.makeText(activity, Welcome, Toast.LENGTH_LONG).show(); } catch (Exception e) { e.printStackTrace(); } } }); builder.show(); } catch (Exception e) { e.printStackTrace(); } } } private void testError() { try { int error = 1 / 0; } catch (Exception e) { e.printStackTrace(); } } } |
打包,反編譯,最後我們得到如下的代碼:
分析上面的代碼我們會發現,自訂的方法名都被替換成無特殊意義的短字母,而activity的onCreate()方法卻沒變;最後一個testError()方法由於我們沒有調用也被剔除掉了。這些就是預設的混淆處理策略。看到這裡,感覺混淆還是小case的哈~~
繼續往下,我們將登出的testError()開啟,打包運行這個時候會報錯,錯誤資訊如下:
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
java.lang.ArithmeticException: divide by zero at com.ttdevs.proguard.MainActivity.b(Unknown Source) at com.ttdevs.proguard.MainActivity.onCreate(Unknown Source) at android.app.Activity.performCreate(Activity.java:4531) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1071) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2150) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2229) at android.app.ActivityThread.access$600(ActivityThread.java:139) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1261) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:4945) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:511) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551) at dalvik.system.NativeStart.main(Native Method) |
由於這個例子比較簡單,很容易看出來是何地方出了問題,不過還是可以用來說明我們想表達的問題:如何還原混淆後的代碼的錯誤資訊。為了達到這個目的,我們需要三個檔案:android-sdk-windows oolsproguardin etrace.bat、mapping.txt和上面的錯誤資訊(log.txt)。然後執行下面的命令(window系統):
retrace.bat mapping.txt log.txt
從中可以很清楚的看到錯誤記錄檔中的b()方法為我們實際代碼中的setName()方法。
這裡需要注意的是每次匯出apk都會在項目中目錄下的proguard檔案夾下產生一個對應的mapping檔案,所以對於每個apk我們都需要儲存與之對應的mapping檔案。至此整個混淆的流程介紹完畢。
參考:
官方文檔:http://developer.android.com/tools/help/proguard.html
官方文檔的翻譯:http://www.cnblogs.com/over140/archive/2011/04/22/2024528.html (本想自己去翻一個,結果發現很久以前農民伯伯已經翻譯,在此直接引用並感謝之)
第二部分
第一部分講了如何操作,參照官方文檔,基本都會掌握。剩下的也是最難的就是proguard-project.txt檔案的編寫。對於這部分,兩種處理策略:自己編寫和使用別人寫好的。先說如何使用別人寫好的,我們引用的第三方庫無論開源還是閉源如有特殊情況我們都可以在他的User Guide中找到混淆代碼的配置,如我們引用了大名鼎鼎的guillep PullToRefresh,我們可以在他的文檔中找到如下的代碼:
?
| 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 |
-optimizationpasses 5 -dontusemixedcaseclassnames -dontskipnonpubliclibraryclasses -dontpreverify -verbose -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* -keep public class * extends android.app.Activity -keep public class * extends android.app.Application -keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver -keep public class * extends android.content.ContentProvider -keep public class * extends android.app.backup.BackupAgentHelper -keep public class * extends android.preference.Preference -keep public class com.android.vending.licensing.ILicensingService -keepclasseswithmembernames class * { native <methods>; } -keepclasseswithmembernames class * { public <init>(android.content.Context, android.util.AttributeSet); } -keepclasseswithmembernames class * { public <init>(android.content.Context, android.util.AttributeSet, int); } -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } -keep class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator *; }</init></init></methods> |
有了這部分代碼我們就可以直接copy插入我們的項目中即可。這種方式還是copy式的。那下面我們舉個小例子看看如何自己寫代碼控制是否混淆。還是用第一部分的例子,我們在這個項目的proguard-project.txt檔案中(之前為空白)加入如下幾行(proguard-project.txt中“#”代表注釋):
?
| 1 2 3 4 5 6 |
# -keep public class com.ttdevs.proguard.** { *; } # -keepclasseswithmembers public class com.ttdevs.proguard.** { *; } -keep public class com.ttdevs.proguard.MainActivity { java.lang.String getString(java.lang.String); } |
然後我們在匯出apk然後反編譯,得到如下代碼:
和之前的對比,我們發現其中的getString方法沒有被混淆。沒錯,上面proguard-project.txt的意思就是保持MainActivity的getString()方法不要被混淆。大家也可以試試上述混淆代碼中被注釋的兩行分別是什麼效果。
講到這裡已經開始涉及ProGuard的核心部分了,剩下的就是研讀ProGuard的文檔,掌握的他的文法並使用之。本想找一個完整的ProGuard的翻譯文檔,但是找了N久沒有發現一個,而且連零零散散的翻譯也非常的少,最近時間很緊,加之能力有限,想翻譯一下常用的幾個命令也是很困,所以細讀的想法只能暫時往後推了。這裡先簡單介紹下keep命令:
-keep [,modifier,...] class_specification
在你的代碼中指定作為切入點而被保留的類或者類的成員(屬性和方法)。例如,為了保持一個應用,你可以指定主類和他的main方法。為了處理一個庫,你需要詳細說明他的public訪問的元素。
另外還有keep的簡單概述 和 文法中規範。Class Specification中會告訴你如何表示構造方法,屬性和方法,* 與“**”的區別等等。比如*表示匹配任何的類名但是不包括包的分隔字元,而**則是匹配任何的類名並且包括任意數量的包分隔字元,因此上面我們注釋掉的代碼意思如下:第一行:保持com.ttdevs.proguard下的所有類和子包下的類的所有方法都不混淆,第二行保持com.ttdevs.proguard下的所有類和子包下的類的所有方法和成員變數都不混淆。
// TODO 細節還有很多,比如-libraryjars、-dontwarn、-keepattributes等等,這些待續吧