[OWASP-MSTG] Uncrackable level 3 Write-Up

두비니

·

2021. 5. 17. 18:04

 

 

 

오늘은 Uncrackable level 3 풀이를 작성합니다. 안드로이드 후킹 오랜만에 하네용

 

 

 

문제 분석

 

우선 앱을 먼저 실행시켜보면 루팅이 감지되었다고 나가라고 하네요. jeb와 jadx를 통해서 분석을 해봅시다

 

 

루팅 감지 후 system.exit을 부르는건 동일하므로 앞선 문제들에서 사용한 방법과 동일하게 진행해보았습니다.

그 방법을 사용할 경우, 다음과 같이 종료됩니다.

 

 

뭔가 싶어 로그를 살펴보니, 

뭔가를 감지했다고 꺼지네요. Text검색을 해봐도 해당 텍스트가 나오지 않는 것을 보아, import한 lib파일 안에서 뭔가를 검색하지 않을까?라는 생각이 드네요.

더 자세히 MainActivity를 분석해봅시다.

 

   private void verifyLibs() {
        Iterator v2;
        ZipFile v1;
        String v0 = "UnCrackable3";
        this.crc = new HashMap();
        this.crc.put("armeabi-v7a", Long.valueOf(Long.parseLong(this.getResources().getString(2131427371))));
        this.crc.put("arm64-v8a", Long.valueOf(Long.parseLong(this.getResources().getString(2131427369))));
        this.crc.put("x86", Long.valueOf(Long.parseLong(this.getResources().getString(2131427380))));
        this.crc.put("x86_64", Long.valueOf(Long.parseLong(this.getResources().getString(2131427381))));
        try {
            v1 = new ZipFile(this.getPackageCodePath());
            v2 = this.crc.entrySet().iterator();
            while(true) {
            label_42:
                boolean v3 = v2.hasNext();
                break;
            }
        }
        catch(IOException ) {
            goto label_114;
        }

        String v4 = ", supposed to be ";
        int v5 = 31337;
        String v6 = "] = ";
        String v7 = "CRC[";
        if(!v3) {
            goto label_86;
        }

        try {
            Object v3_1 = v2.next();
            String v8_1 = "lib/" + ((Map$Entry)v3_1).getKey() + "/libfoo.so";
            ZipEntry v9 = v1.getEntry(v8_1);
            Log.v(v0, v7 + v8_1 + v6 + v9.getCrc());
            if(v9.getCrc() == ((Map$Entry)v3_1).getValue().longValue()) {
                goto label_42;
            }

            MainActivity.tampered = v5;
            Log.v(v0, v8_1 + ": Invalid checksum = " + v9.getCrc() + v4 + ((Map$Entry)v3_1).getValue());
            goto label_42;
        label_86:
            String v2_1 = "classes.dex";
            ZipEntry v1_1 = v1.getEntry(v2_1);
            Log.v(v0, v7 + v2_1 + v6 + v1_1.getCrc());
            if(v1_1.getCrc() == this.baz()) {
                return;
            }

            MainActivity.tampered = v5;
            Log.v(v0, v2_1 + ": crc = " + v1_1.getCrc() + v4 + this.baz());
        }
        catch(IOException ) {
        label_114:
            Log.v(v0, "Exception");
            System.exit(0);
        }
    }
}

 

다음은 MainActivity 안에 있는 verifyLibs()함수입니다. 전체적으로 Level2에 있는 함수랑 비슷하지만, 조금 더 길어졌네요.

분석해봅시다.

 

 

libfoo.so를 불러오는걸 확인할 수 있습니다. 확인해봅시다

 

x86안에 있는 파일을 봅시다

 

libfoo.so 안의 sub_3080 함수를 보니

다음과 같이 frida와 xposed가 실행되고 있는 프로세스 있는지 확인하더라구요. 

즉, frida가 사용중이라면 무조건 종료가 됩니다.

 

그럼 이걸 우회하려면 두 가지 방법이 있습니다.

 

1. strstr함수 후킹

2. pthread_create를 후킹(/proc/self/maps의 thread자체를 바꿔치기)

 

저는 첫 번째 방법으로 해보겠습니다.

 

strstr 후킹

 

strstr함수는 두 문자열을 비교하여 존재하는 경우 그 문자열의 인덱스를, 아니라면 0을 리턴합니다.따라서, strstr함수가 무조건 0을 리턴하도록 후킹하겠습니다.

 

import sys, frida

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

jscode = """
console.log("[+] Start Script1");
Interceptor.attach(Module.findExportByName("libc.so", "strstr"), {
console.log("[+] Hooking strstr function")
onEnter: function (args) {
var haystack = Memory.readUtf8String(args[0]);
if(haystack.indexOf('frida')!==-1 || haystack.indexOf('xposed')!==-1)
    this.frida = Boolean(1);
},
onLeave: function (retval) {
if(this.frida)
    retval.replace(0);
return retval;
}
});
send("Done with native hooks....");
    Java.perform(function(){
        console.log("[+] Hooking System.exit");
		var exitClass = Java.use("java.lang.System");
		exitClass.exit.implementation = function() {
			console.log("[+] System.exit called");
		}
    });
"""

