copy : https://blog.csdn.net/u012573920/article/details/44034397
1.Smali簡介
Smali是Dalvik的寄存器語言,它與Java的關系,簡單理解就是匯編之於C。假如你對匯編有足夠的駕馭能力,那你可以通過修改匯編代碼來改變C/C++代碼的走向。當然,學過匯編的都清楚,匯編比BrainFuck還難學,更不用說去反編譯修改了。
但是Smali有一點不一樣,就是它很簡單,只有一點點的語法,只要你會java,了解Android的相關知識,那你完全可以通過修改Smali代碼來反向修改java代碼,雖然繞了一點,但是在某些情況下你不得不這么做。還好,Smali很簡單。
2.apktool
說了這么多,還沒有說Smali哪來?沒錯。Smali代碼是安卓APK反編譯而來的,所以Smali文件和Java文件一一對應。獲取Smali文件,我們需要下載一個輔助工具:ApkTool。apktool這個命令行工具如果詳細使用功能參數是比較多的,但是這里我們只需要用到2個最基礎的功能:
一個是反編譯decode:
apktool d xxx.apk
另一個是打包build:
apktool b
這里要注意的是路徑問題,apktool如果沒有加入到環境變量中,記得cd到apktool的目錄去使用它。另一個是打包,如果只是簡單的使用參數b,那要求是要在反編譯出來的項目目錄下執行,而打包好的文件會保存在這個項目目錄下的dist目錄。
這是一個HelloWorld的應用程序反編譯和打包的目錄結構:
3.Smali語法
(1)數據類型
dalvik字節碼有兩種類型,原始類型和引用類型。對象和數組是引用類型,其它都是原始類型。
smali數據類型都是用一個字母表示,如果你熟悉Java的數據類型,你會發現表示smali數據類型的字母其實是Java基本數據類型首字母的大寫,除boolean類型外,在smail中用大寫的”Z”表示boolean類型。
| V | void,只能用於返回值類型 |
| Z | boolean |
| B | byte |
| S | short |
| C | char |
| I | int |
| J | long (64 bits) |
| F | float |
| D | double (64 bits) |
對象以Lpackage/name/ObjectName;的形式表示。前面的L表示這是一個對象類型,package/name/是該對象所在的包,ObjectName是對象的名字,“;”表示對象名稱的結束。相當於java中的package.name.ObjectName。
例如:Ljava/lang/String;相當於java.lang.String
數組的表示形式
[I——表示一個整型一維數組,相當於java中的int[]。對於多維數組,只要增加[就行了。[[I相當於int[][],[[[I相當於int[][][]。注意每一維的最多255個。
對象數組的表示
[Ljava/lang/String;表示一個String對象數組。
(2)方法
方法通常必須詳細的指定方法類型(?the type that contains the method) 方法名,參數類型,返回類型,所有這些信息都是為虛擬機是能夠找到正確的方法並執行。
方法表示形式:Lpackage/name/ObjectName;->MethodName(III)Z
在上面的例子中,Lpackage/name/ObjectName;表示類型,MethodName是方法名。III為參數(在此是3個整型參數),Z是返回類型(bool型)。
方法的參數是一個接一個的,中間沒有隔開。
一個更復雜的例子:method(I[[IILjava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
在java中則為:String method(int, int[][], int, String, Object[])
一個比較全面的例子:
.class public interface abstract Lcom/kit/network/CachableImage; .super Ljava/lang/Object; .source "SourceFile" # virtual methods .method public abstract getIsLarge()Z .end method .method public abstract getUrl()Ljava/lang/String; .end method .method public abstract getViewContext()Landroid/content/Context; .end method .method public abstract setBitmap(Landroid/graphics/Bitmap;Z)V .end method .method public abstract setIsLarge(Z)V .end method .method public abstract setUrl(Ljava/lang/String;)V .end method
上面的smali代碼還原后的java代碼為:
(3)字段
表示形式:Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;即包名,字段名和各字段類型。 eg:
這里仍然以一個默認的HelloWorld的應用程序進行解釋吧。新建一個HelloWorld安卓項目,在MainActivity中只保留onCreate函數。代碼如下:
package com.fusijie.helloworld;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
反編譯后的Smali文件如下:
對比一下,可以比較清楚的看出來,smali代碼其實就是對java代碼一個翻譯,只是沒有java看起來那么簡單,smali把很多應該復雜的東西還原成復雜的狀態了。簡單解釋下這段代碼。
- 前三行指明了類名,父類名,和源文件名。
- 類名以“L”開頭相信熟悉Jni的童鞋都比較清楚。
- “#”是smali中的注釋。
- “.method”和“.end method”類似於Java中的大括號,包含了方法的實現代碼段。
- 方法的括號后面指明了返回類型,這同樣類似與Jni的調用。
- “.locals”指明了這個方法用到的寄存器數量,當然寄存器可以重復利用,從“V0”起算。
- “.prologue”指定了代碼開始處。
- “.line”表明這是在java源碼中的第幾行,其實這個值無所謂是多少,可以任意修改,主要用於調試。
- “invoke-direct”這是對方法的調用,可以看到這里調用了是Android.app.Activity的init方法,這在java里是隱式調用的。
- “return-void”表明了返回類型,這和java不一樣,即使沒有返回值,也需要這樣寫。
- 接下來是onCreate方法,“.parameter”指明了參數名,但是一般沒有用,需要注意的是p0代表的是this,p1開始代表函數參數,靜態函數沒有this,所以從p0開始就代表參數。
- 在實現里先是調用了父類的方法,然后再調用setContentView,注意這里給了一個傳參。整形的傳參,這個值是先賦給寄存器v0,然后再調用的使用傳遞進去的。smali中都是這么使用,所有的值必須通過寄存器來中轉。這點和匯編很像。
對比了Java代碼和Smali代碼,可以很清楚的看到,原本只有幾行的代碼到了Smali,內容被大大擴充了。Smali還原了Java隱藏的東西,同時顯式地指定了很多細節。這還只是個最基本的HelloWorld的onCreate函數,如果有內部類,還會分文件顯示。
這樣看來,其實Smali只能說復雜,不能說難。如果想全面了解smali語法,這里給出幾個鏈接,算是總結的相對好一點的(其實我都沒看到有系統總結的。。。如果你有好的資料,歡迎跟帖分享)
這里順便提供2個利器:
從上面一個例子對Smali的用途就很清楚了,沒錯,Smali注入。現在常見的除了測試以外的用途,Smali注入明顯是帶有黑客性質的,小的如破解游戲,替換游戲廣告,大的甚至利用漏洞去破解密碼,偷竊個人資料,財產等等。對Smali,安卓逆向分析,安卓系統安全比較清楚的,這些事其實都不算事。
