7. Smali基礎語法總結


最近在學習Android 移動安全逆向方面,逆向首先要看懂代碼,Android4.4之前一直使用的是 Dalivk虛擬機,而Smali是用於Dalivk的反匯編程序的實現。

Smali 支持注解,調試信息,行數信息等基本Java的基本特性,可以說是很接近Java編譯再JVM上的中間語言,一般用來做Android程序的逆向工程。

1.Smali文件結構

一個Smali文件對應的是一個Java的類,更准確的說是一個.class文件,如果有內部類,需要寫成ClassName$InnerClassAClassName$InnerClassB...這樣的形式

2.基本類型

3.對象

Object類型,即引用類型的對象,在引用時,使用L開頭,后面緊接着的是完整的包名,比如:java.lang.String,對應的Smali語法則是 Ljava/lang/String

4.數組

一維數組在類型的左邊加一個方括號,比如:[I 等同於Java的 int[ ][F 等同於Java的 float[ ] ,每多一維就加一個方括號,最多可以設置255維。

5.方法聲明及調用

官方Wiki中給出的Smali引用方法的模板如下:

Lpackage/name/ObjectName;->MethodName(III)Z

第一部分:Lpackage/name/ObjectName;用於聲明具體的類型,以便JVM尋找

第二部分:MethodName(III)Z,其中 MethodName 為具體的方法名,()中的字符,表示了參數數量和類型,即3個int型參數,Z為返回值的類型,返回Boolean類型

由於方法的參數列表沒有使用逗號這樣的分隔符進行划分,所以只能從左到右,根據類型定義來區分參數個數。

通過幾個例子來說明,以java.lang.String為例:

java方法:public char charAt(int index){...}
Davilk描述:Ljava/lang/String;->charAt(I)C

java方法:public void getChars(int srcBegin,int srcEnd,char dst[],int dstBegin){...}
Davilk描述:Ljava/lang/String;->getChars(II[CI)V

java方法:public boolean equals(Object anObject){...}
Davilk描述:Ljava/lang/String;->equals(Ljava/lang/Object)Z

如果需要調用構造方法,則MethodName為:<init>

例子:

String對象在Smali中為:Ljava/lang/String

Class1對象的一個Boolean成員表示為:Lcom/disney/Class1;->isRunning:Z

Class2對象的一個String對象成員表示為:Lcom/disney/Class2;->name:Ljava/lang/String

可以總結為:對象類型 -> 成員名 : 成員類型

-> 表示所屬關系,類型尾部必須包括一個分號。

6.寄存器聲明及使用

在Smali中,如果需要存儲變量,必須先聲明足夠數量的寄存器,1個寄存器可以存儲32位長度的類型,比如Int,而兩個寄存器可以存儲64位長度類型的數據,比如Long或Double。

聲明可使用的寄存器數量的方式為:.registers N,N代表需要的寄存器的總個數,同時,還有一個關鍵字 .local ,它用於聲明非參數的寄存器個數(包含在registers聲明的個數當中),也叫做本地寄存器,只在一個方法內有效,但不常用,一般使用registers即可。

.registers 3說明該方法有三個寄存器,其中一個本地寄存器v0,兩個參數寄存器p0,p1,細心的人可能會注意到沒有看到p0,原因是p0存放的是this。如果是靜態方法的話就只有2個寄存器了,不需要存this了。

1.本地寄存器(local register,非參寄存器)用v開頭數字結尾的符號來表示,如v0、v1、v2、…,

2.參數寄存器(parameter register)用p開頭數字結尾的符號來表示,如p0、p1、p2、…,

3..registers 用來標明方法中寄存器的總數,即參數寄存器和非參寄存器的總數。

4..local 0,標明在這個函數中最少要用到的本地寄存器的個數,出現在方法中的第一行。在這里,由於只需要調用一個父類的onDestroy()處理,所以只需要用到p0,所以使用到的本地寄存器數為0,在植入代碼后不要忘記可能要修改.local的值。

如 .local 4,則可以使用的寄存器是v0-v3。

5.當一個方法被調用的時候,方法的參數被置於最后N個寄存器中。

6.在實例函數中,p0代指“this”,p1表示函數的第一個參數,p2代表函數中的第二個參數…,

7.在static函數中,p1表示函數的第一個參數,p2代表函數中的第二個參數…,因為Java的static方法中沒有this方法。

那么,如何確定需要使用的寄存器的個數?

由於非static方法,需要占用一個寄存器以保存this指針,那么這類方法的寄存器個數,最低就為1,如果還需要處理傳入的參數,則需要再次疊加,此時還需要考慮Double

Float這種需要占用兩個寄存器的參數類型,舉例來看:

如果一個Java方法聲明如下:

myMethod(int p1, float p2, boolean p3)

那么對應的Smali則為:

method LMyObject;->myMethod(IJZ)V

此時,寄存器的對應情況如下:

那么最少需要的寄存器個數則為:5

如果方法體內含有常量、變量等定義,則需要根據情況增加寄存器個數,數量只要滿足需求,保證需要獲取的值不被后面的賦值沖掉即可,方法有:存入類中的字段中(存入后,寄存器可被重新賦值),或者長期占用一個寄存器

7.Dalvik指令集

如果需要使用Smali編寫程序,還需要掌握常用的Dalvik虛擬機指令,其合集稱為Dalvik指令集。這些指令有點類似x86匯編的指令,但指令更多,使用也非常簡單方便。最

盡的介紹,可以參考Android官方的Dalvik相關文檔:https://source.android.com/devices/tech/dalvik/dalvik-bytecode#instructions

一般的指令格式為:[op]-[type](可選)/[位寬,默認4位] [目標寄存器],[源寄存器](可選),比如:move v1,v2,move-wide/from16 v1,v2

這里也列舉一些常用的指令,並結合Smali進行說明:

  • 移位操作:

此類操作常用於賦值

  • 返回操作:

用於返回值,對應Java中的return語句

  • 常量操作:

用於聲明常量,比如字符串常量(僅聲明,String a = “abc”這種語句包含聲明和賦值)

  • 調用操作:

用於調用方法,基本格式:invoke-kind {vC, vD, vE, vF, vG}, meth@BBBB,其中,BBBB代表方法引用(參見上面介紹的方法定義及調用),vC~G為需要的參數,根據順序一一對應

smali中的函數調用也分為directvirtual兩種類型,direct method就是private函數,public和protected函數都屬於virtual method。在調用函數時,有invoke-direct,invoke

virtual,invoke-static、invoke-super以及invoke-interface等幾種不同的指令。還有invoke-XXX/range 指令的,這是參數多於4個的時候調用的指令,比較少見。

invoke-static:就是調用static函數的,示例:

invoke-static {}, Lcom/disney/Class1;->fun()Z

上句invoke-static后面有一對大括號“{}”,內部是調用該方法的實例和參數列表,由於這是static方法也不需要參數,所以{}內為空。

invoke-super:調用父類方法,在onCreate、onDestroy等方法都能看到。

invoke-direct:調用private函數,示例:

invoke-direct {p0}, Lcom/disney/Class1;->getGlobalIapHandler()Lcom/disney/config/GlobalPurchaseHandler;

上句即this->getGlobalIapHandler(),函數getGlobalIapHandler()是定義在Class1中的一個private函數。

invoke-virtual:用於調用protected或public函數,示例:

sget-object v0, Lcom/disney/Class1;->shareHandler:Landroid/os/Handler; invoke-virtual {v0, v3}, Landroid/os/Handler;->removeCallbacksAndMessages(Ljava/lang/Object;)V

上句v0是shareHandler android/os/Handler,v3是傳遞給removeCallbackAndMessage方法的Ljava/lang/Object參數。

如何獲取函數調用結果:

在smali里調用函數和返回函數結果需要分開來完成,在調用的函數返回非void后,用move-result(返回基本數據類型)和move-result-object(返回對象)指令獲取返回結果。

示例:

const/4 v2, 0x0 invoke-virtual {p0, v2}, Lcom/disney/Class1;->getPreferences(I)Landroid/content/SharedPreferences; move-result-object v1

上句v1保存的就是調用this.getPreferences(int)方法返回的SharedPreferences實例。

  • 判斷操作: 

判斷操作用來比較一個寄存器中的值,是否與目標寄存器中的值相等或不等,對應Java中的if語句,格式為:if-[test] v1,v2, [condition],其衍生操作還有專門與0進行比較的if-[test]z v1, [condition],其中[condition]為符合判斷結果后的跳轉條件,需要提前定義好。判斷操作也通常和goto配合使用,用來實現循環或者if-else語句

if-eq vA, VB, cond_** 如果vA等於vB則跳轉到cond_**。相當於if (vA==vB) if-ne vA, VB, cond_** 如果vA不等於vB則跳轉到cond_**。相當於if (vA!=vB) if-lt vA, VB, cond_** 如果vA小於vB則跳轉到cond_**。相當於if (vA<vB) if-le vA, VB, cond_** 如果vA小於等於vB則跳轉到cond_**。相當於if (vA<=vB) if-gt vA, VB, cond_** 如果vA大於vB則跳轉到cond_**。相當於if (vA>vB) if-ge vA, VB, cond_** 如果vA大於等於vB則跳轉到cond_**。相當於if (vA>=vB) if-eqz vA, :cond_** 如果vA等於0則跳轉到:cond_** 相當於if (VA==0) if-nez vA, :cond_** 如果vA不等於0則跳轉到:cond_**相當於if (VA!=0) if-ltz vA, :cond_** 如果vA小於0則跳轉到:cond_**相當於if (VA<0) if-lez vA, :cond_** 如果vA小於等於0則跳轉到:cond_**相當於if (VA<=0) if-gtz vA, :cond_** 如果vA大於0則跳轉到:cond_**相當於if (VA>0) if-gez vA, :cond_** 如果vA大於等於0則跳轉到:cond_**相當於if (VA>=0)
  •     屬性操作:

屬性操作的分為:取值(get)賦值(put)

目標類型分為:數組(array)、實例(instance)和靜態(static)三種,對應的縮寫前綴就是a、i、s

長度類型分為:默認(什么都不寫)、wide(寬,64位)、object(對象)、boolean、byte、char、short(后面幾種就不解釋了,和Java一致)
指令格式:[指令名] [源寄存器], [目標字段所在對象寄存器], [字段指針],示例代碼如下,操作是為int型的類成員變量mIntA賦值為100

const/16 v0, 0x64 iput v0, p0, Lcom/coderyuan/smali/MainActivity;->mIntA:I

下面列出用於實例字段的指令,其中i都可以換成a或者s,分別用於操作數組字段或者靜態字段

  • 其他指令:

除以上介紹的幾種基本的Dalvik指令外,Dalvik還支持值類型轉換(如:int轉float,double轉float等)、基本運算(數學運算、邏輯運算、自增)兩種指令集,這里只列舉一些常用的指令,其他的可以參考上面提到的Google官方文檔

 

參考鏈接:

  https://blog.csdn.net/qq_32113133/article/details/85163277


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM