Uncrackable 은 Owasp에서 제공하는 모바일 어플리케이션이다.
Uncrackable-Level1.apk를 다운받은 후, Nox 애뮬레이터에 Drag&Drop으로 설치한다.
1. Manifest.xml
apk easy tool로 Uncrackable-Level1을 디컴파일 해주고 디컴파일된 파일 중, AndroidManifest.xml 파일을 열어보면 package명과 MainActivity를 알 수 있다.
상위루트 manifest에 있는 package : AndroidManifest.xml 파일의 루트 요소다. 나중에 프리다를 사용할 때 이 패키지명이 사용된다.
상위루트 activity에 있는 MainActivity : 액티비티 구성 요소를 선언한다. jadx로 어플리케이션을 분석할 때 MainActivity에서 진행된다.
2. Nox 루팅 설정
Nox 자체 설정에서 박스친 부분을 체크해주면 된다.
3. 어플리케이션 실행
루팅 설정을 마친 nox에서 어플리케이션을 실행하면 'Root detected!'라는 문장과 함께 루팅이 탐지되었다고 나타난다. 그리고 OK 버튼을 누를 시, 어플리케이션이 종료된다.
어플리케이션이 종료되는 로직을 확인하기 위해 해당 어플리케이션을 jadx를 통해 디컴파일하여 분석해본다.
4. jadx를 이용한 동적 디버깅
jadx로 Uncrackable-Level1.apk를 열어주면 위 사진과 같은 화면을 볼 수 있는데, 우리는 1번에서 MainActivity의 경로를 확인했었다.
이 경로대로 따라가면 onCreate 함수를 찾을 수 있는데, 이 함수가 어플리케이션이 실행될 때 가장 먼저 실행되는 영역이다.
5. OnCreate(), 루팅 탐지
어플리케이션 실행 화면에서 봤듯이, 어플리케이션을 실행하면 "Root detected!" 라는 문구가 나타난다. 이걸 통해서 해당 문구가 들어있는 OnCreate 메서드가 가장 먼저 켜지는 함수라는 것을 알 수 있다.
c.a / c.b / c.c
위 알파벳은 c 클래스 안에 있는 a, b, c 메서드(멤버 함수)라는 뜻이다.
"Root detected!"는 if문을 통해 위의 세 메서드를 가지고 루팅을 탐지하게 된다.
c 클래스를 확인하여 각각의 메서드들이 어떻게 탐지를 진행하는지 확인해보겠다.
[빨간 박스] c.a 메서드에서는 PATH라는 환경변수 값을 가져온다. 루팅하면 su 바이너리 파일이 생성되는데, 환경변수 PATH 값에 su라는 설정이 되어있으면 프로그램이 루팅되어 있다고 판단한다. su를 찾으면 true를 리턴한다.
[초록 박스] 핸드폰 기기를 루팅하면 Build.TAGS라는 값이 있다. 안드로이드 태그에 "test-keys"라는 태그 값이 등록되어 있다. 루팅을 하면 이 값이 핸드폰 환경 값에 등록이 되는데, 이것이 존재하는지를 확인하는 것이다. "test-keys"가 있으면 true를 리턴한다.
[파란 박스] 루팅에 쓰이는 어플이 있는지 확인한다. /system/app 폴더에 루팅에 쓰이는 apk가 존재하는지 확인한 후, 존재한다면 루팅됐다고 판단하여 true를 리턴한다.
c.a, c.b, c.c 셋 중 하나라도 해당된다면 true가 반환되어 if문이 참이 되고, "Root detected!"가 출력된다.
6. OnCreate(), System.exit()
위 과정에서 루팅이 탐지되어 "Root detected!"가 나타나며 "This is unacceptable. The app is now going to exit." 문구가 나타난다.
여기서 OK 버튼을 누르게 되면 어플리케이션이 종료된다.
해당 과정이 동작되는 함수는 아래와 같다.
버튼을 누르게 되면 onClick() 함수가 실행되며 System.exit()에 의해 어플리케이션이 종료된다.
이 영역이 어플리케이션 종료의 원인이니, 나는 루팅이 탐지되어도 종료되지 않게 하기 위해 System.exit()를 smail 코드에서 변조해줄 것이다.
7. 우회 방법 3가지
어플리케이션이 종료되지 않고 실행하기 위해서는 종료되는 부분을 우회해야 한다.
첫번째 방법은 5번에서 본 c 클래스의 루팅 탐지 영역을 우회하는 것이다.
두번째 방법은 exit() 함수를 smali 변조를 통해 우회한다.
세번째 방법은 exit() 함수를 Frida로 우회해서 루팅 탐지가 되더라도 종료되지 않게 한다.
7.1. 루팅 탐지 우회 - Frida 후킹
5번에서 봤듯, 세 메서드에서 하나라도 true 값이 리턴된다면 루팅이 탐지된다. 이를 후킹하기 위해 Frida를 사용했다.
다음과 같은 스크립트를 작성해서 a, b, c 메서드들을 후킹해 false로 반환해준다.
그리고 스크립트를 실행시키면 어플리케이션이 종료되지 않고 사용할 수 있다.
7.2. exit() 우회 - smali 변조
MainActivity.smali 코드를 보면 if-nez v0, :cond_0이라고 되어 있고, cond_0을 보면 "Root detected!" 문구가 나타난다.
코드를 보면 a 함수에 문자열이 인자 값으로 들어가면 어플리케이션이 종료되게 되어있다. 그래서 나는 이 문자열이 나타나지 않게 cond_0을 cond_1로 바꿨다.
다시 apk easy tool로 Sign APK → Compile을 진행하고, 어플리케이션을 실행해보면 "Root detected!"라는 문구가 나타나지 않고 어플리케이션이 종료되지도 않는다.
7.3. exit() 우회 - Frida 후킹
다음과 같은 스크립트를 작성해서 exit() 함수를 후킹한다.
스크립트를 실행하면 Root detected! 창이 나타나고, OK 버튼을 누르면 exit() 함수가 후킹되어 어플리케이션이 종료되지 않고 시크릿 스트링을 작성할 수 있게 된다.
8. verify() 분석
루팅탐지를 우회한 후, 어플리케이션이 꺼지지 않으니 SecretCode를 작성할 수 있다. 아무 문자열을 입력해봤는데 당연하게도 Nope... 이라는 타이틀과 That's not it. Try again. 이라는 문장이 나타난다.
SecretCode코드를 알아내기 위해 코드를 살펴봤다.
verify 함수에서 findViewById는 우리가 입력한 text box 안에 있는 내용을 getText()의 toString()으로 가져와서 obj 변수에 저장한다.
verify 함수에서 findViewById는 우리가 입력한 text box 안에 있는 내용을 getText()의 toString()으로 가져와서 obj 변수에 저장한다.
9. a.a() 분석
입력 값에 대한 검증이 이루어지는 a.a() 메서드를 분석한다.
Success를 출력하기 위해서는 어떤 값이 Secret String에 들어가야 하는지 알아내야 한다.
8d127684cbc37c17616d806cf50473cc를 b() 메소드에 넣은 값과 5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=를 Base64로 디코딩한 값을 인자로 sg.vantagepoint.a.a.a() 메소드를 호출하여 bArr에 저장한다.
9.1. sg.vantagepoint.a.a.a()
단순히 AES를 decrypt하는 함수다.
그리고 bArr과 입력한 값을 equals()를 통해서 비교한 결과 값을 반환한다.
equals는 str과 equals에 넘어온 문자열 파라미터가 서로 같은지 다른지 판단하는 기능을 하는 메서드다.
equals에 넘어온 값은 복호화된 secret string인 bArr이다.
여기서 리턴해주는 부분을 무조건 True로 반환해주는 방법도 가능하지만, 나는 복호화된 secret string을 반환하는 후킹 스크립트를 작성했다.
10. Secret String 우회 - Frida 후킹
후킹한 리턴 값을 retval 변수에 하나씩 저장하고, 그 변수들을 secret_string에 합쳐서 문자열 형태로 저장해 출력시키는 스크립트를 작성했다.
스크립트를 실행시키면 아무 값이나 입력해도 콘솔창에서는 secret string이 나타나게 된다. 그 값을 입력하면 문제를 푸는데 성공한다.
댓글