Smali和逆向分析

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代碼爲:

//#注:在實際代碼中我們還必須引入相關的包
import android.content.Context;
import android.graphics.Bitmap;

public interface CachableImage {

	public abstract boolean getIsLarge();

	public abstract String getUrl();

	public abstract Context getViewContext();

	public abstract void setBitmap(Bitmap bitmap);

	public abstract void setIsLarge(boolean islarge);

	public abstract void setUrl(String url);
}



 (3)字段

 

表示形式:Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;即包名,字段名和各字段類型。 eg:

.field private _requestLayout:Z

.field public isLarge:Z

.field public resize:Z

.field public thumbnailSize:I

.field public url:Ljava/lang/String;


還原後的java代碼爲:

public boolean _requestLayout;
public boolean isLarge;
public boolean resize;
public int thumbnailSize;
public String url;


 

這裏仍然以一個默認的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文件如下:

  
  .class public Lcom/fusijie/helloworld/MainActivity;
  .super Landroid/app/Activity;
  .source "MainActivity.java"
  # direct methods
  .method public constructor ()V
    .locals 0
    .prologue
    .line 14
    invoke-direct {p0}, Landroid/app/Activity;->()V
    return-void
  .end method
  # virtual methods
  .method protected onCreate(Landroid/os/Bundle;)V
    .locals 1
    .parameter "savedInstanceState"
    .prologue
    .line 18
    invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
    .line 19
    const/high16 v0, 0x7f03
    invoke-virtual {p0, v0}, Lcom/fusijie/helloworld/MainActivity;->setContentView(I)V
    .line 20
    return-void
  .end method

對比一下,可以比較清楚的看出來,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個利器:

4.小試牛刀

瞭解了Smali的基本語法,那我們要動手試一下,Smali能做什麼?仍然以HelloWorld爲例,假如我們沒有Android項目的源代碼,只有一個APK,給他加個新功能吧!

這個功能很簡單,只是在HelloWorld中輸出一個“Hello, Smali”。

(1)第一步還是先使用apktool來反編譯HelloWorld.apk。

(2)打開smali下的com/fusijie/helloworld/MainActivity.smali文件。

(3)原本我們在Java中要寫的代碼是:

 Toast.makeText(this, "Hello, Smali", Toast.LENGTH_LONG).show();


翻譯成Smali就是:
   
 .line xx
    const-string v0, "Hello, Smali"
    const/4 v1, 0x1
    invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
    move-result-object v0
    invoke-virtual {v0}, Landroid/widget/Toast;->show()V

(4)最後在插入Smali的時候,我們需要修改2個地方:

  • “.locals 1”,因爲本來只用到了v0,現在多用了一個v1,所以改爲“.locals 2”。
  • “.line xx” xx隨意改爲一個不重複的值即可。

(5)使用apktool打包成apk,因爲打包完後原有的密鑰會丟失,所以需要重新打上我們自己的密鑰,可以參考很早以前我寫的一個帖子

(6)最後的效果是這樣的。


5.乾點壞事

從上面一個例子對Smali的用途就很清楚了,沒錯,Smali注入。現在常見的除了測試以外的用途,Smali注入明顯是帶有黑客性質的,小的如破解遊戲,替換遊戲廣告,大的甚至利用漏洞去破解密碼,偷竊個人資料,財產等等。對Smali,安卓逆向分析,安卓系統安全比較清楚的,這些事其實都不算事。

我這裏以一個實際上線的遊戲破解爲例,看看我們平時在寫代碼時要注意哪些問題,避免辛辛苦苦寫遊戲,卻在幫人家數錢。這裏的破解不是重點,反破解纔是重點。

以市場上很火的一款單機遊戲《消滅小星星》爲例,下載地址是:http://apk.91.com/Soft/Android/com.brianbaek.popstar-340.html

相同的方法反編譯,在/smali/com/zplay/android/sdk/pay/ZplayPay.smali文件的dopay函數開頭,注入如下代碼:

    const/4 v0, 0x1  
    invoke-static {v0}, Lcom/zplay/iap/ZplayJNI;->sendMessage(I)V
    return-void

原理很簡單,這個遊戲使用了Zplay的支付系統,在Java層的處理了支付邏輯,如果覺得Smali讀起來費勁,那麼直接使用dex2jar就能很清楚的看到,支付提醒甚至是中文的。Java層處理完支付邏輯後會給C++層丟個消息,調用C++層的代碼去處理遊戲邏輯,比如成功支付,那麼幸運星就會相應地增加。這裏使用native方法進行處理。

所以注入的代碼是,一旦進入支付邏輯,直接返回成功,同時強制返回函數,這就實現了支付的破解。當然作爲一個有節操,有逼格的遊戲從業者,這裏就不放出破解版了(不過說得也夠明白了)。查找注入點這東西靠的是耐心,細心和運氣。

爲了方便,一般會先用正常的java寫一些調試類,然後反編譯出靜態的smali放入目標文件夾中以供調試使用。

再放張圖,星星用不完了:


6.總結

就像上面說的,Smali能做的不僅僅是這些。有興趣的可以看看這篇文章《支付寶錢包手勢密碼破解實戰》,我沒有去驗證過真僞,但是如作者所描述的應該是可行無誤。這裏用到的一個更高級的功能是將支付寶的加密解密邏輯Smali的jar包導入自己新建的工程,進而直接在自己的程序中集成支付寶的加密解密功能。

在逆向分析遊戲的過程中,我也發現了幾個重要的點能幫助開發者提高自己程序的安全性。

首先,完全避免破解是不可能的,我們能做的工作就是盡最大可能去妨礙破解者破解遊戲,提高破解成本。

  • 一定要使用混淆。不單單是第三方SDK,你的代碼也是。破解遊戲很重要一點就是要抓住遊戲的邏輯。代碼混淆後,Smali更加晦澀難懂,邏輯也更難掌握。
  • 回到開頭的話,解讀彙編比解讀Smali難度大的多得多。所以重要的邏輯可以放到C/C++層去處理就不要放在Java層上去處理。
  • 多用連續調用的方式。這樣出來的效果是Java只有一行,Smali可能有好幾十行,看着都蛋疼。當然這對熟練的破解老手無效~
  • 在一些關鍵的點上,比如支付,多繞一下。而不是像《消滅小星星》這樣,直接在Java內用中文顯示“支付成功”,同時去調用JNI方法。用dex2jar看一眼就暴露了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章