Security Tech

OWASP - MASTG UnCrackable-LEVEL 3 (1)

작성자 - 5rocks_

Ⅰ. UnCrackable

   1. 개요

GitHub Logo
Crackmes/Android/Level_03//UnCrackable-Level3.apk
OWASP/owasp-mastg
GitHub 미리보기

      1. OWASTP MASTG(Mobile Application Security Testing Guide) 에서 역공학을 통해 모바일 진단을 실습이            가능한 앱

      2. 아래의 페이지 처럼 AOS, IOS 단계별로 문제가 제공


Ⅱ. Uncracable AOS - LEVEL 3

   1. 정적 분석

      - Rooting 탐지

      - 디버깅 탐지

      - 무결성 검사

 

   2. 동적 분석

      - Frida 탐지

 

   3. Frida 탐지 우회

      - fgets 우회

 

   4. Rooting 탐지 우회

      - su 파일 탐지 우회

      - /system/build.prop ⭢ test-key 탐지 우회

      - 바이너리 파일 탐지 우회

 


Ⅲ. 정적 분석

   1. MainActivity (1~53)

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "UnCrackable3";
    static int tampered = 0;
    private static final String xorkey = "pizzapizzapizzapizzapizz";
    private CodeCheck check;
    Map<String, Long> crc;

    private native long baz();

    private native void init(byte[] bArr);

    /* JADX INFO: Access modifiers changed from: private */
    public void showDialog(String str) {
        AlertDialog create = new AlertDialog.Builder(this).create();
        create.setTitle(str);
        create.setMessage("This is unacceptable. The app is now going to exit.");
        create.setButton(-3, "OK", new DialogInterface.OnClickListener() { // from class: sg.vantagepoint.uncrackable3.MainActivity.1
            @Override // android.content.DialogInterface.OnClickListener
            public void onClick(DialogInterface dialogInterface, int i) {
                System.exit(0);
            }
        });
        create.setCancelable(false);
        create.show();
    }

 

      - tampered 변수명 ⭢ 무결성 탐지 추측

      - xorkey 변수명 ⭢ xor 암/복호화에 사용하는 키 추측

      - 사용자의 어떤 조작 있을 시 해당 OK 버튼 클릭 후 System.exit(0)을 호출 하여 앱 강제 종료

 

   2. verifyLibs() Method (54 ~ 99)

    private void verifyLibs() {
        this.crc = new HashMap();
        this.crc.put("armeabi-v7a", Long.valueOf(Long.parseLong(getResources().getString(owasp.mstg.uncrackable3.R.string.armeabi_v7a))));
        this.crc.put("arm64-v8a", Long.valueOf(Long.parseLong(getResources().getString(owasp.mstg.uncrackable3.R.string.arm64_v8a))));
        this.crc.put("x86", Long.valueOf(Long.parseLong(getResources().getString(owasp.mstg.uncrackable3.R.string.x86))));
        this.crc.put("x86_64", Long.valueOf(Long.parseLong(getResources().getString(owasp.mstg.uncrackable3.R.string.x86_64))));
        try {
            ZipFile zipFile = new ZipFile(getPackageCodePath());
            for (Map.Entry<String, Long> entry : this.crc.entrySet()) {
                String str = "lib/" + entry.getKey() + "/libfoo.so";
                ZipEntry entry2 = zipFile.getEntry(str);
                Log.v(TAG, "CRC[" + str + "] = " + entry2.getCrc());
                if (entry2.getCrc() != entry.getValue().longValue()) {
                    tampered = 31337;
                    Log.v(TAG, str + ": Invalid checksum = " + entry2.getCrc() + ", supposed to be " + entry.getValue());
                }
            }
            ZipEntry entry3 = zipFile.getEntry("classes.dex");
            Log.v(TAG, "CRC[classes.dex] = " + entry3.getCrc());
            if (entry3.getCrc() != baz()) {
                tampered = 31337;
                Log.v(TAG, "classes.dex: crc = " + entry3.getCrc() + ", supposed to be " + baz());
            }
        } catch (IOException unused) {
            Log.v(TAG, "Exception");
            System.exit(0);
        }
    }

 

      - 실행하는 모바일 기기의 아키텍처(ABI) 확인

