Information Security/Android

Hack The Box: SAW

hackcatml 2022. 1. 6. 00:02
반응형

앱(https://app.hackthebox.com/challenges/saw) 설치하여도 실행이 안됩니다.

디컴파일 결과를 보니 인텐트를 실어서 액티비티를 실행해야 다음단계로 넘어갈 수 있을 것 같습니다.

 

 

AndroidManifest.xml 파일을 보니 "open": "sesame" 인텐트와 함께 "com.stego.saw.MainActivity" 액티비티의 "android.intent.action.MAIN" 액션을 실행시키면 되는 것으로 보입니다.

am start -a android.intent.action.MAIN --es open "sesame" -n com.stego.saw/.MainActivity

 

 

그런데, 다시 "Click me" 버튼을 클릭하면 앱이 종료됩니다. 디컴파일 결과에 의하면 "click me" 버튼 클릭시 새로운 버튼이 생성되며, 새로운 버튼 클릭 시 사용자 값을 입력받는 AlertDialog가 팝업 되어야 합니다.

 

 

로그캣을 통해 크래시 로그를 보면 "permission denied for window type 2038" 메세지를 확인할 수 있습니다.

 

 

https://stackoverflow.com/a/52917515 에 의하면 해당 에러는 Setting에서 앱 권한을 설정하여 해결할 수 있습니다.
앱 권한 설정 항목으로 들어가서 "다른 앱 위에 표시 허용" 을 체크해줍니다.

 

 

다시 액티비티를 실행하고 "Click me" 버튼을 클릭하면 버튼이 생성되고, 생성된 버튼을 클릭하면 AlertDialog가 보입니다.

 

 

소스코드에 의하면 알람창에 아무값이나 입력하고 XORIFY 터치하면 네이티브 메서드 "a" 에 "FILE_PATH_PREFIX" 와 함께 입력값이 전달되는 로직입니다.

 

 

네이티브 메서드 "a"와 shared library(.so파일) 내 함수의 매핑정보는 libart.so 파일의 RegisterNatives 메서드를 후킹하면 확인 가능합니다.
"com.stego.saw.MainActivity.a" 메서드는 libdefault.so 라이브러리의 offset 0xc90 에 위치한 함수와 매핑되고 있습니다.

 

 

해당 네이티브 메서드의 psuedocode는 다음과 같습니다. _Z1aP7_JNIEnvP8_1 함수에 const char * v7, const char *v8이 전달되고 있으며, 이는 다시 _Z17_Z1aP7_JNIEnvP8_1PKcS0_ 함수에 전달되고 있으므로 어떤 값이 전달되는지 확인해볼 필요가 있겠습니다.

 

 

_Z17_Z1aP7_JNIEnvP8_1PKcS0_ 메서드에 대한 프리다 후킹코드 작성합니다.
var awaitForCondition = function(callback) {
    var module_loaded = 0;
    var int = setInterval(function() {
        Process.enumerateModulesSync()
        .filter(function(m){ return m['path'].toLowerCase().indexOf('libdefault.so') != -1; })
        .forEach(function(m) {
            console.log("libdefault.so loaded!");
            return module_loaded = 1;
        })
        if(module_loaded) {
            clearInterval(int);
            callback();
            return;
        }
    }, 0);
}

function nativeTrace(nativefunc) {
    var nativefunc_addr=Module.getExportByName("libdefault.so", nativefunc) 
    var func=ptr(nativefunc_addr);
    
    Interceptor.attach(func, {
        // set hook 
        onEnter: function (args) { 
            console.warn("\n[+] " + nativefunc + " called"); // before call 
            if (nativefunc == "_Z17_Z1aP7_JNIEnvP8_1PKcS0_") { 
                console.log("\n\x1b[31margs[0]:\x1b[0m \x1b[34m" + args[0].readUtf8String() + ", \x1b[32mType: "); 
                console.log("\n\x1b[31margs[1]:\x1b[0m \x1b[34m" + args[1].readUtf8String() + ", \x1b[32mType: "); 
            } 
        }, 
        onLeave: function (retval) { 
            if(nativefunc == "_Z17_Z1aP7_JNIEnvP8_1PKcS0_"){ 
                console.warn("[-] " + nativefunc + " ret: " + retval.toString() ); // after call 
            } 
        } 
    }); 
}

function hook() {
    nativeTrace("_Z17_Z1aP7_JNIEnvP8_1PKcS0_");
}

awaitForCondition(hook);

 

 

임의의 문자열 입력 후 XORIFY 클릭하면 _Z17_Z1aP7_JNIEnvP8_1PKcS0_ 함수의 첫번째 인자로는 "/data/user/0/com.stego.saw/", 두번째 인자로는 내가 입력한 임의의 문자열이 전달되는 것을 확인 가능합니다.

 

 

_Z17_Z1aP7_JNIEnvP8_1PKcS0_ 함수의 pseudocode는 다음과 같은데, 내가 입력한 문자열의 각 자리와 XOR연산을 수행하여 하나라도 일치하지 않으면 함수를 종료하고 있습니다.

 

 

[0xA, 0xB, 0x18, 0xF, 0x5E, 0x31, 0xC, 0xF] ^ [내가 입력한 문자열] == [0x6C, 0x67, 0x28, 0x6E, 0x2A, 0x58, 0x62, 0x68] 이 되어야 합니다.
XOR 연산의 역연산은 XOR이므로 다음 관계가 성립합니다.
[0xA, 0xB, 0x18, 0xF, 0x5E, 0x31, 0xC, 0xF] ^ [0x6C, 0x67, 0x28, 0x6E, 0x2A, 0x58, 0x62, 0x68] ==> [내가 입력해야 할 문자열]

 

 

XOR 연산을 통해 "내가 입력해야 할 문자열"은 "fl0ating" 임을 알 수 있습니다.

 

 

_Z17_Z1aP7_JNIEnvP8_1PKcS0_ 함수의 pseudocode를 조금 더 살펴보면 문자열을 제대로 입력하면 파일을 만들어서 무언가를 쓰는 로직입니다.

 

 

"fl0ating" 문자열 입력 및 "XORIFY" 버튼 클릭 후 위에서 _Z17_Z1aP7_JNIEnvP8_1PKcS0_ 함수의 첫번째 인자로 전달된 "/data/user/0/com.stego.saw/" 경로에 가보면, h 라는 파일이 생성되어 있는 것을 볼 수 있습니다.

 

 

생성된 파일을 읽으면 플래그 획득이 가능합니다.

 

 

반응형