try:
    device = frida.get_usb_device(timeout=5)
    pid = device.spawn([PACKAGE_NAME])
    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 Hook")
    script.load()
    sys.stdin.read()
except Exception as e:
    print(e)

 

코드는 다음 문서 참고하여 작성했습니다 : https://secuinfo.tistory.com/entry/FRIDA-Frida%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-Native-Hooking

 

[FRIDA] Frida를 이용한 Native Hooking

개발자가 C ++ 또는 C 언어로 코드를 개발하고 APK의 기능에 액세스 할 수있는 Android NDK를 사용하여 루팅 탐지와 같은 다양한 작업을 수행하는 경우에 Frida를 사용하여 C ++ 또는 C로 개발 된 함수를

secuinfo.tistory.com

 

 

 

비밀번호 찾기

 

그럼 이제 비밀번호를 찾아봅시다.

보니깐 check_code를 실행시켜서 이에 대한 값을 확인하네요. libfoo도 봅시다

 

 

이렇게 분석해도 되는건지는 모르겠지만, 거의 반이상 감으로 풀었습니다..ㅋㅋㅋ첫 번째 빨간색 박스를 보면 좌변과 xor을 한 우변을 비교하는걸 볼 수 있는데, 이게 최종 비교방법이라고 생각했습니다.그리고 좌변은 원하는 값일거고, 우변은 우리가 입력한 값과 xor을 해야하는 값이 적혀있다고 생각했습니다.그리고 나머지는 그냥 포인터 주소 늘려주는거고, 0x18이하일때만 while문을 돌도록 하길래, 길이가 0x18이겠구나 했습니다.

 

 

그리고 코드를 보면 id로 뭘 긁어와서 check_code를 실행시킵니다.그리고 그 id를 확인해보면?

 

 

 

이상하다고 생각했던 string에, 길이도 딱 24여서 아, 이게 xorkey구나 라고 생각했습니다.아무튼 결론적으로, 비교대상인 string을 얻을 수 있다면 xorkey도 알고 있기 때문에 flag를 구할수 있겠죠?그걸 기반으로 작성한 코드입니다.

 

실행하면 xor하기 전의 값을 줍니다. 그럼 xor을 해주면 되겠죠? 이건 그냥 python기반으로 했습니다.

 

final code

import sys, frida
def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)
PACKAGE_NAME = "owasp.mstg.uncrackable3"
jscode = """
console.log("[+] Start Script1");
console.log("[+] Hooking strstr()");
Interceptor.attach(Module.findExportByName("libc.so", "strstr"), {
onEnter: function (args) {
var haystack = Memory.readUtf8String(args[0]);
if(haystack.indexOf('frida')!==-1 || haystack.indexOf('xposed')!==-1)
    this.frida = Boolean(1);
},
onLeave: function (retval) {
if(this.frida)
    retval.replace(0);
return retval;
}
});

console.log("[+] Finding password..");
var address = 0x00003446;
function hook(){
    //libfoo.so Address
    var p_foo = Module.findBaseAddress('libfoo.so');
    send("libfoo.so @ " + p_foo.toString());
    var target_address = p_foo.add(address);
    send("target_Address @ " + target_address.toString());
    Interceptor.attach(target_address, {
        onEnter: function (args) {
        var esp = this.context.esp;
        send("esp:"+esp);
        send(hexdump(esp,{
            offset: 0,
            length: 24,
            header: false,
            ansi:false
        }));
     },
     onLeave: function (retval) {
        send("onLeave() p_strncmp_xor64");
        send(retval);
     }

    });
}
console.log("[+] Done with native hooks!!");

Java.perform(function () {
    send("Hooking calls to System.exit");
    var sys = Java.use("java.lang.System");
    sys.exit.overload("int").implementation = function(var_0) {
        send("System.exit called");
    };
    hook();
    send("Done Java hooks installed.");
});
"""

try:
    device = frida.get_usb_device(timeout=5)
    pid = device.spawn([PACKAGE_NAME])
    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 Hook")
    script.load()
    sys.stdin.read()
except Exception as e:
    print(e)

secret = bytes.fromhex("1d0811130f1749150d0003195a1d1315080e5a0017081314").decode("utf-8")
xorkey = "pizzapizzapizzapizzapizz"

password = ""

for i in range(24):
    password += chr((ord(secret[i])^ord(xorkey[i])))

print("[!] Found flag: " + password)

 

끝!

어렵다 어려워

'SYSTEM HACKING > Hooking' 카테고리의 다른 글

[OWASP-MSTG] Uncrackable level 2 Write-Up  (6) 2021.01.22
[OWASP-MSTG] Uncrackable level 1 Write-Up  (0) 2021.01.17
[Frida-Lab] level 1~8 Write-Up  (0) 2021.01.10