閱讀707 返回首頁    go 阿裏雲 go 技術社區[雲棲]


利用FRIDA攻擊Android應用程序(二)

本係列文章的第一篇中,我們已經對Frida的原理進行了詳細的介紹,現在,我們將演示如何通過Frida搞定crackme問題。有了第一篇的內容作為基礎,理論上講這應該不是什麼難事。如果你想親自動手完成本文介紹的實驗的話,請下載 

OWASP Uncrackable Crackme Level 1 (APK)

BytecodeViewer

dex2jar


當然,這裏假定您已在計算機上成功地安裝了Frida(版本9.1.16或更高版本),並在(已經獲得root權限的)設備上啟動了相應服務器的二進製代碼。我們這裏將在模擬器中使用Android 7.1.1 ARM映像。

然後,請在您的設備上安裝Uncrackable Crackme Level 1應用程序: 

adb install sg.vantagepoint.uncrackable1.apk


安裝完成後,從模擬器的菜單(右下角的橙色圖標)啟動它: 


一旦啟動應用程序,您就會注意到它不太樂意在已經獲取root權限的設備上運行: 


如果單擊“OK”,應用程序會立即退出。嗯,不太友好啊。看起來我們無法通過這種方法來搞定crackme。真是這樣嗎?讓我們看看到底怎麼回事,同時考察一下這個應用程序的內部運行機製。


現在,使用dex2jar將apk轉換為jar文件: 

michael@sixtyseven:/opt/dex2jar/dex2jar-2.0$ ./d2j-dex2jar.sh -o /home/michael/UnCrackable-Level1.jar /home/michael/UnCrackable-Level1.apk 
dex2jar /home/michael/UnCrackable-Level1.apk -> /home/michael/UnCrackable-Level1.jar


然後,將其加載到BytecodeViewer(或其他支持Java的反匯編器)中。你也可以嚐試直接加載到BytecodeViewer中,或直接提取classes.dex,但是試了一下好像此路不通,所以我才提前使用dex2jar完成相應的轉換。


為了使用CFR解碼器,需要在BytecodeViewer中依次選擇View-> Pane1-> CFR-> Java。如果你想將反編譯器的結果與Smali反匯編(通常比反編譯稍微準確一些)進行比較的話,可以將Pane2設置為Smali代碼。



下麵是CFR解碼器針對應用程序的MainActivity的輸出結果: 

package sg.vantagepoint.uncrackable1;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.text.Editable;
import android.view.View;
import android.widget.EditText;
import sg.vantagepoint.uncrackable1.a;
import sg.vantagepoint.uncrackable1.b;
import sg.vantagepoint.uncrackable1.c;
public class MainActivity
extends Activity {
    private void a(String string) {
        AlertDialog alertDialog = new AlertDialog.Builder((Context)this).create();
        alertDialog.setTitle((CharSequence)string);
        alertDialog.setMessage((CharSequence)"This in unacceptable. The app is now going to exit.");
        alertDialog.setButton(-3, (CharSequence)"OK", (DialogInterface.OnClickListener)new b(this));
        alertDialog.show();
    }
    protected void onCreate(Bundle bundle) {
        if (sg.vantagepoint.a.c.a() || sg.vantagepoint.a.c.b() || sg.vantagepoint.a.c.c()) {
            this.a("Root detected!"); //This is the message we are looking for
        }
        if (sg.vantagepoint.a.b.a((Context)this.getApplicationContext())) {
            this.a("App is debuggable!");
        }
        super.onCreate(bundle);
        this.setContentView(2130903040);
    }
    public void verify(View object) {
        object = ((EditText)this.findViewById(2131230720)).getText().toString();
        AlertDialog alertDialog = new AlertDialog.Builder((Context)this).create();
        if (a.a((String)object)) {
            alertDialog.setTitle((CharSequence)"Success!");
            alertDialog.setMessage((CharSequence)"This is the correct secret.");
        } else {
            alertDialog.setTitle((CharSequence)"Nope...");
            alertDialog.setMessage((CharSequence)"That's not it. Try again.");
        }
        alertDialog.setButton(-3, (CharSequence)"OK", (DialogInterface.OnClickListener)new c(this));
        alertDialog.show();
    }
}


if (sg.vantagepoint.a.c.a() || sg.vantagepoint.a.c.b() || sg.vantagepoint.a.c.c())通過查看其他反編譯的類文件,我們發現它是一個小應用程序,並且貌似可以通過逆向解密例程和字符串修改例程來解決這個crackme問題。然而,既然有神器Frida在手,自然會有更方便的手段可供我們選擇。首先,讓我們看看這個應用程序是在哪裏檢查設備是否已獲取root權限的。在“Root detected”消息上麵,我們可以看到: 

