끄적끄적

[UnCrackable] Level 1 본문

Security/Mobile

[UnCrackable] Level 1

Go0G 2022. 5. 29. 21:12

배경지식

안티루팅(루팅 탐지)

루팅 탐지 방법은 아래와 같은 방법들이 있다.

  1. 안드로이드 환경 내에 설치된 Package(앱) 검색
  2. 설치된 파일 검사(/bin/su, /sbin/su 등)
  3. 빌드 태그 검사(test-key 검사)
  4. 쉘 커맨드 이용(su 명령어 입력)
  5. 디렉터리 권한 검사(/data, /, /system, /sbin 등)

모바일 보안 솔루션에서는 위와 같은 기능이 포함된 SDK 파일을 제공하며, 개발자는 SDK 파일 이용하여 특정 시점(Spalish Acitivity, Main Activity 등의 onCreate)에서 안티 루팅 로직을 작성한다.

SDK 형식으로만 제공되는 안티 루팅 로직은 APK 디컴파일을 통해 분석을 수행하고 Frida로 우회가 가능(난독화 수준에 따라 소요되는 시간이 상이)하다. 이에 SDK에 C언어로 작성된 NDK(Native Development Kit)를 포함한 방식의 안티 루팅 로직이 존재하나, 라이브러리(.so) 분석 및 후킹을 통해 안티 루팅 로직을 우회할 수 있다. 

 

최근 모바일 보안 솔루션은 Frida 후킹을 탐지하는 로직도 추가되고 있다. 

AES 암호화

안드로이드에서는 JDK의 javax.crypto.Cipher, javax.crypto.sepc.SecretKeySpec 클래스를 아래와 같이 이용하여 복호화 기능을 수행한다. AES 암호화 로직에 관해서는 다음 포스팅에서 자세히 다루도록 하겠다. 

ECB 복호화 로직(문제 내에서)

public static byte[] Decryption(byte[] bArr, byte[] bArr2) {
    SecretKeySpec secretKeySpec = new SecretKeySpec(bArr, "AES/ECB/PKCS7Padding"); //ECB 암호 운용모드 사용
    Cipher instance = Cipher.getInstance("AES");
    instance.init(2, secretKeySpec); //DECRYPT_MODE 사용
    return instance.doFinal(bArr2); //복호화 결과값 Byte로 반환
}

문제

본 문제는 Reference [2]를 통해 다운로드할 수 있다. 앱 실행 결과 안티루팅 로직이 실행되며 앱이 종료된다. 

애플리케이션 실행화면

안티 루팅 로직을 우회한 후, 특정 문자열을 입력하고 [VERIFY] 버튼을 클릭하면 문자열이 올바르지 않다는 메시지를 볼 수 있다.

안티루팅 우회 후, 문자열 입력


분석

원하는 결과를 얻기 전에 앞서, 안티루팅 로직 우회가 필요하며 결과적으로 "Sucess!" 알림창을 띄우는 것을 목표로 한다. 이를 위한 분석 과정은 아래와 같다.

  1. 안티루팅 체크 로직 우회
  2. "Sucess!" 알람 메시지 확인 

안티루팅 체크 로직 우회

앱 실행시, onCreate가 실행되며 분기문 내의 클래스가 실행된다. c 클래스와 b 클래스를 이용하여 안티루팅 및 디버깅 여부를 판단한다.

이후, 메인 엑티비티에서 문자열을 입력하고 [VERIFY] 버튼 클릭 시 올바른 문자열일 경우 "Sucess!" 알림창을 확인할 수 있다.

메인 엑티비티 코드

메인 엑티비티에서는 c 클래스의 메소드 리턴 값을 OR 연산을 수행하여 분기 여부를 결정한다.

c 클래스: 안티루팅 로직

  • a 메소드: 시스템 환경 변수 "PATH"에 "su" 존재 여부 확인
  • b 메소드: 빌드 태그 검사
  • c 메소드: 설치된 파일 검사

c 클래스

b 클래스: 디버깅 여부 확인

  • a 메소드: 디버깅 여부 확인

"Success!" 알람 메시지 확인

"Sccuess!" 알람 메시지는 obj는 사용자의 입력을 a 클래스의 a 메소드로 전송한 결과 값에 따라 분기한다.

메인 엑티비티

a 클래스: String 일치 여부 확인

