Self-Improvement

[UnCrackable-Level1] Frida Hooking - (System.exit(), equals(), return, byte array to string) 본문

AOS/UnCrackable

[UnCrackable-Level1] Frida Hooking - (System.exit(), equals(), return, byte array to string)

JoGeun 2021. 10. 24. 22:31

Uncrackable 1

 

System.exit() 후킹

이번에는 함수를 후킹해서 해볼 것이다.
마찬가지로 루팅 탐지할 때의 문자열을 디컴파일하여 검색해보면 메인 액티비티에서 c클래스의 a(), b(), c() 메소드의 결과 값 중 하나라도 True일 경우에는 "Root detected!"가 출력된다.

 

그리고 화면에 출력된 텍스트박스를 클릭하면  a() 메소드에서 System.exit(0)으로 종료가 되어진다.

 

이 때의 system.exit() 함수를 후킹해서 원래의 기능인 종료가 아닌 단순하게 콘솔 로그를 찍는 로직으로 재정의하여 종료되지 않도록 해볼 것 이다.
import frida, sys

Hook_package = "owasp.mstg.uncrackable1"


def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)


jscode = """
    send("[*] Injected JS");
    Java.perform(function() {
        var system = Java.use("java.lang.System");
        system.exit.implementation = function(){
            console.log("[*] System.exit hooking");
        }
    });
"""

try:
    device = frida.get_usb_device(timeout=5)
    pid = device.spawn([Hook_package])
    print("App is starting.. pid:{}".format(pid))
    process = device.attach(pid)
    device.resume(pid)
    script = process.create_script(jscode)
    script.on('message', on_message)
    print("[-] Running FR1DA!")
    script.load()
    sys.stdin.read()

except Exception as e:
    print(e)

 

위 코드를 실행하면 정상적으로 루팅 탐지와 함께 종료되지 않고 계속 사용할 수 있다.

 

System.exit() 후킹 그 외... 실패

루팅되는 코드를 보면 c클래스의 각 a() b() c() 메소드의 리턴값에 의해 "Root detected"가 출력되고 종료가 된다.
이때 각 메소드를 후킹하여 리턴값을 변조하면 루팅을 통과할 수 있다.

 

각 a(), b(), c() 메소드를 후킹하여 리턴값을 출력한 후 우회하기 위해 변조하면 되는데 아래의.. 코드가 제대로 동작되지 않는다.. 왜일까?
jscode = """
    send("[*] Injected JS");
    Java.perform(function () {
        send("a() perform");
        var root_check = Java.use('sg.vantagepoint.a.c');
        var root_check_1 = root_check.a;
        root_check_1.implementation = function(){
            send("Retrun Value : ");
            var ret = this.a();
            send("Retrun Value : " + ret);
            ret=false
            return ret
        }
        send("b() perform2");
        var root_check = Java.use('sg.vantagepoint.a.c');
        var root_check_2 = root_check.b;
        root_check_2.implementation = function(){
            send("Retrun Value : ");
            var ret = this.b();
            send("Retrun Value : " + ret);
            return ret
        }
        send("c() perform3");
        var root_check = Java.use('sg.vantagepoint.a.c');
        var root_check_3 = root_check.c;
        root_check_3.implementation = function(){
            send("Retrun Value : ");
            var ret = this.c();
            send("Retrun Value : " + ret);
            return ret
        }
    });
"""

 

Equals() 후킹

루팅 후킹한 후에는 텍스트 박스에 입력할 수 있는 화면이 보이고 "VERIFY" 버튼을 눌러서 검증을 하는 것으로 보인다.
아무 입력값이나 넣은 뒤 버튼을 눌르면 Secret 값이 알맞지 않다면서 다시 시도하라고 출력된다.

 

소스코드에서 해당 로직을 확인해보면 verify() 메소드에서 a.a(obj)의 값이 True일 경우에는 secret 값이 일치하다고 출력된다.

 

a 클래스의 a() 메소드를 확인해보면 입력한 값을 인자로 받아서 sg.cantagepoing.a.a.a() 메소드를 실행하게 되는데
인자로는 첫 번째 - b() 메소드에 "8d127684cbc37c17616d806cf50473cc" 값을 전달하여 리턴한 값과
두 번째 - "5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=" 값을 base64 디코딩한 값을 인자로 사용하고 있다.
위 과정에서 얻은 문자열을 equals() 메소드로 같은지 확인하여 리턴하고 있었다.

 

equals() 메소드를 후킹하여 결과 값을 확인해 보도록 하자.
equals() 메소드의 클래스인 java.lang.String을 java.use()의 인자로 전달해준다.
그리고 메소드를 새로 정의하며 인자로 받은 값을 console.log 로 출력해주고 리턴값도 True로 설정해준다.
jscode = """
    send("[*] Injected JS");
    Java.perform(function() {
        var system = Java.use("java.lang.System");
        system.exit.implementation = function(){
            console.log("[*] System.exit hooking");
        }
        var str_equal = Java.use("java.lang.String");
        str_equal.equals.implementation = function(arg1){
            console.log(arg1);
            return true;
        }
    });
"""

 

Secret Value 후킹

위의 방식대로 하면 해결은 되었고 출력되는 equals string 값 중 Secret 값이 있겠지만 정확하게 알아내기 위해 좀 더 분석을 해본다.
실질적으로 sg.vantagepoing.a.a.a() 메소드를 호출하여 리턴된 값을 str.equals() 메소드를 통해 검증하고 있다.

 

sg.vantagepoing.a.a.a() 메소드를 확인해보면 AES 형식의 암호화?를 진행하고 어떠한 값을 리턴하고 있었다.
이때의 리턴한 값을 출력해보기로 한다.

 

아래의 후킹 코드를 사용하여 리턴 값을 출력해보면 byte 값이 출력되는 것을 알 수 있다.
jscode = """
    send("[*] Injected JS");
    Java.perform(function() {
        var system = Java.use("java.lang.System");
        system.exit.implementation = function(){
            console.log("[*] System.exit hooking");
        }
        var aes_str = Java.use("sg.vantagepoint.a.a");
        aes_str.a.implementation = function(arg1, arg2){
            var ret = this.a(arg1,arg2);
            console.log(ret);
        }
    });
"""

 

Byte Array to String

byte 값을 String 으로 변환하는 것은 아래의 링크 내용을 참조하였다.
https://reverseengineering.stackexchange.com/questions/17835/print-b-byte-array-in-frida-js-script
jscode = """
    send("[*] Injected JS");
    Java.perform(function() {
        var system = Java.use("java.lang.System");
        system.exit.implementation = function(){
            console.log("[*] System.exit hooking");
        }
        var aes_str = Java.use("sg.vantagepoint.a.a");
        aes_str.a.implementation = function(arg1, arg2){
            var ret = this.a(arg1,arg2);
            var buffer = Java.array('byte', ret);
            var result = "";
            for(var i = 0; i< buffer.length; ++i){
                result += (String.fromCharCode(buffer[i]));
            }
            console.log(result);
            return ret;
        }
    });
"""