getprop ro.product.cpu.abi 명령어를 통해 확인
armeabi-v7a: 32비트 ARM 프로세서용 (대부분의 구형 안드로이드 기기)
arm64-v8a: 64비트 ARM 프로세서용 (최신 안드로이드 기기)
x86: 32비트 인텔/AMD 프로세서용 (일부 태블릿, 에뮬레이터)
x86_64: 64비트 인텔/AMD 프로세서용 (일부 태블릿, 에뮬레이터)

 

      - tampered 변수가 31337 값을 할당하면 앱 변조를 탐지

 

   3. onCreate() Method (103 ~ 134)

    public void onCreate(Bundle bundle) {
        verifyLibs();
        init(xorkey.getBytes());
        new AsyncTask<Void, String, String>() { // from class: sg.vantagepoint.uncrackable3.MainActivity.2
            /* JADX INFO: Access modifiers changed from: protected */
            @Override // android.os.AsyncTask
            public String doInBackground(Void... voidArr) {
                while (!Debug.isDebuggerConnected()) {
                    SystemClock.sleep(100L);
                }
                return null;
            }

            /* JADX INFO: Access modifiers changed from: protected */
            @Override // android.os.AsyncTask
            public void onPostExecute(String str) {
                MainActivity.this.showDialog("Debugger detected!");
                System.exit(0);
            }
        }.execute(null, null, null);
        if (RootDetection.checkRoot1() || RootDetection.checkRoot2() || RootDetection.checkRoot3() || IntegrityCheck.isDebuggable(getApplicationContext()) || tampered != 0) {
            showDialog("Rooting or tampering detected.");
        }
        this.check = new CodeCheck();
        super.onCreate(bundle);
        setContentView(owasp.mstg.uncrackable3.R.layout.activity_main);
    }

 

      - onCreate: 안드로이드 액티비가 처음 실행 될 때 호출 되는 함수

      - checkRoot1, checkRoot2, checkRoot3, isDebuggable 메서드를 통해 루팅 및 디버깅 탐지

      3-1. RootDetection Class

package sg.vantagepoint.util;

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

/* loaded from: classes.dex */
public class RootDetection {
    public static boolean checkRoot1() {
        for (String str : System.getenv("PATH").split(":")) {
            if (new File(str, "su").exists()) {
                return true;
            }
        }
        return false;
    }

    public static boolean checkRoot2() {
        String str = Build.TAGS;
        return str != null && str.contains("test-keys");
    }

    public static boolean checkRoot3() {
        for (String str : 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/"}) {
            if (new File(str).exists()) {
                return true;
            }
        }
        return false;
    }
}

 

         - checkRoot1메서드 에서는 루팅된 안드로이드 폰에서만 su 파일이 생성되어

           해당 파일의 존재 여부 확인

         - checkRoot2메서드 에서는 /system/build.prop 파일에 test-keys 가 있는지 확인

         - cehckRoot3 메서드 에서는 루팅 기기에서 사용 되는 바이너리 파일 경로 확인

 

"/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/"

 

 

      3-2. IntegerityCheck Class

public class IntegrityCheck {
    public static boolean isDebuggable(Context context) {
        return (context.getApplicationContext().getApplicationInfo().flags & 2) != 0;
    }
}

 

          - IntegrityCheck 메서드의 getApplicationInfo().flags 값을 통해 플래그가 2(디버거 실행중)인지 확인

 

   4. verify() 메서드 분석 (137 ~  )

    public void verify(View view) {
        String obj = ((EditText) findViewById(owasp.mstg.uncrackable3.R.id.edit_text)).getText().toString();
        AlertDialog create = new AlertDialog.Builder(this).create();
        if (this.check.check_code(obj)) {
            create.setTitle("Success!");
            create.setMessage("This is the correct secret.");
        } else {
            create.setTitle("Nope...");
            create.setMessage("That's not it. Try again.");
        }
        create.setButton(-3, "OK", new DialogInterface.OnClickListener() { // from class: sg.vantagepoint.uncrackable3.MainActivity.3
            @Override // android.content.DialogInterface.OnClickListener
            public void onClick(DialogInterface dialogInterface, int i) {
                dialogInterface.dismiss();
            }
        });
        create.show();
    }

  

      - check_code()를 통해 해당 문제 코드 

 