a 메소드는 sg.vantagepoint.a  패키지의 a 클래스의 a 메소드에 두개의 매개변수에 대한 리턴 값을 저장한 배열과 사용자의 입력 값 일치 여부를 확인한다.

a 클래스

sg.vantagepoint.a  패키지의 a 클래스의 a 메소드

ECB 암호 운용 모드로, AES 암호화 모드를 사용하여 입력 받은 bArr 를 키 값으로하여 bArr2를 복호화한다.


PoC

안티루팅 로직을 우회 후, "Success!" 메시지를 띄우는 순서로 진행된다.

안티루팅 우회

앞서 분석하였던, c 클래스와 b 클래스에 대한 메소드의 리턴 값을 모두 false로 변경하여 분기문으로 넘어가지 않도록 후킹

c, b 클래스내 메소드 리턴 값 후킹

setImmediate(function () {
    Java.perform(function () {
        console.log("[*] START")

        var Rooting_Check = Java.use("sg.vantagepoint.a.c");
        Rooting_Check.a.overload().implementation = function() { //"su" 명령어 확인
            console.log("a() Method");
            return false;
        }
        Rooting_Check.b.overload().implementation = function() { //빌드 설명 태그
            console.log("b() Method");
            return false;
        }
        Rooting_Check.c.overload().implementation = function() { //시스템 내 루팅 관련 파일 존재여부 확인
            console.log("c() Method");
            return false;
        }

        var Debug_Check = Java.use("sg.vantagepoint.a.b");
        Debug_Check.a.overload('android.content.Context').implementation = function() { //시스템 내 루팅 관련 파일 존재여부 확인
            console.log("c() Method");
            return false;
        }
    });
});

후킹 과정에서 해당 로직이 실행되지 않는다. 클래스가 메모리에 로드되는 a 메소드를 먼저 실행하는 것으로 예상하였다.

따라서, exit 함수를 후킹하는 로직을 추가

c, b 클래스내 메소드 리턴 값, exit 함수 후킹 

setImmediate(function () {
    Java.perform(function () {
        console.log("[*] START")
        
        var exit_class = Java.use("java.lang.System")
        exit_class.exit.implementation = function(){ //exit method Hooking
            console.log("exit Hook");            
        }
        
        var Rooting_Check = Java.use("sg.vantagepoint.a.c");
        Rooting_Check.a.overload().implementation = function() { //"su" 명령어 확인
            console.log("a() Method");
            return false;
        }
        Rooting_Check.b.overload().implementation = function() { //빌드 설명 태그
            console.log("b() Method");
            return false;
        }
        Rooting_Check.c.overload().implementation = function() { //시스템 내 루팅 관련 파일 존재여부 확인
            console.log("c() Method");
            return false;
        }

        var Debug_Check = Java.use("sg.vantagepoint.a.b");
        Debug_Check.a.overload('android.content.Context').implementation = function() { //시스템 내 루팅 관련 파일 존재여부 확인
            console.log("c() Method");
            return false;
        }
    });
});

실행결과

루팅 탐지 관련 알람 이후, 애플리케이션이 종료되지 않는다. 간헐적으로 c, b 클래스에 대한 후킹 로직이 작동한다.

"Success!" 알람 메시지 확인

두 가지 방법으로 알람 메시지를 확인할 수 있었다.

  1. a 클래스의 a 메소드의 리턴 값을 true로 변경: 어떤 입력 값이라도 메시지 확인 가능
  2. AES 복호화 로직 구현: 문제에서 원하는 특정 문자열 확인 가능

a 클래스의 a 메소드의 리턴 값을 true로 변경

이 경우, 어떤 값을 입력하더라도 "Success!" 알람을 확인할 수 있다.

