CTF之安卓逆向uncrackme level1

安卓逆向方面好像沒有類似vulnhub的網站,有大量的靶機練習滲透測試。目前我找到了XCTF有逆向和移動端的題目,和 OWASP MSTG CTF項目。先從MSTG開始做完吧!

CTF項目的github在點擊這裏。不過由於項目較大,github經常會出現下載失敗的情況,像我就下載了一個上午,50m的帶寬下載速度只有20kb/s,速度極其感人。不過我已經上傳到CSDN了,有需要的小夥伴可以直接下載

運行

將UnCrackable-Level1.apk安裝到安卓設備。
adb install UnCrackable-Level1.apk

打開運行,存在root檢測,由於我的模擬器是已經root了的,所以點擊OK,程序它就自己退出了。
在這裏插入圖片描述
有兩種方法可以解決這個問題:

  • 關掉root權限
  • 繞過root檢測

第一種方法顯然不符合我氣質,那麼就開始進行檢測繞過。

反編譯靜態分析

我使用jeb進行反編譯,編譯後是smali彙編代碼,按tab可以將smali彙編轉成Java僞代碼。

.uncrackable1.MainActivity代碼部分

package sg.vantagepoint.uncrackable1;

import android.app.Activity;
import android.app.AlertDialog$Builder;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface$OnClickListener;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import sg.vantagepoint.a.b;
import sg.vantagepoint.a.c;

public class MainActivity extends Activity {
    public MainActivity() {
        super();
    }

    private void a(String arg4) {
        AlertDialog v0 = new AlertDialog$Builder(((Context)this)).create();
        v0.setTitle(((CharSequence)arg4));
        v0.setMessage("This is unacceptable. The app is now going to exit.");
        v0.setButton(-3, "OK", new DialogInterface$OnClickListener() {
            public void onClick(DialogInterface arg1, int arg2) {
                System.exit(0);
            }
        });
        v0.setCancelable(false);
        v0.show();
    }

    protected void onCreate(Bundle arg2) {
        if((c.a()) || (c.b()) || (c.c())) {
            this.a("Root detected!");
        }

        if(b.a(this.getApplicationContext())) {
            this.a("App is debuggable!");
        }

        super.onCreate(arg2);
        this.setContentView(0x7F030000);
    }

    public void verify(View arg4) {
        String v4 = this.findViewById(0x7F020001).getText().toString();
        AlertDialog v0 = new AlertDialog$Builder(((Context)this)).create();
        if(a.a(v4)) {
            v0.setTitle("Success!");
            v4 = "This is the correct secret.";
        }
        else {
            v0.setTitle("Nope...");
            v4 = "That\'s not it. Try again.";
        }

        v0.setMessage(((CharSequence)v4));
        v0.setButton(-3, "OK", new DialogInterface$OnClickListener() {
            public void onClick(DialogInterface arg1, int arg2) {
                arg1.dismiss();
            }
        });
        v0.show();
    }
}

程序首先運行onCreate函數,從onCreate一段一段分析看它幹了什麼。
首先,如果c類中的a,b,c方法中有一個條件滿足,那麼它就會進入MainActivity類的a方法,並傳入"Root detected!"字符串。

這就是程序一開始打開時的情況,所以我們先看看c類中的a、b、c方法和MainActivity.a方法分別做了些啥。
在這裏插入圖片描述

a類的代碼如下:

package sg.vantagepoint.a;

import android.os.Build;
import java.io.File;

public class c {
    public static boolean a() {
        String[] v0 = System.getenv("PATH").split(":");
        int v1 = v0.length;
        int v3;
        for(v3 = 0; v3 < v1; ++v3) {
            if(new File(v0[v3], "su").exists()) {
                return 1;
            }
        }

        return 0;
    }

    public static boolean b() {
        String v0 = Build.TAGS;
        if(v0 != null && (v0.contains("test-keys"))) {
            return 1;
        }

        return 0;
    }

    public static boolean c() {
        String[] v0 = new String[]{"/system/app/Superuser.apk", "/system/xbin/daemonsu", "/system/etc/init.d/99SuperSUDaemon", "/system/bin/.ext/.su", "/system/etc/.has_su_daemon", "/system/etc/.installed_su_daemon", "/dev/com.koushikdutta.superuser.daemon/"};
        int v1 = v0.length;
        int v3;
        for(v3 = 0; v3 < v1; ++v3) {
            if(new File(v0[v3]).exists()) {
                return 1;
            }
        }

        return 0;
    }
}
  • a方法檢測路徑中有沒有存在su這個文件,如果存在就判斷爲設備已經被root了
  • b方法檢查Build.TAGS中是否存在test-keys,如果存在就判斷設備已經被root了
  • c方法是在檢測一系列文件,如果有一個被找到就判斷設備已經被root。

很顯然,c類中使用3中方式對root進行檢測。

MainActivity.a方法代碼

private void a(String arg4) {
        AlertDialog v0 = new AlertDialog$Builder(((Context)this)).create();
        v0.setTitle(((CharSequence)arg4));
        v0.setMessage("This is unacceptable. The app is now going to exit.");
        v0.setButton(-3, "OK", new DialogInterface$OnClickListener() {
            public void onClick(DialogInterface arg1, int arg2) {
                System.exit(0);
            }
        });
        v0.setCancelable(false);
        v0.show();
    }

調用了此方法,函數會彈出對話框,並顯示傳入的字符串內容,如果點擊OK,程序將退出,System.exit(0)。

繞過root 檢測

回到onCreate中,經過靜態分析,有兩種辦法繞過root檢測。

  • 使用動態調試的方式,查看c類中a、b、c三個方法中哪一個方法返回了1,選擇hook修改返回值或者修改smali代碼重新打包,繞過root檢測。
  • 修改MainActivity.a函數,讓其不執行System.exit(0),而只是return void。需要修改smali代碼並重新打包。

