Android安全攻防戰,反編譯與混淆技術完全解析(上)

轉載請註明出處:http://blog.csdn.net/guolin_blog/article/details/49738023
之前一直有猶豫過要不要寫這篇文章,畢竟去反編譯人家的程序並不是什麼值得驕傲的事情。不過單純從技術角度上來講,掌握反編譯功能確實是一項非常有用的技能,可能平常不太會用得到,但是一旦真的需要用到的了,而你卻不會的話,那就非常頭疼了。另外既然別人可以反編譯程序,我們當然有理由應該對程序進行一定程度的保護,因此代碼混淆也是我們必須要掌握的一項技術。那麼最近的兩篇文章我們就圍繞反編譯和混淆這兩個主題來進行一次完全解析。


反編譯

我們都知道,Android程序打完包之後得到的是一個APK文件,這個文件是可以直接安裝到任何Android手機上的,我們反編譯其實也就是對這個APK文件進行反編譯。Android的反編譯主要又分爲兩個部分,一個是對代碼的反編譯,一個是對資源的反編譯,我們馬上來逐個學習一下。
在開始學習之前,首先我們需要準備一個APK文件,爲了尊重所有開發者,我就不拿任何一個市面上的軟件來演示了,而是自己寫一個Demo用來測試。
這裏我希望代碼越簡單越好,因此我們建立一個新項目,在Activity里加入一個按鈕,當點擊按鈕時彈出一個Toast,就這麼簡單,代碼如下所示:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "you clicked button", Toast.LENGTH_SHORT).show();
            }
        });
    }

}

activity_main.xml中的資源如下所示:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"/>

</RelativeLayout>

然後我們將代碼打成一個APK包,並命名成Demo.apk,再把它安裝到手機上,結果如下所示:


好的,到這裏準備工作就已經基本完成了,接下來就讓我們開始對這個Demo程序進行反編譯吧。

反編譯代碼

要想將APK文件中的代碼反編譯出來,我們需要用到以下兩款工具:

將這兩個工具都下載好並解壓,然後我們就開始對Demo程序進行反編譯。解壓dex2jar壓縮包後,你會發現有很多個文件,如下圖所示:


其中我們要用到的是d2j-dex2jar.bat這個文件,當然如果你是linux或mac系統的話就要用d2j-dex2jar.sh這個文件。
然後我們將Demo.apk文件也進行解壓,如果不知道怎麼直接解壓的可以先將文件重命名成Demo.zip,然後用解壓軟件打開。解壓之後你會發現裏面有一個classes.dex文件,如下圖所示:

這個classes.dex文件就是存放所有java代碼的地方了,我們將它拷貝到dex2jar解壓後的目錄下,並在cmd中也進入到同樣的目錄,然後執行:

d2j-dex2jar classes.dex

執行結果如下圖所示:


沒有報任何錯誤,這就說明我們已經轉換成功了。現在觀察dex2jar目錄,你會發現多了一個文件,如下圖所示:

可以看到,classes-dex2jar.jar這個文件就是我們藉助工具之後成功轉換出來的jar文件了。但是對於我們而言,jar文件也不是可讀的,因此這裏還需要再借助一下jd-gui這個工具來將jar文件轉換成java代碼。
下面就很簡單了,使用jd-gui工具打開classes-dex2jar.jar這個文件,結果如下圖所示:

OK,由此可見,我們的代碼反編譯工作已經成功了,MainActivity中的代碼非常清晰,基本已經做到了90%以上的還原工作。但是如果想要做到100%的代碼還原還是非常有難度的,因爲像setContentView()方法傳入的參數,其實就是一個資源的id值而已,那麼這裏反編譯也就只能將相應的id值進行還原,而無法變成像R.layout.activity_main這樣直觀的代碼展示。
另外,除了MainActivity之外,還有很多其它的代碼也被反編譯出來了,因爲當前項目有引用support-v4和support-v7的包,這些引用的library也會作爲代碼的一部分被打包到classes.dex文件當中,因此反編譯的時候這些代碼也會一起被還原。
好的,學完了反編譯代碼,接下來我們看一下如何反編譯資源。

反編譯資源

其實細心的朋友可能已經觀察到了,剛纔Demo.apk的解壓目錄當中不是已經有資源文件了嗎,有AndroidManifest.xml文件,也有res目錄。進入res目錄當中,內容如下圖所示:


