利用FRIDA攻击Android应用程序(二)
在本系列文章的第一篇中,我們已經(jīng)對(duì)Frida的原理進(jìn)行了詳細(xì)的介紹,現(xiàn)在,我們將演示如何通過Frida搞定crackme問題。有了第一篇的內(nèi)容作為基礎(chǔ),理論上講這應(yīng)該不是什么難事。如果你想親自動(dòng)手完成本文介紹的實(shí)驗(yàn)的話,請(qǐng)下載?
OWASP Uncrackable Crackme Level 1?(APK)
BytecodeViewer
dex2jar
當(dāng)然,這里假定您已在計(jì)算機(jī)上成功地安裝了Frida(版本9.1.16或更高版本),并在(已經(jīng)獲得root權(quán)限的)設(shè)備上啟動(dòng)了相應(yīng)服務(wù)器的二進(jìn)制代碼。我們這里將在模擬器中使用Android 7.1.1 ARM映像。
然后,請(qǐng)?jiān)谀脑O(shè)備上安裝Uncrackable Crackme Level 1應(yīng)用程序:?
adb install sg.vantagepoint.uncrackable1.apk
安裝完成后,從模擬器的菜單(右下角的橙色圖標(biāo))啟動(dòng)它:?
一旦啟動(dòng)應(yīng)用程序,您就會(huì)注意到它不太樂意在已經(jīng)獲取root權(quán)限的設(shè)備上運(yùn)行:?
如果單擊“OK”,應(yīng)用程序會(huì)立即退出。嗯,不太友好啊。看起來我們無法通過這種方法來搞定crackme。真是這樣嗎?讓我們看看到底怎么回事,同時(shí)考察一下這個(gè)應(yīng)用程序的內(nèi)部運(yùn)行機(jī)制。
現(xiàn)在,使用dex2jar將apk轉(zhuǎn)換為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完成相應(yīng)的轉(zhuǎn)換。
為了使用CFR解碼器,需要在BytecodeViewer中依次選擇View-> Pane1-> CFR-> Java。如果你想將反編譯器的結(jié)果與Smali反匯編(通常比反編譯稍微準(zhǔn)確一些)進(jìn)行比較的話,可以將Pane2設(shè)置為Smali代碼。
下面是CFR解碼器針對(duì)應(yīng)用程序的MainActivity的輸出結(jié)果:?
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())通過查看其他反編譯的類文件,我們發(fā)現(xiàn)它是一個(gè)小應(yīng)用程序,并且貌似可以通過逆向解密例程和字符串修改例程來解決這個(gè)crackme問題。然而,既然有神器Frida在手,自然會(huì)有更方便的手段可供我們選擇。首先,讓我們看看這個(gè)應(yīng)用程序是在哪里檢查設(shè)備是否已獲取root權(quán)限的。在“Root detected”消息上面,我們可以看到:?
if (sg.vantagepoint.a.c.a() || sg.vantagepoint.a.c.b() || sg.vantagepoint.a.c.c())
如果你查看sg.vantagepoint.a.c類的話,你就會(huì)發(fā)現(xiàn)與root權(quán)限有關(guān)的各種檢查:?
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,這一點(diǎn)我們已經(jīng)在第一篇中介紹過了。但是,當(dāng)一個(gè)函數(shù)由于檢測(cè)到設(shè)備已經(jīng)取得了root權(quán)限而返回true時(shí),結(jié)果會(huì)怎樣呢? 正如我們?cè)贛ainActivity函數(shù)中看到的那樣,它會(huì)打開一個(gè)對(duì)話框。此外,它還會(huì)設(shè)置一個(gè)onClickListener,當(dāng)我們按下OK按鈕時(shí)就會(huì)觸發(fā)它:?
alertDialog.setButton(-3, (CharSequence)"OK", (DialogInterface.OnClickListener)new b(this));
這個(gè)onClickListener的實(shí)現(xiàn)代碼如下所示:?
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);
? ? }
}
它的功能并不復(fù)雜,實(shí)際上只是通過System.exit(0)退出應(yīng)用程序而已。所以我們要做的事情就是防止應(yīng)用程序退出。為此,我們可以用Frida覆蓋onClick方法。下面,讓我們創(chuàng)建一個(gè)文件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")
? ? })
})
如果你已經(jīng)閱讀了本系列文章的第一篇的話,這個(gè)腳本應(yīng)該不難理解:將我們的代碼封裝到setImmediate函數(shù)中,以防止超時(shí),然后通過Java.perform來使用Frida用于處理Java的方法。接下來,我們將得到一個(gè)類的包裝器,可用于實(shí)現(xiàn)OnClickListener接口并覆蓋其onClick方法。在我們的版本中,這個(gè)函數(shù)只是向控制臺(tái)寫一些輸出。與之前不同的是,它不會(huì)退出應(yīng)用程序。由于原來的onClickHandler被替換為Frida注入的函數(shù),因此它絕對(duì)不會(huì)被調(diào)用了,所以當(dāng)我們點(diǎn)擊對(duì)話框的OK按鈕時(shí),應(yīng)用程序就不退出了。好了,讓我們實(shí)驗(yàn)一下:打開應(yīng)用程序(使其顯示“Root detected”對(duì)話框)?
并注入腳本:?
frida -U -l uncrackable1.js sg.vantagepoint.uncrackable1
Frida注入代碼需要幾秒鐘的時(shí)間,當(dāng)你看到“onClick handler modified”消息時(shí)說明注入完成了(當(dāng)然,注入完成時(shí)你也可以得到一個(gè)shell之前,因?yàn)榭梢园盐覀兊拇a放入一個(gè)setImmediate包裝器中,從而讓Frida在后臺(tái)執(zhí)行它)。
然后,點(diǎn)擊應(yīng)用程序中的OK按鈕。如果一切順利的話,應(yīng)用程序就不會(huì)退出了。
我們看到對(duì)話框消失了,這樣我們就可以輸入密碼了。下面讓我們輸入一些內(nèi)容,點(diǎn)擊Verify,看看會(huì)發(fā)生什么情況:?
不出所料,這是一個(gè)錯(cuò)誤的密碼。但是這并不要緊,因?yàn)槲覀冋嬲业氖?#xff1a;加密/解密例程以及結(jié)果和輸入的比對(duì)。
再次檢查MainActivity時(shí),我們注意到了下面的函數(shù)?
public void verify(View object) {
它調(diào)用了類sg.vantagepoint.uncrackable1.a的方法:?
if (a.a((String)object)) {
下面是sg.vantagepoint.uncrackable1.a類的反編譯結(jié)果:?
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代碼塊中字符串a(chǎn)rrby2的創(chuàng)建。arrby2是函數(shù)sg.vantagepoint.a.a.a的返回值。string.equals會(huì)將我們的輸入與arrby2進(jìn)行比較。所以,我們要追蹤sg.vantagepoint.a.a的返回值。
現(xiàn)在,我們可以著手對(duì)這些字符串操作函數(shù)和解密函數(shù)進(jìn)行逆向工程,并處理原始加密字符串了,實(shí)際上它們也包含在上面的代碼中。或者,我們還可以讓應(yīng)用程序替我們完成字符串的處理和加密工作,而我們只要鉤住sg.vantagepoint.a.a.a函數(shù)來捕獲其返回值就可以坐享其成了。返回值是我們的輸入將要與之比較的解密字符串(它以字節(jié)數(shù)組的形式返回)。具體可以參考下面的腳本:?
? ? ? ? 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函數(shù),截獲其返回值并將其轉(zhuǎn)換為可讀字符串。這正是我們要找的解密字符串,所以我們將其打印到控制臺(tái)。
將上述代碼放到一起,就組成了一個(gè)完整的腳本:?
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");
? ? });
});
現(xiàn)在,我們來運(yùn)行這個(gè)腳本。然后,將其保存為uncrackable1.js,并執(zhí)行下列命令(如果Frida沒有自動(dòng)重新運(yùn)行的話)?
frida -U -l uncrackable1.js sg.vantagepoint.uncrackable1
耐心等待,直到您看到消息sg.vantagepoint.a.a發(fā)生變化,然后在Root detected對(duì)話框中單擊OK,在secret code中輸入一些字符,然后按Verify按鈕。哎,運(yùn)氣好像不太好啊。
但是,請(qǐng)注意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
太好了。我們實(shí)際上已經(jīng)得到了解密的字符串:I want to believe。那么,我們趕緊輸入這個(gè)字符串,看看是否正確:?
本文到此結(jié)束,但愿讀者閱讀本文后,能夠?qū)W(xué)習(xí)Frida的動(dòng)態(tài)二進(jìn)制插樁功能有所幫助。
文章傳送門:【技術(shù)分享】利用FRIDA攻擊Android應(yīng)用程序(一)
*?原文出處:https://www.codemetrix.net/hacking-android-apps-with-frida-2/,轉(zhuǎn)載自安全客
總結(jié)
以上是生活随笔為你收集整理的利用FRIDA攻击Android应用程序(二)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 利用FRIDA攻击Android应用程序
- 下一篇: 浅谈android hook技术