Uncrackable이란 OWASP에서 모바일 리버스 엔지니어링 연습용으로 개발한 앱으로 총 4개의 레벨로 구성이 되어있다.
https://github.com/OWASP/owasp-mastg/tree/master/Crackmes/Android
위의 깃헙 URL에서 Uncrackable Level 1~4를 다운 받을 수 있다.
이번에 해볼 것은 Uncrackable Level 1이다.
그리고 이번에는 에뮬레이터에서 하는것이 아닌 이전에 루팅해놓은 공기계에서 해볼것이다!
( 기기 : 갤럭시 A7 [SM-A710S], OS : Android 7 )
우선 연결되어 있는 기기의 상태가 adb로 연결이 가능한지 보기 위해 adb devices 로 확인해 준다.

다운받은 apk 파일을 에뮬레이터에 다운받아주기 위해 adb install Uncrackable-Level1.apk 명령어를 사용해준다.

공기계에서 확인해보면

다음과 같이 정상적으로 설치가 되어있는 것을 볼 수 있다.
자, 어떤 문제인지 알아보기 위해 앱을 실행시켜보겠다.

위의 사진과 같이 루팅 탐지가 되면 강제로 앱이 종료되도록 만들어져있다.
어떤 로직으로 동작하는지 jadx로 디컴파일하여 확인하여보자.

Uncrackable의 패키지는 sg.vantagepoint이고, MainActivity 클래스에 보게되면 Root detected!라는 문자를 출력하는 메서드가 존재 하는것을 볼 수 있다.
이 부분에서는 다시 c를 호출하여 각각a와b와c를 확인하는 것으로 보이는데 c를 클릭해서 어떻게 구성돼있는지 보겠다.

이게 c클래스에 포함되어있는 메서드들의 내용이다.
a() 메서드는 시스템 환경변수의(PATH)의 모든 경로를 확인하여 su파일이 있는지 검사하여 존재하면 true 존재하지 않으면 false를 반환한다.
b() 메서드는 안드로이드의 빌드 정보중 Build.TAGS를 확인하여 test-kets가 존재하는지 확인하고 있다. 이 빌드는 개발자 또는 테스트 목적으로 사용되는 것으로 이것이 존재하면 루팅되었을 확률이 높다.
c() 메서드에서는 지정된 경로를 검사하는데
/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/
이 리스트의 파일 또는 디렉터리가 존재하면 true 존재하지 않으면 false를 반환한다.
이또한 루팅 관련 앱이나 작업에서 생성되는 파일과 디렉터리이다.
여기중에서 true가 반환되는 것이 있으면 MainActivity의 onCreate메서드로 돌아와 a메서드로 가게 되는데

루팅 탐지가 될시 alert를 띄우고 setMessage의 내용을 보여준 다음에 OK버튼을 생성해 onClick 이벤드를 발생하 프로그램을 종료시키는 것을 알 수 있다.
그렇다면 여기에서 다양한 선택지가 있을것이다.
c클래스에 있는 메서드들을 우회 할것이냐... MainActvity에서 우회를 할것이냐...
그런데 생각을 해보면 탐지가 되고 OK를 누르기 전까지는 그냥 alert띄우는것만 하는거 아닌가?
버튼의 onClick함수를 후킹해서 System.exit(0)을 무력화 시킬수 있는것 아닌가?
한번 시도해 보겠다.
Java.perform(function() {
var byp = Java.use("sg.vantagepoint.uncrackable1.MainActivity$1");
byp.onClick.implementation = function(arg1,arg2){
console.log("[+]루팅 탐지 우회 성공.");
}
})
자 여기에서 onClick에 대한 함수를 또 보여주겠다.

setButton()의 new DialogInterface.OnClickListener()를 통해서 새로운 익명 클래스가 생성이 되었다.
MainActivity에서 새로운 익명 클래스가 생성되었기 때문에 Java 에서 사용하는 중첩 클래스 표현인 $1를 Java.use부분에 사용하였다.
또 onClick을 재작성할때 인자를 두개를 준것도 익명으로 생성된 클래스에서 onClick함수에 인자가 두개 들어가서이다!!
어쨌든 작성한 bypass.js코드를 frida -U -l bypass.js Uncrackable1 명령어로 확인해보겠다.

좋아 정상적으로 실행됐다!