if (sg.vantagepoint.a.c.a() || sg.vantagepoint.a.c.b() || sg.vantagepoint.a.c.c())


如果你查看sg.vantagepoint.a.c類的話,你就會發現與root權限有關的各種檢查: 

public static boolean a()
    {
        String[] a = System.getenv("PATH").split(":");
        int i = a.length;
        int i0 = 0;
        while(true)
        {
            boolean b = false;
            if (i0 >= i)
            {
                b = false;
            }
            else
            {
                if (!new java.io.File(a[i0], "su").exists())
                {
                    i0 = i0 + 1;
                    continue;
                }
                b = true;
            }
            return b;
        }
    }
    public static boolean b()
    {
        String s = android.os.Build.TAGS;
        if (s != null && s.contains((CharSequence)(Object)"test-keys"))
        {
            return true;
        }
        return false;
    }
    public static boolean c()
    {
        String[] a = new String[7];
        a[0] = "/system/app/Superuser.apk";
        a[1] = "/system/xbin/daemonsu";
        a[2] = "/system/etc/init.d/99SuperSUDaemon";
        a[3] = "/system/bin/.ext/.su";
        a[4] = "/system/etc/.has_su_daemon";
        a[5] = "/system/etc/.installed_su_daemon";
        a[6] = "/dev/com.koushikdutta.superuser.daemon/";
        int i = a.length;
        int i0 = 0;
        while(i0 < i)
        {
            if (new java.io.File(a[i0]).exists())
            {
                return true;
            }
            i0 = i0 + 1;
        }
        return false;
    }


在Frida的幫助下,我們可以通過覆蓋它們使所有這些方法全部返回false,這一點我們已經在第一篇中介紹過了。但是,當一個函數由於檢測到設備已經取得了root權限而返回true時,結果會怎樣呢? 正如我們在MainActivity函數中看到的那樣,它會打開一個對話框。此外,它還會設置一個onClickListener,當我們按下OK按鈕時就會觸發它: 

alertDialog.setButton(-3, (CharSequence)"OK", (DialogInterface.OnClickListener)new b(this));


這個onClickListener的實現代碼如下所示: 

package sg.vantagepoint.uncrackable1;
class b implements android.content.DialogInterface$OnClickListener {
    final sg.vantagepoint.uncrackable1.MainActivity a;
    b(sg.vantagepoint.uncrackable1.MainActivity a0)
    {
        this.a = a0;
        super();
    }
    public void onClick(android.content.DialogInterface a0, int i)
    {
        System.exit(0);
    }
}


它的功能並不複雜,實際上隻是通過System.exit(0)退出應用程序而已。所以我們要做的事情就是防止應用程序退出。為此,我們可以用Frida覆蓋onClick方法。下麵,讓我們創建一個文件uncrackable1.js,並把我們的代碼放入其中: 

setImmediate(function() { //prevent timeout
    console.log("[*] Starting script");
    Java.perform(function() {
      bClass = Java.use("sg.vantagepoint.uncrackable1.b");
      bClass.onClick.implementation = function(v) {
         console.log("[*] onClick called");
      }
      console.log("[*] onClick handler modified")
    })
})


如果你已經閱讀了本係列文章的第一篇的話,這個腳本應該不難理解:將我們的代碼封裝到setImmediate函數中,以防止超時,然後通過Java.perform來使用Frida用於處理Java的方法。接下來,我們將得到一個類的包裝器,可用於實現OnClickListener接口並覆蓋其onClick方法。在我們的版本中,這個函數隻是向控製台寫一些輸出。與之前不同的是,它不會退出應用程序。由於原來的onClickHandler被替換為Frida注入的函數,因此它絕對不會被調用了,所以當我們點擊對話框的OK按鈕時,應用程序就不退出了。好了,讓我們實驗一下:打開應用程序(使其顯示“Root detected”對話框) 



並注入腳本: 

frida -U -l uncrackable1.js sg.vantagepoint.uncrackable1


Frida注入代碼需要幾秒鍾的時間,當你看到“onClick handler modified”消息時說明注入完成了(當然,注入完成時你也可以得到一個shell之前,因為可以把我們的代碼放入一個setImmediate包裝器中,從而讓Frida在後台執行它)。



然後,點擊應用程序中的OK按鈕。如果一切順利的話,應用程序就不會退出了。



我們看到對話框消失了,這樣我們就可以輸入密碼了。下麵讓我們輸入一些內容,點擊Verify,看看會發生什麼情況: 



不出所料,這是一個錯誤的密碼。但是這並不要緊,因為我們真正要找的是:加密/解密例程以及結果和輸入的比對。

再次檢查MainActivity時,我們注意到了下麵的函數 

