首先說句抱歉,今天事情比較多,教程出來的比較晚,請包含。今后幾天也會稍晚一點,但是我會每天更新的。 今天我們來詳細的看看反編譯,想要修改一個系統(tǒng)自帶的應用程序和它的代碼,在沒有源碼的情況下,我們就不得不用反編譯來修改。 和很多書籍一樣,為了向經(jīng)典的"Hello, World"致敬,我們也從一個簡單的程序開始HelloActivity.apk。當你把這個APK安裝到手機上運行后,在屏幕上就顯示一行文字"Hello, World!"(世界我來了!是的,兄弟姐妹們,從今天起我們真正進入反編譯的世界,我們來了?。?br> 1. 反編譯 為了介紹方便,從現(xiàn)在起,我會用cracker~$作為命令提示符,其后的文字表示我們需要運行的命令。如果其后有斜體字,表示命令的輸出結(jié)果。 cracker~$ apktool d HelloActivity.apk 這條命令運行完后,在當前目錄下會生成一個名為HelloActivity的目錄。 該目錄的結(jié)構(gòu)為(名稱后跟/表示這是一個目錄): HelloActivity/ |--------------AndroidManifest.xml |--------------apktool.yml |--------------res/ |--------------smali/ apktool.yml是apktool生成的一個配置文件,基本上你不需要修改這個文件。下面的章節(jié)我們逐個介紹剩下的AndroidManifest.xml文件和res, smali目錄。 2. AndroidManifest.xml 要想完全理解這個文件,你得對Android的內(nèi)部運作機制非常清楚。幸好我們修改一個APK的時候基本上不改這個文件。這里幫助你有個大致的了解。 Android安裝程序一般叫apk文件(apk是Android Package的縮寫,表示Android安裝包)。一般來說,程序都會有一個或多個Activity, Activity是什么呢,從概念說它是一個和用戶交互的窗口,你每天使用Android手機的時候基本上你打交道的每個界面都是一個Activity。AndroidManifest.xml是一個xml格式的清單文件,就像你去超市買東西會打印出一個購物清單,AndroidManifest.xml也起著一個清單的作用,它告訴系統(tǒng),我有這些Activity。(實際情況遠比這復雜,想學Android編程的同學請看這個http://developer.android.com/guide/index.html,好好學習其中的內(nèi)容)。 具體到HelloActivity下的AndroidManifest.xml文件,大家可以找到如下內(nèi)容: <application android:label="@string/app_name" android:icon="@drawable/ic_launcher_hello"> <activity android:name="HelloActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> 其中有一行<category android:name="android.intent.category.LAUNCHER" />,包含這一行的Activity會顯示在桌面中,就是說你可以通過桌面顯示的圖標啟動這個Activity。里面還有android:label="@string/app_name" android:icon="@drawable/ic_launcher_hello"。這兩個屬性是做什么的呢,android:label表示程序顯示在桌面上的名字,android:icon表示程序顯示在桌面上的圖標。如果想要改顯示的名字和圖標,修改其后的兩個資源,如何修改資源,下一節(jié)詳細介紹。 3. 資源 res目錄下放置了程序所需要的所有資源。資源是什么呢,一般來說,一個圖形用戶界面(GUI)程序總是會使用一些圖片,或者顯示的文字的大小和顏色等?;蛘呓缑娴牟季郑热顼@示的界面上面是文字,下面是兩個按鈕等等。這些程序的一個重要特點就是用戶界面和代碼邏輯的分離。當我們需要替換圖片或者簡單修改界面布局的時候,不需要改變代碼。而Android程序會將這些代碼中需要用到的文件都放在res目錄下,稱之為資源。 可以看到res目錄的內(nèi)容為: res/ |--------drawable-hdpi/ |-----------ic_launcher_hello.png |--------layout/ |---------hello_activity.xml |--------values/ |---------ids.xml |---------public.xml |---------strings.xml 對于HelloActivity來說,res目錄下有三個子目錄drawable-hdpi, layout, values。由于HelloActivity比較簡單,因此res下內(nèi)容不多,但是一個復雜的程序res目錄下內(nèi)容相應的也會比較多,但是基本原理都是一樣的。 res下面的子目錄基本上是按照資源類型來分類組織的,以drawable開頭的表示圖片資源,大家可能會看到drawable-hdpi, drawable-mdpi, drawable-ldpi等,這些hdpi,mdpi,lpid分別表示高/中/低分辨率,會根據(jù)不同的屏幕分辨率選擇不同的圖片。要想替換圖片,替換這些目錄下的圖片就可以了。(替換圖片比這稍復雜點,一般替換圖片,最好保持和原圖片兼容,比如說色系,尺寸以及點9圖片的一些參數(shù)等)。 以layout開頭的表示布局文件,用來描述程序的界面。anim子目錄存放程序使用到的動畫,xml開頭的目錄存放程序用到的一些xml文件等。 values開頭的目錄下面存放一些我們稱之為基本元素的定義,比如說colors.xml給出顏色值的定義,dimens.xml給出一些大小的定義。strings.xml是一些字符串的定義。我們看看HellloActivity的strings.xml文件。 <?xml version="1.0" encoding="utf-8"?> <resources> <string name="hello_activity_text_text">Hello, World!</string> <string name="app_name">HelloWorld</string> </resources> 其中的以<string開頭便是這是一個字符串,name是給這個資源起一個名字,后面的字符串表示這個字符串的值。Android的資源大致按這種形式來組織的,先將資源分成幾種類型,然后每一種類型的所有資源取一個名字,這個名字對應了這個資源的值/內(nèi)容。 我們一般修改資源通常情況下是修改圖片或者漢化。漢化比較簡單,values/strings.xml文件存放程序用到的所有英文字符串值。要漢化,首先在values下建立一個目錄values-zh-rCN。然后將values/strings.xml拷貝到該目錄中,將每一個字符串翻譯成中文。我們現(xiàn)在將strings.xml拷貝到values-zh-rCN目錄下,并將文件內(nèi)容改為: <?xml version="1.0" encoding="utf-8"?> <resources> <string name="hello_activity_text_text">你好,世界!</string> <string name="app_name">你好世界</string> </resources> 現(xiàn)在我們需要把修改后的文件在編回apk文件,運行如下命令: cracker~$ apktool b HelloActivity HelloActivity.apk 這條命令表示編譯HelloActivity目錄的內(nèi)容,輸出文件為HelloActivity.apk,如果你不想覆蓋原有的文件,可以換一個名字或者放在另外一個目錄下。 接下來我們需要對生成的APK進行簽名,下載附件中的壓縮包sign.zip, 解壓后有一個腳本sign.sh。假定你把解壓后的文件都放在/home/cracker/tools目錄中。運行如下命令: cracker~$ export KEY_PATH=/home/cracker/tools cracker~$ /home/cracker/tools/sign.sh HelloActivity.apk cracker~$ adb install -r HelloActivity.apk.signed.aligned 注意最后一條命令如果失敗,如果你不是用我們提供的HelloActivity做實驗的話,會發(fā)生簽名不一致的錯誤,這個時候先卸載原來的,再安裝。運行看看,對的,現(xiàn)在顯示在你面前的是“你好,世界!” 漢化成功了,是的,漢化就這么簡單。如果你只想停留在漢化或者替換圖片這個階段,從這里開始以后的文章不用看了。如果你沒有Android編程基礎,從這里開始以后的文章也不用看了。 到底發(fā)生了什么魔法,為什么這樣替換一下圖片或者改字符串就能改變程序最終運行的結(jié)果呢,想要理解這個,我們就的大致的了解一下資源的編譯過程。首先我們看看values目錄下一個有意思的文件publics.xml,它的內(nèi)容如下: <?xml version="1.0" encoding="utf-8"?> <resources> <public type="drawable" name="ic_launcher_hello" id="0x7f020000" /> <public type="layout" name="hello_activity" id="0x7f030000" /> <public type="string" name="hello_activity_text_text" id="0x7f040000" /> <public type="string" name="app_name" id="0x7f040001" /> <public type="id" name="text" id="0x7f050000" /> </resources> 每一行的id后面都有一個看起來很奇怪的數(shù)字,這個數(shù)字是干嘛的呢?Android下有一個資源編譯器會編譯res目錄下的所有文件,它為每一個資源名字分配一個數(shù)字標識符,這個標識符分成3個部分,最前面的1個字節(jié)表示包名,所有的apk這個字節(jié)都是7f。表示這些資源是非共享的,其它APK訪問不到。所有那些可以共享的資源放在system/framework/framework-res.apk下。/system/framework往往還有其它共享的資源包。這些共享的資源包前面的1個字節(jié)從0x1開始,依次增加。中間的一個字節(jié)表示資源的類型,每一個類型的數(shù)字標識符是不一樣的,最后的2個字節(jié)是資源的序號,統(tǒng)一類型的資源序號從0依次往上遞增。一般來說,資源id是由資源編譯器(aapt)自動產(chǎn)生的,但是定義在publics.xml中的值告訴編譯器,你必須為這個id使用這個值。apktool會為所有的資源名稱定義這個值在publics.xml里,這樣可以保證替換資源后資源的id不會變化。 為啥資源的id這么重要,如果變了,會怎么樣呢,這得結(jié)合代碼理解。我們在Java代碼里通常這樣引用資源,比如R.string.app_name。這個Java代碼經(jīng)過編譯后,這條引用直接變成了資源id,即0x7f040001,所以你在下面反編譯后的smali代碼里面是看不到R.string.app_name這個東西的,只能看到0x7f040001。資源編譯器會生成一個查找表,對于每一個id,查找表中保存了這個id對應的名字和值(如果是文件,則為文件所在路徑)。程序在運行的時候,會根據(jù)id去查詢這個查找表找到對應的資源的值或文件。 最后說一句,資源的ID非常重要,運行adb pull /system/framework/framework-res.apk反編譯這個文件,好好的消化一下這一節(jié)的內(nèi)容吧。 4. smali 終于迎來我們最重要的部分了smali目錄,smali目錄存放的是反編譯后的Java代碼,文件名以smali結(jié)尾,故稱作smali文件。這些代碼比一般的Java代碼可讀性差太多了,但是和傳統(tǒng)的x86或者其他體系結(jié)構(gòu)下的匯編文件那又是好讀多了。雖然有工具可以直接把這些反匯編成java代碼,單是好不了太多,我們還是直接讀取修改smali文件。從這里我們才真正的開始是一名程序員。 我們來看一下反編譯后的smali目錄下的HelloActivity.smali文件,和Java組織源代碼的方式一樣,smali目錄下的文件也是按文件包的包名結(jié)構(gòu)組織目錄結(jié)構(gòu)的,文件的內(nèi)容如下: .class public Lcom/example/android/helloactivity/HelloActivity; .super Landroid/app/Activity; .source "HelloActivity.java" # direct methods .method public constructor <init>()V .locals 0 .prologue .line 27 invoke-direct {p0}, Landroid/app/Activity;-><init>()V return-void .end method # virtual methods .method public onCreate(Landroid/os/Bundle;)V .locals 2 .parameter "savedInstanceState" .prologue .line 33 invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V .line 37 const/high16 v1, 0x7f03 invoke-virtual {p0, v1}, Lcom/example/android/helloactivity/HelloActivity;->setContentView(I)V .line 38 const/high16 v1, 0x7f05 invoke-virtual {p0, v1}, Lcom/example/android/helloactivity/HelloActivity;->findViewById(I)Landroid/view/View; move-result-object v0 check-cast v0, Landroid/widget/TextView; .line 39 .local v0, txtView:android/widget/TextView; const/high16 v1, 0x7f04 invoke-virtual {v0, v1}, Landroid/widget/TextView;->setText(I)V .line 40 return-void .end method 文件中的以#開頭的文字表示注釋, 以.開頭的叫做annotations,其中的.line表示對應的源代碼的行號,這個對調(diào)試很重要。.metho和.end method表示一個方法定義的開始和結(jié)束。Smali文件中的這些指令的功能請參照http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html,有一些不懂沒關(guān)系,在接下來得兩章我們會接觸到大部分指令。 其中.line 39的代碼對應的源代碼是 txtView.setText(R.string.hello_activity_text_text) 我們現(xiàn)在想將這行代碼改成txtView.setText("Happy, Cracker!"),將.line 39到.line 40行的代碼改為: .line 39 .local v0, txtView:android/widget/TextView; const-string v1, "Happy, Cracker!" invoke-virtual {v0, v1}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V 再按照上一節(jié)所說得重新編譯,簽名,安裝運行,好了,現(xiàn)在出現(xiàn)在你面前的是Happy, Cracker!了,真happy! 接下來的兩章我們都會介紹如何直接修改smali代碼從而改變程序的功能,這種方法我們叫做代碼插樁,接下來的兩章我們將會用代碼插樁的方法將MIUI的功能加到原生ROM中去。 |