編輯:關於Android編程
在上一篇文章當中,我們學習了Android程序反編譯方面的知識,包括反編譯代碼、反編譯資源、以及重新打包等內容。通過這些內容我們也能看出來,其實我們的程序並沒有那麼的安全。可能資源被反編譯影響還不是很大,重新打包又由於有簽名的保護導致很難被盜版,但代碼被反編譯就有可能會洩漏核心技術了,因此一款安全性高的程序最起碼要做到的一件事就是:對代碼進行混淆。
混淆代碼並不是讓代碼無法被反編譯,而是將代碼中的類、方法、變量等信息進行重命名,把它們改成一些毫無意義的名字。因為對於我們而言可能Cellphone類的call()方法意味著很多信息,而A類的b()方法則沒有任何意義,但是對於計算機而言,它們都是平等的,計算機不會試圖去理解Cellphone是什麼意思,它只會按照設定好的邏輯來去執行這些代碼。所以說混淆代碼可以在不影響程序正常運行的前提下讓破解者很頭疼,從而大大提升了程序的安全性。
今天是我們Android安全攻防戰系列的下篇,本篇文章的內容建立在上篇的基礎之上,還沒有閱讀過的朋友可以先去參考 Android安全攻防戰,反編譯與混淆技術完全解析(上) 。
本篇文章中介紹的混淆技術都是基於Android Studio的,Eclipse的用法也基本類似,但是就不再為Eclipse專門做講解了。
我們要建立一個Android Studio項目,並在項目中添加一些能夠幫助我們理解混淆知識的代碼。這裡我准備好了一些,我們將它們添加到Android Studio當中。
首先新建一個MyFragment類,代碼如下所示:
public class MyFragment extends Fragment {
private String toastTip = "toast in MyFragment";
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_layout, container, false);
methodWithGlobalVariable();
methodWithLocalVariable();
return view;
}
public void methodWithGlobalVariable() {
Toast.makeText(getActivity(), toastTip, Toast.LENGTH_SHORT).show();
}
public void methodWithLocalVariable() {
String logMessage = "log in MyFragment";
logMessage = logMessage.toLowerCase();
System.out.println(logMessage);
}
}
可以看到,MyFragment是繼承自Fragment的,並且MyFragment中有一個全局變量。onCreateView()方法是Fragment的生命周期函數,這個不用多說,在onCreateView()方法中又調用了methodWithGlobalVariable()和methodWithLocalVariable()方法,這兩個方法的內部分別引用了一個全局變量和一個局部變量。
接下來新建一個Utils類,代碼如下所示:
public class Utils {
public void methodNormal() {
String logMessage = "this is normal method";
logMessage = logMessage.toLowerCase();
System.out.println(logMessage);
}
public void methodUnused() {
String logMessage = "this is unused method";
logMessage = logMessage.toLowerCase();
System.out.println(logMessage);
}
}
這是一個非常普通的工具類,沒有任何繼承關系。Utils中有兩個方法methodNormal()和methodUnused(),它們的內部邏輯都是一樣的,唯一的據別是稍後methodNormal()方法會被調用,而methodUnused()方法不會被調用。
下面再新建一個NativeUtils類,代碼如下所示:
public class NativeUtils {
public static native void methodNative();
public static void methodNotNative() {
String logMessage = "this is not native method";
logMessage = logMessage.toLowerCase();
System.out.println(logMessage);
}
}
這個類中同樣有兩個方法,一個是native方法,一個是非native方法。
最後,修改MainActivity中的代碼,如下所示:
public class MainActivity extends AppCompatActivity {
private String toastTip = "toast in MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction().add(R.id.fragment, new MyFragment()).commit();
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
methodWithGlobalVariable();
methodWithLocalVariable();
Utils utils = new Utils();
utils.methodNormal();
NativeUtils.methodNative();
NativeUtils.methodNotNative();
Connector.getDatabase();
}
});
}
public void methodWithGlobalVariable() {
Toast.makeText(MainActivity.this, toastTip, Toast.LENGTH_SHORT).show();
}
public void methodWithLocalVariable() {
String logMessage = "log in MainActivity";
logMessage = logMessage.toLowerCase();
System.out.println(logMessage);
}
}
可以看到,MainActivity和MyFragment類似,也是定義了methodWithGlobalVariable()和methodWithLocalVariable()這兩個方法,然後MainActivity對MyFragment進行了添加,並在Button的點擊事件裡面調用了自身的、Utils的、以及NativeUtils中的方法。注意調用native方法需要有相應的so庫實現,不然的話就會報UnsatisefiedLinkError,不過這裡其實我也並沒有真正的so庫實現,只是演示一下讓大家看看混淆結果。點擊事件的最後一行調用的是LitePal中的方法,因為我們還要測試一下引用第三方Jar包的場景,到LitePal項目的主頁去下載最新的Jar包,然後放到libs目錄下即可。
完整的build.gradle內容如下所示:
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
defaultConfig {
applicationId "com.example.guolin.androidtest"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.2.0'
}
好的,到這裡准備工作就已經基本完成了,接下來我們就開始對代碼進行混淆吧。
在Android Studio當中混淆APK實在是太簡單了,借助SDK中自帶的Proguard工具,只需要修改build.gradle中的一行配置即可。可以看到,現在build.gradle中minifyEnabled的值是false,這裡我們只需要把值改成true,打出來的APK包就會是混淆過的了。如下所示:
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
其中minifyEnabled用於設置是否啟用混淆,proguardFiles用於選定混淆配置文件。注意這裡是在release閉包內進行配置的,因此只有打出正式版的APK才會進行混淆,Debug版的APK是不會混淆的。當然這也是非常合理的,因為Debug版的APK文件我們只會用來內部測試,不用擔心被人破解。
那麼現在我們來打一個正式版的APK文件,在Android Studio導航欄中點擊Build->Generate Signed APK,然後選擇簽名文件並輸入密碼,如果沒有簽名文件就創建一個,最終點擊Finish完成打包,生成的APK文件會自動存放在app目錄下。除此之外也可以在build.gradle文件當中添加簽名文件配置,然後通過gradlew assembleRelease來打出一個正式版的APK文件,這種方式APK文件會自動存放在app/build/outputs/apk目錄下。
那麼現在已經得到了APK文件,接下來就用上篇文章中學到的反編譯知識來對這個文件進行反編譯吧,結果如下圖所示:
# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose
# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).
-dontoptimize
-dontpreverify
# Note that if you want to enable optimization, you cannot just
# include optimization flags in your own project configuration file;
# instead you will need to point to the
# "proguard-android-optimize.txt" file instead of this one from your
# project.properties file.
-keepattributes *Annotation*
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
-keepclasseswithmembernames class * {
native ;
}
# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
-keepclassmembers class **.R$* {
public static ;
}
# The support library contains references to newer platform versions.
# Dont warn about those in case this app is linking against an older
# platform version. We know about them, and they are safe.
-dontwarn android.support.**
這個就是默認的混淆配置文件了,我們來一起逐行閱讀一下。
-dontusemixedcaseclassnames
表示混淆時不使用大小寫混合類名。
-dontskipnonpubliclibraryclasses
表示不跳過library中的非public的類。
-verbose
表示打印混淆的詳細信息。
-dontoptimize
表示不進行優化,建議使用此選項,因為根據proguard-android-optimize.txt中的描述,優化可能會造成一些潛在風險,不能保證在所有版本的Dalvik上都正常運行。
-dontpreverify
表示不進行預校驗。這個預校驗是作用在Java平台上的,Android平台上不需要這項功能,去掉之後還可以加快混淆速度。
-keepattributes *Annotation*
表示對注解中的參數進行保留。
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
表示不混淆上述聲明的兩個類,這兩個類我們基本也用不上,是接入Google原生的一些服務時使用的。
-keepclasseswithmembernames class * {
native ;
}
表示不混淆任何包含native方法的類的類名以及native方法名,這個和我們剛才驗證的結果是一致的。
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
表示不混淆任何一個View中的setXxx()和getXxx()方法,因為屬性動畫需要有相應的setter和getter的方法實現,混淆了就無法工作了。
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
表示不混淆Activity中參數是View的方法,因為有這樣一種用法,在XML中配置android:onClick=”buttonClick”屬性,當用戶點擊該按鈕時就會調用Activity中的buttonClick(View view)方法,如果這個方法被混淆的話就找不到了。
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
表示不混淆枚舉中的values()和valueOf()方法,枚舉我用的非常少,這個就不評論了。
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
表示不混淆Parcelable實現類中的CREATOR字段,毫無疑問,CREATOR字段是絕對不能改變的,包括大小寫都不能變,不然整個Parcelable工作機制都會失敗。
-keepclassmembers class **.R$* {
public static ;
}
表示不混淆R文件中的所有靜態字段,我們都知道R文件是通過字段來記錄每個資源的id的,字段名要是被混淆了,id也就找不著了。
-dontwarn android.support.**
表示對android.support包下的代碼不警告,因為support包中有很多代碼都是在高版本中使用的,如果我們的項目指定的版本比較低在打包時就會給予警告。不過support包中所有的代碼都在版本兼容性上做足了判斷,因此不用擔心代碼會出問題,所以直接忽略警告就可以了。
好了,這就是proguard-android.txt文件中所有默認的配置,而我們混淆代碼也是按照這些配置的規則來進行混淆的。經過我上面的講解之後,相信大家對這些配置的內容基本都能理解了。不過proguard語法中還真有幾處非常難理解的地方,我自己也是研究了好久才搞明白,下面和大家分享一下這些難懂的語法部分。
proguard中一共有三組六個keep關鍵字,很多人搞不清楚它們的區別,這裡我們通過一個表格來直觀地看下:
除此之外,proguard中的通配符也比較讓人難懂,proguard-android.txt中就使用到了很多通配符,我們來看一下它們之間的區別:
雖說上面表格已經解釋的很詳細了,但是很多人對於keep和keepclasseswithmembers這兩個關鍵字的區別還是搞不懂。確實,它們之間用法有點太像了,我做了很多次試驗它們的結果都是相同的。其實唯一的區別就在於類中聲明的成員存不存在,我們還是通過一個例子來直接地看一下,先看keepclasseswithmember關鍵字:
-keepclasseswithmember class * {
native ;
}
這段代碼的意思其實很明顯,就是保留所有含有native方法的類的類名和native方法名,而如果某個類中沒有含有native方法,那就還是會被混淆。
但是如果改成keep關鍵字,結果會完全不一樣:
-keep class * {
native ;
}
使用keep關鍵字後,你會發現代碼中所有類的類名都不會被混淆了,因為keep關鍵字看到class *就認為應該將所有類名進行保留,而不會關心該類中是否含有native方法。當然這樣寫只會保證類名不會被混淆,類中的成員還是會被混淆的。
比較難懂的用法大概就這些吧,掌握了這些內容之後我們就能繼續前進了。
回到Android Studio項目當中,剛才打出的APK雖然已經成功混淆了,但是混淆的規則都是按照proguard-android.txt中默認的規則來的,當然我們也可以修改proguard-android.txt中的規則,但是直接在proguard-android.txt中修改會對我們本機上所有項目的混淆規則都生效,那麼有沒有什麼辦法只針對當前項目的混淆規則做修改呢?當然是有辦法的了,你會發現任何一個Android Studio項目在app模塊目錄下都有一個proguard-rules.pro文件,這個文件就是用於讓我們編寫只適用於當前項目的混淆規則的,那麼接下來我們就利用剛才學到的所有知識來對混淆規則做修改吧。
這裡我們先列出來要實現的目標:
下面我們就來逐一實現這些目標。
首先要對MyFragment類進行完全保留可以使用keep關鍵字,keep後聲明完整的類名,然後保留類中的所有內容可以使用*通配符實現,如下所示:
-keep class com.example.guolin.androidtest.MyFragment {
*;
}
然後保留Utils類中的未調用方法可以使用keepclassmembers關鍵字,後跟Utils完整類名,然後在內部聲明未調用的方法,如下所示:
-keepclassmembers class com.example.guolin.androidtest.Utils {
public void methodUnused();
}
最後不要混淆第三方庫,目前我們使用了兩種方式來引入第三方庫,一種是通過本地jar包引入的,一種是通過remote引入的,其實這兩種方式沒什麼區別,要保留代碼都可以使用**這種通配符來實現,如下所示:
-keep class org.litepal.** {
*;
}
-keep class android.support.** {
*;
}
所有內容都在這裡了,現在我們重新打一個正式版的APK文件,然後再反編譯看看效果:
-keep class * extends org.litepal.crud.DataSupport {
*;
}
因為LitePal中所有的Model都是應該繼承DataSupport類的,所以這裡我們將所有繼承自DataSupport的類都進行保留就可以了。
關於混淆APK的用法就講這麼多,如果你還想繼續了解關於Proguard的更多用法,可以參考官方文檔:http://proguard.sourceforge.net/index.html
在本篇文章的第二部分我想講一講混淆Jar包的內容,因為APK不一定是我們交付的唯一產品。就比如說我自己,我在公司是負責寫SDK的,對於我來說交付出去的產品就是Jar包,而如果Jar包不混淆的話將會很容易就被別人反編譯出來,從而洩漏程序邏輯。
實際上Android對混淆Jar包的支持在很早之前就有了,不管你使用多老版本的SDK,都能在 /tools目錄下找到proguard這個文件夾。然後打開裡面的bin目錄,你會看到如下文件:
jar -cvf androidtest.jar -C app/build/intermediates/classes/debug .
在項目的根目錄下就會生成androidtest.jar這個文件,這樣我們就把Jar包准備好了。
現在雙擊proguardgui.bat打開混淆工具,如果是Mac或Ubuntu系統則使用sh proguardgui.sh命令打開混淆工具,界面如下圖所示:
首先我們寫的都是Java代碼,Java代碼的運行要基於Jre基礎之上,沒有Jre計算機將無法識別Java的語法,因此第一個要依賴的就是Jre的rt.jar。 然後由於我們導出的Jar包中有Android相關的代碼,比如Activity、Fragment等,因此還需要添加Android的編譯庫,android.jar。 除此之外,我們使用的AppCompatActivity和Fragment分別來自於appcompat-v7包和support-v4包,那麼這兩個Jar包也是需要引入的。 最後就是代碼中還引入了litepal-1.3.1.jar。
整理清楚了之後我們就來一個個添加,Input/Output有上下兩個操作界面,上面是用於導入要混淆的Jar包和配置混淆後文件的輸出路徑的,下面則是導入該Jar包所依賴的所有其它Jar包的,全部導入後結果如下圖所示:
-injars /Users/guolin/AndroidStudioProjects/AndroidTest/androidtest.jar
-outjars /Users/guolin/androidtest_obfuscated.jar
-libraryjars /Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/rt.jar
-libraryjars /Users/guolin/Library/Android/sdk/platforms/android-23/android.jar
-libraryjars /Users/guolin/AndroidStudioProjects/AndroidTest/app/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.2.0/jars/classes.jar
-libraryjars /Users/guolin/AndroidStudioProjects/AndroidTest/app/build/intermediates/exploded-aar/com.android.support/support-v4/23.2.0/jars/classes.jar
-libraryjars /Users/guolin/AndroidStudioProjects/AndroidTest/app/build/intermediates/exploded-aar/com.android.support/support-v4/23.2.0/jars/libs/internal_impl-23.2.0.jar
-libraryjars /Users/guolin/AndroidStudioProjects/AndroidTest/app/libs/litepal-1.3.1.jar
-dontshrink
-dontoptimize
-dontusemixedcaseclassnames
-keepattributes *Annotation*
-dontpreverify
-verbose
-dontwarn android.support.**
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
-keepclassmembers class * extends android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
-keepclassmembers class **.R$* {
public static ;
}
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
-keep class * extends android.content.ContentProvider
# Also keep - Enumerations. Keep the special static methods that are required in
# enumeration classes.
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# Keep names - Native method names. Keep all native class/method names.
-keepclasseswithmembers,allowshrinking class * {
native ;
}
恩,由此可見其實GUI工具只是給我們提供了一個方便操作的平台,背後工作的原理還是通過這些配置來實現的,相信上面的配置內容大家應該都能看得懂了吧。
接下來我們還可以點擊Save configuration按鈕來保存一下當前的配置文件,這樣下次混淆的時候就可以直接Load進來而不用修改任何東西了。
最後點擊Process!按鈕來開始混淆處理,中間會提示一大堆的Note信息,我們不用理會,只要看到最終顯示Processing completed successfully,就說明混淆Jar包已經成功了,如下圖所示:
好了,本篇文章的內容就到這裡,混淆技術掌握這麼多相信已經足夠大家在平時的工作當中使用了。當然除了使用混淆之外,還有一些加固軟件也能提升程序的安全性,不過這些軟件都是第三方的,並非Google原生支持,所以我就不進行講解和推薦了。那麼我們Android安全攻防戰系列的文章到此結束,感謝大家有耐心看到最後。
靠譜助手作為國內知名的電腦安卓模擬器,為很多網友所熟知。但靠譜助手的一大诟病就是卡,也沒辦法設置。一個小軟件都能很卡,畫面不流暢、操作也不流暢。結合官方和廣
錯誤為:Android.mk文件c++的調用方法為:復制代碼 代碼如下:LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)
以下說明全部針對Android3.0(Api-11)。本指南將介紹三種基本菜單分別是PartA:操作欄(選項菜單OptionMenu)、PartB:上下文操作模式(Act
這篇我們來介紹一下享元模式(Flyweight Pattern),Flyweight 代表輕量級的意思,享元模式是對象池的一種實現。享元模式用來盡可能減少內存使用量,它適