앱에서도 정상적으로 실행되어 OK를 눌러도 앱이 종료되지 않는것을 볼 수 있다.
이제 Enter the Secret String에 문자를 집어넣어 푸는것 같은데..

흠 일단 이것도 코드를 보면서 어떻게 동작하는지 확인해보겠다.

MainActivity에서 veryfy 메서드를 확인해보면 여기에서 문자열을 확인하는 부분이 있다는 것을 알 수 있다.
a클래스에서 a메서드를 확인하여 Success또는 Nope를 출력하는듯 하다.
a클래스로 클릭해서 가보도록 하자.

음 일단 대강 봤을때는 sg.vantagepoint.a.a.a()함수는 b("8d127684cbc37c17616d806cf50473cc")와 Base64.decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", 0)를 인자로 받고 있다.
sg.vantagepoint.a로 타고 가보도록 하자.

a클래스의 a함수는 배열bArr과 배열bArr2를 인자로 받아 AES 암호 복호화 과정을 거쳐서 cipher.doFinal(bArr2)로 복호화된 문자열을 반환해준다.
일단 복호화된 문자열을 반환해주는게 가능하니까 문제가 있는거지 않을까?
한번 두개의 인자를 출력해보겠다.
Java.perform(function() {
var byp = Java.use("sg.vantagepoint.uncrackable1.MainActivity$1");
byp.onClick.implementation = function(arg1,arg2){
console.log("[+]루팅 탐지 우회 성공.");
}
var cip = Java.use("sg.vantagepoint.a.a");
cip.a.implementation = function(arg1,arg2){
console.log("arg1 :"+arg1);
console.log("arg2 :"+arg2);
}
})
이 코드를 통해 실행을 해보면

arg1 :-115,18,118,-124,-53,-61,124,23,97,109,-128,108,-11,4,115,-52
arg2 :-27,66,98,21,-53,91,-102,6,-61,-96,-75,-26,-92,-67,118,-102,73,-24,-16,116,-8,46,-1,29,-107,-85,124,23,20,118,24,-25
두 인자의 배열이 다음과 같이 출력되게 된다.
이 배열들을 보아하니 딱 봐도 아스키 코드 같아보이지 않은가?
그냥 아스키 코드 해석해주는 사이트에서 해석해서 대입할수도 있지만 코드로 출력하도록 하겠다!
Java에서는 String.fromCharCode()를 사용하면 아스키 코드를 문자열로 변환하는 것이 가능하다고 한다.
이제 arg1 과 arg2의 배열을 선언해주고 반복문으로 해석된 문장을 추가하는 코드를 짜보겠다.
Java.perform(function() {
var byp = Java.use("sg.vantagepoint.uncrackable1.MainActivity$1");
byp.onClick.implementation = function(arg1,arg2){
console.log("[+]루팅 탐지 우회 성공.");
}
var cip = Java.use("sg.vantagepoint.a.a");
const arg1 = [-115,18,118,-124,-53,-61,124,23,97,109,-128,108,-11,4,115,-52];
const arg2 = [-27,66,98,21,-53,91,-102,6,-61,-96,-75,-26,-92,-67,118,-102,73,-24,-16,116,-8,46,-1,29,-107,-85,124,23,20,118,24,-25];
let dec = "";
const result = cip.a(arg1, arg2);
for (let i = 0; i < result.length; i++){
dec += String.fromCharCode(result[i]);
}
console.log("해독된 문장 :",dec);
})
다음 코드를 사용해서 프리다 명령어를 실행시키게 되면

I want to believe가 Secret String 임을 알 수 있다.
이제 이 문장을 앱에 넣어주게 되면

야호!!!!! 정답이다!!!
Uncrackable Level1도 클리어 했다!!
확실히 FridaLab과는 조금더 다른 난이도의 문제였다...
푸는데도 오래걸리고 고민을 하는 시간도 좀더 걸렸던거 같다.
클래스를 타고 들어가는 것도 몇단계를 거쳐 해야할뿐 아니라 익명 클래스 생성에 따른 Java.use 선언을 할때 $명시에 대해서도 처음 알게되었다.
Frida Lab에서 풀었던 것들은 드림핵에서 새싹문제정도면 Uncrackable은 확실히 그 윗단계 라고 느껴진다.
다음 단계는 얼마나 어려울지....
조만간 레벨 2로 돌아오도록 하겠다!!
- 다음 포스트에 계속...