這不是所有資源文件都在這裏了麼?其實這些資源文件都是在打包的時候被編譯過了,我們直接打開的話是看不到明文的,不信的話我們打開AndroidManifest.xml文件來瞧一瞧,內容如下圖所示:

可以看到,這代碼是完全沒法閱讀的。當然如果你去打開activity_main.xml看看,結果也不會好到哪兒去:

由此可見,直接對APK包進行解壓是無法得到它的原始資源文件的,因此我們還需要對資源進行反編譯才行。
要想將APK文件中的資源反編譯出來,又要用到另外一個工具了:

關於這個工具的下載我還要再補充幾句,我們需要的就是apktool.bat和apktool.jar這兩個文件。目前apktool.jar的最新版本是2.0.3,這裏我就下載最新的了,然後將apktool_2.0.3.jar重命名成apktool.jar,並將它們放到同一個文件夾下就可以了,如下圖所示:


接下來的工作就很簡單了,我們將Demo.apk拷貝到和這兩個文件同樣的目錄當中,然後cmd也進入到這個目錄下,並在cmd中執行如下命令:

apktool d Demo.apk

其中d是decode的意思,表示我們要對Demo.apk這個文件進行解碼。那除了這個基本用法之外,我們還可以再加上一些附加參數來控制decode的更多行爲:

  • -f 如果目標文件夾已存在,則強制刪除現有文件夾(默認如果目標文件夾已存在,則解碼失敗)。
  • -o 指定解碼目標文件夾的名稱(默認使用APK文件的名字來命名目標文件夾)。
  • -s 不反編譯dex文件,也就是說classes.dex文件會被保留(默認會將dex文件解碼成smali文件)。
  • -r 不反編譯資源文件,也就是說resources.arsc文件會被保留(默認會將resources.arsc解碼成具體的資源文件)。

常用用法就這麼多了,那麼上述命令的執行結果如下圖所示:


這就說明反編譯資源已經成功了。
當然即使你在和我執行一模一樣的操作,也有可能會在這裏反編譯失敗,比如說會報如下錯誤:
這裏寫圖片描述

出現這個錯誤的原因很有可能是你之前使用過apktool的老版本進行過反編譯操作,然後apktool就會在你係統的C:\Users\Administrator\apktool\framework這個目錄下生成一個名字爲1.apk的緩存文件,將這個緩存文件刪除掉,然後再重新執行反編譯命令應該就可以成功了。
現在你會發現在當前目錄下多了一個Demo文件夾,這個文件夾中存放的就是反編譯的結果了。我們可以打開AndroidManifest.xml來瞧一瞧,如下圖所示:

怎麼樣?這樣就完全能看得懂了吧,然後可以再到res/layout中看一下activity_main.xml文件,如下圖所示:

可以看到,activity_main.xml中的內容基本和源代碼中的內容是一致的,外層是一個RelativeLayout,裏面則是一個Button。你可以再到其它目錄中去看一看別的資源,基本上都是可以正常還原的,這樣我們就把反編譯資源的方法也已經掌握了。

重新打包

那麼對於反編譯出來的文件夾,我們能不能重新把它打包成APK文件呢?答案是肯定的,只不過我實在想不出有什麼義正言辭的理由可以讓我們這麼做。有的人會說漢化,沒錯,漢化的方式確實就是將一個APK進行反編譯,然後翻譯其中的資源再重新打包,但是不管怎麼說這仍然是將別人的程序進行破解,所以我並不認爲這是什麼光榮的事情。那麼我們就不去討論本身這件事情的對或錯,這裏只是站在技術的角度來學習一下重新打包的相關知識。
首先我們來看一下通過apktool反編譯後的包目錄情況,如下圖所示:


其中,original文件夾下存放的是未經反編譯過、原始的AndroidManifest.xml文件,res文件夾下存放的是反編譯出來的所有資源,smali文件夾下存放的是反編譯出來的所有代碼,AndroidManifest.xml則是經過反編譯還原後的manifest文件。這裏值得一提的是smali文件夾,如果你進入到這個文件夾中你會發現它的目錄結構和我們源碼中src的目錄結構是幾乎一樣的,主要的區別就是所有的java文件都變成了smali文件。smali文件其實也是真正的源代碼,只不過它的語法和java完全不同,它有點類似於彙編的語法,是Android虛擬機所使用的寄存器語言,語法結構大概如下所示:

