利用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 http://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的動態二進制插樁功能有所幫助。
文章傳送門:【技術分享】利用FRIDA攻擊Android應用程序(一)
*?原文出處:https://www.codemetrix.net/hacking-android-apps-with-frida-2/,轉載自安全客
總結
以上是生活随笔為你收集整理的利用FRIDA攻击Android应用程序(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 利用FRIDA攻击Android应用程序
- 下一篇: Android无需权限显示悬浮窗, 兼谈