目前選擇第二種方法,因爲它是最簡單的,只需要將System.exit(0)對應的smali彙編代碼註釋掉即可。但由於出於學習目的,後面文章我還會附上第一種方法的做法。

使用apktool將apk進行反編譯。

apktool d UnCrackable-Level1.apk -o uncrackable_dissas

在這裏插入圖片描述

smali代碼位於
uncrackable_dissas/smali/sg/vantagepoint/

在這裏插入圖片描述
可以看出和jeb反編譯的目錄是一樣的。
在這裏插入圖片描述
接下來要找smali代碼的注入點,我發現其實這一步是有技巧的,而且非常有用,這個項目比較小,所以找的比較快。如果是比較大的項目,沒有一定技巧是沒有那麼容易就能找到注入點的。

首先我們需要確定要修改的smali代碼在哪個文件裏,主要針對含有匿名內部類的Java文件而言。MainActivity方法被反編譯成MainActivity.smali、MainActivity$ 1.smali、MainActivity$ 2.smali。這裏MainActivity$ 1.smali、MainActivity$ 2.smali這些都是匿名內部類的smali代碼文件,由於沒有名字,所以編譯後只能用$XXX來區分。

然後使用vscode打開的smali文件和jeb打開的Java代碼進行上下文比對,就能找到注入點。(ps:推薦使用vscode打開smali,下載smali插件即可完美編輯)。

在這裏插入圖片描述

將41行進行註釋,那麼調用MainActivity.a方法後,點擊確定也不會退出程序,就可以進行繞過了。
在這裏插入圖片描述
重新進行打包

apktool b uncrackable_dissas -o modified_uncracjable.apk

然後進行簽名,就可以安裝運行在android設備上。
關於簽名我寫了一鍵簽名工具,直接拖入apk位置就可以自動完成簽名。

點擊了ok,程序也沒有退出,root檢測成功繞過!
在這裏插入圖片描述

拿到FLAG!

接下來在jeb中找到關鍵函數,verify函數很可疑,並且有Sucess提示。
關鍵是a.a()內部,讓其返回值爲真就可以。
在這裏插入圖片描述
進入a.a函數看看。

package sg.vantagepoint.uncrackable1;

import android.util.Base64;
import android.util.Log;

public class a {
    public static boolean a(String arg5) {
        byte[] v0_2;
        String v0 = "8d127684cbc37c17616d806cf50473cc";
        byte[] v1 = Base64.decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", 0);
        byte[] v2 = new byte[0];
        try {
            v0_2 = sg.vantagepoint.a.a.a(a.b(v0), v1);
        }
        catch(Exception v0_1) {
            Log.d("CodeCheck", "AES error:" + v0_1.getMessage());
            v0_2 = v2;
        }

        return arg5.equals(new String(v0_2));
    }

    public static byte[] b(String arg7) {
        int v0 = arg7.length();
        byte[] v1 = new byte[v0 / 2];
        int v2;
        for(v2 = 0; v2 < v0; v2 += 2) {
            v1[v2 / 2] = ((byte)((Character.digit(arg7.charAt(v2), 16) << 4) + Character.digit(arg7.charAt(v2 + 1), 16)));
        }

        return v1;
    }
}


函數內部實現比較簡單,主要是算法構成。
v1是字符串5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=進行base64解密後的byte數組。
v0也是一個常量。

這裏的關鍵點在於v0_2,這個是由加密最密集的地方產生的數據,而它也是最後和傳入的arg5進行比較的,所以我會把關注點放在產生v0_2的函數:sg.vantagepoint.a.a.a()

而它的函數實現如下:

package sg.vantagepoint.a;

import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class a {
    public static byte[] a(byte[] arg2, byte[] arg3) {
        SecretKeySpec v0 = new SecretKeySpec(arg2, "AES/ECB/PKCS7Padding");
        Cipher v2 = Cipher.getInstance("AES");
        v2.init(2, ((Key)v0));
        return v2.doFinal(arg3);
    }
}

這裏的方法有兩個參數、arg2和arg3。

首先使用arg2隨機生成一個key祕鑰,再使用祕鑰對arg3進行AES進行對稱加密,外部參數和它進行對比。

而這裏由於傳入的v0、v1都是常量,我們需要hook此方法,把這個固定的加密返回值打印出來即可,這就是crack的思路。

這裏使用frida工具進行解密,有關frida的介紹和文檔在這裏
貼上腳本:

Java.perform(function(){
    //hook the target class 
    var aes = Java.use("sg.vantagepoint.a.a");

    //hook the function inside the class
    aes.a.implementation = function(var0, var1){
        //call itself
        var decrypt = this.a(var0,var1);
        var flag = "";

        for (var i =0; i < decrypt.length; i++){
            flag += String.fromCharCode(decrypt[i]);
        }
        console.log(flag);
        return decrypt;
    }
});


找到運行的apk名稱,
在這裏插入圖片描述
執行,

frida -U -f owap.mstg.uncrackable1 -l exploit.js --no-pause

-U 代表進入USB設備
-f 代表指定應用程序文件
-l 指定腳本
–no-pause 在應用程序啓動後,自動加載到主進程中去。

當我們運行上面的命令行後,模擬器或手機APP會自動加載,

隨便在app中輸入

在這裏插入圖片描述
查看腳本打印…
在這裏插入圖片描述
ok,腳本將flag打印出來了。
在這裏插入圖片描述
拿到flag!

總結

  • 在進行靜態分析的時候,對一個問題進行多角度思考,有沒有更好的辦法能夠解決目前的問題
  • 找到smali注入點的技巧,熟能生巧,經驗活。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章