Ⅳ. 동적 분석 및 보안 로직 우회

   1. Rooting Detection

      - Rooting 된 폰을 통해 앱 접근 시 루팅 및 무결성 검증 메시지 확인

[그림 1] Rooting Detecion


   2. Frida Detection

   - /lib/arm64/libfoo.so 네이티브 라이브러리 파일의 goodbye()함수가 호출되는 부분에서 문제 발생 확인

[그림 2] Frida Detection

 

      2-1. radare2 역공학 도구를 활용한 분석

         - r2 [so 파일명] 

         - aaa 상세분석

[그림 3] r2-Analyze all

 

         - afl: 전체 함수 목록 조회 ⭢ goodbye 함수 확인

[그림 4] goodbye 함수 확인

 

         - pd @0x00003080  goodbye 함수 디스어셈블

[그림 5] goodby 함수 디스어셈블

 

         - goodbye 함수에서 /proc/self/maps에서 frida, xposed 문자열 탐지 하고 있음을 확인

[그림 6] frida, xposed 문자열 탐지 로직 분석

 

      2-2. Frida를 통한 fgets 문자열 출력

         - Frida를 통해서 fgets 첫번째 매개변수 출력해 문자열을 frida, xposed 탐지로 인해 Frida가 종료되는지 확인

Interceptor.attach(Module.findExportByName(null, 'fgets'), {
    onEnter: function (args) {
        this.buffer = args[0];
    },
    onLeave: function (retval) {
        if (!retval.isNull()) {
            const content = Memory.readUtf8String(this.buffer);
            console.log("fgets 버퍼 문자열 - " + content);
        }
    }
});

 

         - Frida 문자열 읽은 이후 Frida가 종료되는것을 확인

fgets 버퍼 문자열 - 75191c7000-751938c000 rw-p 00000000 103:0f 2228329                       /data/app/~~DmwAGGEkYU6cOddmjpNPhQ==/owasp.mstg.uncrackable3-yhjaAh8_uuEIcIBavSIJTg==/oat/arm64/base.vdex

fgets 버퍼 문자열 - 751938c000-751938d000 r--p 0003c000 103:0f 2228326                       /data/app/~~DmwAGGEkYU6cOddmjpNPhQ==/owasp.mstg.uncrackable3-yhjaAh8_uuEIcIBavSIJTg==/oat/arm64/base.odex

fgets 버퍼 문자열 - 751938d000-751938e000 rw-p 0003d000 103:0f 2228326                       /data/app/~~DmwAGGEkYU6cOddmjpNPhQ==/owasp.mstg.uncrackable3-yhjaAh8_uuEIcIBavSIJTg==/oat/arm64/base.odex
fgets 버퍼 문자열 - 7529058000-7529a52000 r--p 00000000 00:06 278932                         /memfd:frida-agent-64.so (deleted)

Process crashed: Trace/BPT trap

***
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'samsung/beyond1lteks/beyond1:12/SP1A.210812.016/G973NKSU7HVJ5:user/release-keys'
Revision: '26'
ABI: 'arm64'
Processor: '6'
Timestamp: 2025-04-27 15:51:33.213545384+0900
Process uptime: 1s
Cmdline: owasp.mstg.uncrackable3
pid: 14710, tid: 14744, name: tg.uncrackable3  >>> owasp.mstg.uncrackable3 <<<
uid: 10294
    lr  0000007585cd7090  sp  0000007585ccfa00  pc  00000078980fa068  pst 0000000000000000
