Android/Uncrackable

Uncrackable 1 - Root Detection

앱 정보

앱을 실행시켜보면 다음 그림과 같이 "Root detected"라는 창이 생기고 OK 버튼을 누르면 앱이 종료되는 것을 확인할 수 있다.

[그림 1] 앱 실행 직후

 

앱 분석

onCreate()

public void onCreate(Bundle bundle) {
        if (c.a() || c.b() || c.c()) {
            a("Root detected!");
        }
        if (b.a(getApplicationContext())) {
            a("App is debuggable!");
        }
        super.onCreate(bundle);
        setContentView(R.layout.activity_main);
    }
public class c {
    public static boolean a() {
        for (String file : System.getenv("PATH").split(":")) {
            if (new File(file, "su").exists()) {
                return true;
            }
        }
        return false;
    }

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

    public static boolean c() {
        for (String file : 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(file).exists()) {
                return true;
            }
        }
        return false;
    }
}

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

private void a(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() {
            public void onClick(DialogInterface dialogInterface, int i) {
                System.exit(0);
            }
        });
        create.setCancelable(false);
        create.show();
}

onCreate() 함수를 확인해보면 다음과 같은 정보를 알 수 있다.

  • c 클래스의 a(), b(), c() 함수를 통해 각각 환경변수, Build.TAGS, 루팅 관련 앱 유무를 통해 루팅 된 기기인지 아닌지 확인한다.
  • b 클래스의 a() 함수를 통해서는 디버그 모드 유무를 확인한다.
  • 만약 루팅 혹은 디버그가 감지되어 a() 함수가 실행이 되면 다음과 같이 진행된다.
    • 인자로 넘겨져온 문자열을 Title로 하는 메시지 박스를 생성 및 출력 후 만약 해당 박스에 "OK"버튼을 눌렀다면 exit() 함수를 통해 종료시킨다.

위 분석을 통해 알 수 있듯이 루팅 감지를 우회하기 위해서는 다음 두 가지 방법이 있다.

  1. c 클래스의 a(), b(), c() 함수를 후킹 하여 반환 값을 false로 고정시킨다.
  2. exit() 함수를 후킹하여 종료되지 않도록 한다.

 

Exploit

반환값 조정을 통한 루팅 우회

Exploit Code

console.log("Script loaded successfully");
Java.perform(function x() {
    console.log("java perform function");
    var sg = Java.use('sg.vantagepoint.a.c');
    sg.a.overload().implementation = function () {
        console.log("function a bypass");
        return false;
    };
    sg.b.overload().implementation = function () {
        console.log("function b bypass");
        return false;
    };
    sg.c.overload().implementation = function () {
        console.log("function c bypass");
        return false;
    };
});

우회를 위한 코드는 간단하게 c 클래스의 a(), b(), c() 함수를 후킹 한 후 반환 값을 false로 돌려주면 끝이다.

스크립트를 실행하면 다음과 같이 각 함수를 후킹한 모습을 확인할 수 있으며, 루팅 우회 또한 정상적으로 이루어져 애플리케이션이 종료되지 않은 모습을 확인할 수 있다.

[그림 2] a(), b(), c() 함수 후킹 완료
[그림 3] 루팅 탐지 우회 후

 

exit() 함수 후킹을 통한 우회

console.log("Script loaded successfully");
Java.perform(function x() {
    console.log("java perform function");
    var sys = Java.use('java.lang.System');
    sys.exit.implementation = function () {
        console.log("success");
    }
});

우회 코드는 간단하게 exit() 함수를 후킹한 후 로그만 찍어주는 코드이다.

스크립트를 실행하면 기존과 똑같이 "Root detected" 라는 메시지 박스가 보이지만 "OK"버튼을 눌러주면

[그림 4] 앱 실행 직후

 

"success"라는 로그가 출력된 뒤 애플리케이션이 종료되지 않는 모습을 확인할 수 있다.

[그림 5] exit() 함수 후킹 완료

 

Reference

'Android > Uncrackable' 카테고리의 다른 글

Uncrackable 2  (0) 2021.11.08
Uncrackable 1 - Secret String  (0) 2021.11.01