setImmediate(function () {
    Java.perform(function () {
        console.log("[*] START")

        var exit_class = Java.use("java.lang.System")
        exit_class.exit.implementation = function(){ //exit method Hooking
            console.log("exit Hook");            
        }

        var Rooting_Check = Java.use("sg.vantagepoint.a.c");
        Rooting_Check.a.overload().implementation = function() { //"su" 명령어 확인
            console.log("a() Method");
            return false;
        }
        Rooting_Check.b.overload().implementation = function() { //빌드 설명 태그
            console.log("b() Method");
            return false;
        }
        Rooting_Check.c.overload().implementation = function() { //시스템 내 루팅 관련 파일 존재여부 확인
            console.log("c() Method");
            return false;
        }

        var Debug_Check = Java.use("sg.vantagepoint.a.b");
        Debug_Check.a.overload('android.content.Context').implementation = function() { //시스템 내 루팅 관련 파일 존재여부 확인
            console.log("c() Method");
            return false;
        }

        //PoC 1: a.a Method 값을 True로 변경
        var String_Verify = Java.use("sg.vantagepoint.uncrackable1.a")
        String_Verify.a.overload('java.lang.String').implementation = function(args){
            console.log("a Method args: "+args);
            return true         
        }

    });
});

AES 복호화 로직 구현

아래 코드를 통해 복호화에 필요한 정보(암호문, 키, 관련 메소드 등)를 획득할 수 있다. 

암호화 키 값과 암호화 문자열
복호화 로직

아래와 같이 Frida 코드를 구현할 수 있다.

setImmediate(function () {
    Java.perform(function () {
        console.log("[*] START")

        var exit_class = Java.use("java.lang.System")
        exit_class.exit.implementation = function(){ //exit method Hooking
            console.log("exit Hook");            
        }

        var Rooting_Check = Java.use("sg.vantagepoint.a.c");
        Rooting_Check.a.overload().implementation = function() { //"su" 명령어 확인
            console.log("a() Method");
            return false;
        }
        Rooting_Check.b.overload().implementation = function() { //빌드 설명 태그
            console.log("b() Method");
            return false;
        }
        Rooting_Check.c.overload().implementation = function() { //시스템 내 루팅 관련 파일 존재여부 확인
            console.log("c() Method");
            return false;
        }

        var Debug_Check = Java.use("sg.vantagepoint.a.b");
        Debug_Check.a.overload('android.content.Context').implementation = function() { //시스템 내 루팅 관련 파일 존재여부 확인
            console.log("c() Method");
            return false;
        }

        //PoC 1: a.a Method 값을 True로 변경
        var String_Verify = Java.use("sg.vantagepoint.uncrackable1.a")
        String_Verify.a.overload('java.lang.String').implementation = function(args){
            console.log("a Method args: "+args);
            return this.a(args);            
        }


        //PoC 2: 결과 값 디코딩
        var obfuscation_String;
        var String_Verify = Java.use("sg.vantagepoint.uncrackable1.a")
        String_Verify.b.overload('java.lang.String').implementation = function(args){
            console.log("b Method args: "+args);
            obfuscation_String = this.b(args);
            console.log("b Method Result: "+obfuscation_String);
            return this.b(args);          
        }
        

        var base64decode_String;        
        var base64decode = Java.use("android.util.Base64")
        base64decode.decode.overload('java.lang.String', 'int').implementation = function(args1, args2){
            console.log("base64_Decode Method args: "+args1);
            base64decode_String = this.decode(args1, args2);
            console.log("base64_Decoded Result: "+base64decode_String);
            return this.decode(args1, args2);          
        }        

        var AES_Result;        
        var AES = Java.use("sg.vantagepoint.a.a")
        AES.a.overload('[B', '[B').implementation = function(args1, args2){
            console.log("aes Method args: "+args1);
            AES_Result = this.a(args1, args2);
            console.log("aes Result: "+AES_Result);
            console.log("aes Type Result: "+typeof(AES_Result));            

            var AES_ASCII_Encode = JSON.stringify(AES_Result);
            AES_ASCII_Encode = AES_ASCII_Encode.substr(1,AES_ASCII_Encode.length-2);
            var words = AES_ASCII_Encode.split(',');
            var result = ''; 
            for(var i = 0; i<words.length;i++){
                result += String.fromCharCode(words[i]);
            }
            
            console.log("Input Data: " + result )

            return this.a(args1, args2);          
        }         

    });
});

Reference

 

'Security > Mobile' 카테고리의 다른 글

[UnCrackable] Level 2  (0) 2022.05.30
[HacktheBox] SeeTheSharpFlag (feat. Xamarin)  (0) 2022.05.27
[HackTheBox] APKey  (0) 2022.05.24
[HackTheBox] Cat (Feat. Android Back Up)  (0) 2022.05.23
[ERROR] jadx is running low on memory  (0) 2022.04.16
Comments