backtrace:
      #00 pc 00000000000a0068  /apex/com.android.runtime/lib64/bionic/libc.so!libc.so (tgkill+8) (BuildId: 88b933fc529619f5e58bc6d5510017ee)

   3. Frida Detection Bypass

      - sp에 frida, xposed 문자열을 읽으면 buffer 값을 빈값으로 출력하여 프리다 탐지 우회

Interceptor.attach(Module.findExportByName(null, 'fgets'), {
    onEnter: function (args) {
        this.buffer = args[0];
    },
    onLeave: function (retval) {
        if (!retval.isNull()) {
            const content = Memory.readUtf8String(this.buffer);

            if (content.indexOf("frida") !== -1 || content.indexOf("xposed") !== -1) {
                Memory.writeUtf8String(this.buffer, "");
            }
        }
    }
});

 

      - frida 탐지 우회에 성공 하여 강제종료되지 않는것을 확인

[그림 7] Frida 탐지 우회 확인


   4. Rooting Detection Bypass

      - frida 탐지와 정적 분석을 통한 Rooting 탐지 우회 frida 코드를 작성

      - checkRoot1의 환경변수를 통해 su 파일 탐지 하므로 환경변수 변조

      - checkRoot2의 test-keys를 통해 탐지 하므로 mss-key로 변조

      - checkRoot3의 해당 바이너리 파일 탐지 못하도록 변조

Java.perform(function() {
// Frida 탐지 우회
    Interceptor.attach(Module.findExportByName(null, 'fgets'), {
        onEnter: function (args) {
            this.buffer = args[0];
        },
        onLeave: function (retval) {
            if (!retval.isNull()) {
                const content = Memory.readUtf8String(this.buffer);

                if (content.indexOf("frida") !== -1 || content.indexOf("xposed") !== -1) {
                    Memory.writeUtf8String(this.buffer, "");
                }
            }
        }
    });

// Rooting 탐지 우회
    var RootDetection = Java.use('sg.vantagepoint.util.RootDetection');
    RootDetection.checkRoot1.implementation = function() {
        var System = Java.use('java.lang.System');
        var getenv = System.getenv.overload('java.lang.String'); 
        
        getenv.implementation = function(name) {
            if (name === "PATH") {
                return "/mokpo/security/story"; 
            }
            return getenv.call(this, name);
        };
        
        return false;
    };

    RootDetection.checkRoot2.implementation = function() {
        var Build = Java.use('android.os.Build');
        
        Object.defineProperty(Build, 'TAGS', {
            get: function() {
                return 'mss-keys'; 
            }
        });
        
        return false;
    };

    RootDetection.checkRoot3.implementation = function() {
        var File = Java.use('java.io.File');

        File.exists.implementation = function() {
            var path = this.getAbsolutePath();

            var bynaryFile = [
                "Superuser.apk",
                "daemonsu",
                ".su",
                "99SuperSUDaemon",
                ".has_su_daemon",
                ".installed_su_daemon",
                "/dev/com.koushikdutta.superuser.daemon/"
            ];

            for (var i = 0; i < bynaryFile.length; i++) {
                if (path.indexOf(bynaryFile[i]) !== -1) {
                    return false; 
                }
            }
            
            return this.exists();
        };
        
        return false;
    };
});

Ⅴ. 우회 완료

[그림 8] Frida, Rooting 탐지 우회 확인


Ⅵ. 추후 진단

      - Secret String 찾기

 

'Security Tech' 카테고리의 다른 글

스택 구조 그리기  (0) 2025.04.30
V2X (Vehicle-to-Everything) 통신  (0) 2025.02.28
API 취약점 진단  (0) 2025.02.07
Cursor AI를 활용한 Macie 대체 서비스 개발기  (0) 2025.01.31
CAN 통신 프로토콜  (0) 2025.01.31
Contents

이 글이 도움이 되었다면, 응원의 댓글 부탁드립니다.