看上去有點暈頭轉向是嗎?但是如果你一旦能夠看得懂smali文件的話,那麼你就可以做很恐怖的事情了——你可以隨意修改應用程序內的邏輯,將其進行破解!
不過我對這種黑技術並沒有什麼太大的興趣,因此我也沒有去做具體研究,但即使是這樣,也已經可以對程序的邏輯做一定程度的修改了。比如說當我們點擊按鈕時會彈出you clicked button這樣一句Toast,邏輯是寫在MainActivity按鈕點擊事件的匿名類當中的,因此這段代碼反編譯之後一定就會在MainActivity$1.smali這個文件當中,讓我們打開瞧一瞧,部分代碼如下所示:

雖說多數的代碼我是看不懂的,但其中第47行實在太明顯了,Toast顯示的內容不就是在這裏定義的麼,那麼如果我們想把Demo程序hack掉,就可以將這段字符串給改掉,比如說我把它改成Your app is been hacked
關於smali的語法,網上的資料也非常多,如果你對這門技術十分感興趣的話可以直接上網去搜,這裏我只是簡單介紹一下,就不再深入講解相關知識了。
改了一處代碼後我們再來改一處資源吧,比如這裏想要把Demo的應用圖標給換掉,那麼首先我們要準備好一張新的圖片,如下圖所示:

然後從AndroidManifest.xml文件中可以看出,應用圖標使用的是ic_launcher.png這張圖片,那麼我們將上面籃球這張圖片命名成ic_launcher.png,然後拷貝到所有以res/mipmap開頭的文件夾當中完成替換操作。
在做了兩處改動之後,我們現在來把反編譯後的Demo文件夾重新打包成APK吧,其實非常簡單,只需要在cmd中執行如下命令:

apktool b Demo -o New_Demo.apk

其中b是build的意思,表示我們要將Demo文件夾打包成APK文件,-o用於指定新生成的APK文件名,這裏新的文件叫作New_Demo.apk。執行結果如下圖所示:


現在你會發現在同級目錄下面生成了一個新的APK文件:

不過不要高興得太早了,目前這個New_Demo.apk還是不能安裝的,因爲它還沒有進行簽名。那麼如果這是別人的程序的話,我們從哪兒能拿到它原來的簽名文件呢?很顯然,這是根本沒有辦法拿到的,因此我們只能拿自己的簽名文件來對這個APK文件重新進行簽名,但同時也表明我們重新打包出來的軟件就是個十足的盜版軟件。這裏大家學學技術就好了,希望不要有任何人去做什麼壞事情。
那麼這裏我就用一個之前生成好的簽名文件了,使用Android Studio或者Eclipse都可以非常簡單地生成一個簽名文件。
有了簽名文件之後在cmd中執行簽名命令就可以進行簽名了,命令格式如下:

jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore 簽名文件名 -storepass 簽名密碼 待簽名的APK文件名 簽名的別名

其中jarsigner命令文件是存放在jdk的bin目錄下的,需要將bin目錄配置在系統的環境變量當中纔可以在任何位置執行此命令。
簽名之後的APK文件現在已經可以安裝到手機上了,不過在此之前Android還極度建議我們對簽名後的APK文件進行一次對齊操作,因爲這樣可以使得我們的程序在Android系統中運行得更快。對齊操作使用的是zipalign工具,該工具存放於<Android SDK>/build-tools/<version>目錄下,將這個目錄配置到系統環境變量當中就可以在任何位置執行此命令了。命令格式如下:

zipalign 4 New_Demo.apk New_Demo_aligned.apk

其中4是固定值不能改變,後面指定待對齊的APK文件名和對齊後的APK文件名。運行這段命令之後就會生成一個New_Demo_aligned.apk文件,如下所示:


這個New_Demo_aligned.apk就是我們重新打包簽名對齊後的文件了,現在把它安裝到手機上,效果如下圖所示:

可以看到,應用圖標已經成功改成了籃球,另外點擊按鈕後彈出的Toast的提示也變成了我們修改後的文字,說明重新打包操作確實已經成功了。


好的,我們把反編譯代碼、反編譯資源、重新打包這三大主題的內容都已經掌握了,關於反編譯相關的內容就到這裏,下篇文章會介紹Android代碼混淆方面的相關技術,感興趣的朋友請繼續閱讀: Android安全攻防戰,反編譯與混淆技術完全解析(下)

關注我的技術公衆號,每天都有優質技術文章推送。關注我的娛樂公衆號,工作、學習累了的時候放鬆一下自己。

微信掃一掃下方二維碼即可關注:

        

發佈了120 篇原創文章 · 獲贊 2萬+ · 訪問量 1031萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章