public void verify(View object) {


它調用了類sg.vantagepoint.uncrackable1.a的方法: 

if (a.a((String)object)) {


下麵是sg.vantagepoint.uncrackable1.a類的反編譯結果: 

package sg.vantagepoint.uncrackable1;
import android.util.Base64;
import android.util.Log;
/*
 * Exception performing whole class analysis ignored.
 */
public class a {
    public static boolean a(String string) {
        byte[] arrby = Base64.decode((String)"5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", (int)0);
        byte[] arrby2 = new byte[]{};
        try {
            arrby2 = arrby = sg.vantagepoint.a.a.a((byte[])a.b((String)"8d127684cbc37c17616d806cf50473cc"), (byte[])arrby);
        }
        catch (Exception var2_2) {
            Log.d((String)"CodeCheck", (String)("AES error:" + var2_2.getMessage()));
        }
        if (!string.equals(new String(arrby2))) return false;
        return true;
    }
    public static byte[] b(String string) {
        int n = string.length();
        byte[] arrby = new byte[n / 2];
        int n2 = 0;
        while (n2 < n) {
            arrby[n2 / 2] = (byte)((Character.digit(string.charAt(n2), 16) << 4) + Character.digit(string.charAt(n2 + 1), 16));
            n2 += 2;
        }
        return arrby;
    }
}


注意在a方法末尾的string.equals比較,以及在上麵的try代碼塊中字符串arrby2的創建。arrby2是函數sg.vantagepoint.a.a.a的返回值。string.equals會將我們的輸入與arrby2進行比較。所以,我們要追蹤sg.vantagepoint.a.a的返回值。


現在,我們可以著手對這些字符串操作函數和解密函數進行逆向工程,並處理原始加密字符串了,實際上它們也包含在上麵的代碼中。或者,我們還可以讓應用程序替我們完成字符串的處理和加密工作,而我們隻要鉤住sg.vantagepoint.a.a.a函數來捕獲其返回值就可以坐享其成了。返回值是我們的輸入將要與之比較的解密字符串(它以字節數組的形式返回)。具體可以參考下麵的腳本: 

        aaClass = Java.use("sg.vantagepoint.a.a");
        aaClass.a.implementation = function(arg1, arg2) {
            retval = this.a(arg1, arg2);
            password = ''
            for(i = 0; i < retval.length; i++) {
               password += String.fromCharCode(retval[i]);
            }
            console.log("[*] Decrypted: " + password);
            return retval;
        }
        console.log("[*] sg.vantagepoint.a.a.a modified");


其中,我們覆蓋了sg.vantagepoint.a.a.a函數,截獲其返回值並將其轉換為可讀字符串。這正是我們要找的解密字符串,所以我們將其打印到控製台。

將上述代碼放到一起,就組成了一個完整的腳本: 

setImmediate(function() {
    console.log("[*] Starting script");
    Java.perform(function() {
        bClass = Java.use("sg.vantagepoint.uncrackable1.b");
        bClass.onClick.implementation = function(v) {
         console.log("[*] onClick called.");
        }
        console.log("[*] onClick handler modified")
        aaClass = Java.use("sg.vantagepoint.a.a");
        aaClass.a.implementation = function(arg1, arg2) {
            retval = this.a(arg1, arg2);
            password = ''
            for(i = 0; i < retval.length; i++) {
               password += String.fromCharCode(retval[i]);
            }
            console.log("[*] Decrypted: " + password);
            return retval;
        }
        console.log("[*] sg.vantagepoint.a.a.a modified");
    });
});


現在,我們來運行這個腳本。然後,將其保存為uncrackable1.js,並執行下列命令(如果Frida沒有自動重新運行的話) 

frida -U -l uncrackable1.js sg.vantagepoint.uncrackable1


耐心等待,直到您看到消息sg.vantagepoint.a.a發生變化,然後在Root detected對話框中單擊OK,在secret code中輸入一些字符,然後按Verify按鈕。哎,運氣好像不太好啊。

但是,請注意Frida的輸出: 

michael@sixtyseven:~/Development/frida$ frida -U -l uncrackable1.js sg.vantagepoint.uncrackable1
     ____
    / _  |   Frida 9.1.16 - A world-class dynamic instrumentation framework
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://www.frida.re/docs/home/
[*] Starting script
[USB::Android Emulator 5554::sg.vantagepoint.uncrackable1]-> [*] onClick handler modified
[*] sg.vantagepoint.a.a.a modified
[*] onClick called.
[*] Decrypted: I want to believe


太好了。我們實際上已經得到了解密的字符串:I want to believe。那麼,我們趕緊輸入這個字符串,看看是否正確: 



本文到此結束,但願讀者閱讀本文後,能夠對學習Frida的動態二進製插樁功能有所幫助。


最後更新:2017-04-07 21:25:10

  上一篇:go 利用FRIDA攻擊Android應用程序(一)
  下一篇:go 實現一個購物網站的首頁的js代碼