1. 程式人生 > >Android安全攻防戰,反編譯與混淆技術完全解析(上)

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安全攻防戰,反編譯與混淆技術完全解析(下)

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

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