OWASP Android Crack-Me逆向分析

UnCrackable-Level1

第一题是一个纯java层逆向,首先程序会进行root和debug检测。

root检测就是通过检测su等文件和Build.TAGS是否包含test-keys。因为我的机子是自己编译的userdebug版本,所以会被检测到root,frida直接hook检测函数并返回false即可绕过。

检测通过后会验证输入的flag是否正确,验证逻辑也比较简单就是调用sg.vantagepoint.a.a.a进行AES加密,加密的数据就是正确的flag。

通过frida hooksg.vantagepoint.a.a.a得到的返回值即为flag:I want to believe

UnCrackable-Level2

第二题也会进行root和debug检测,同时还会调用native函数init。

init会通过fork子进程并互相ptrace,所以如果使用frida进行hook时应该以spawn模式启动,如果以attach模式启动会失败。

最后调用native函数bar验证,最后得到flag:Thanks for all the fish

UnCrackable-Level3

第三题首先会加载libfoo.so,此so的init_array存在一个反调试函数sub_31B0

sub_31B0会调用pthread_create创建一个线程,线程函数sub_30d0会通过/proc/self/maps检测frida和xposed。绕过的活可以hook pthread_create函数阻止反调试线程的创建,不过这里我想尝试hook线程函数sub_30d0并直接返回。

因为反调试线程是在init_array中的函数创建的,执行时机比较早,所以要想在init_array函数执行前进行hook的话需要选择合适的执行时机。这里我通过hook linker64的call_array函数,因为so的init_array中的函数都是由此函数调用的。反编译linker64得到call_array的偏移为0x50AF4

绕过init_array中的检测使用的frida脚本如下。

// hook linker64's call_array
var linker64_module = Module.getBaseAddress("linker64");
Interceptor.attach(linker64_module.add(0x50AF4), {
    onEnter:function(args){
        if(args[3].readCString().match("libfoo.so")){
            // hook libfoo.so + 0x30D0 , pass firda/xposed check
            var libfoo_module = Module.findBaseAddress("libfoo.so");
            Interceptor.replace(libfoo_module.add(0x30D0), new NativeCallback(function (){
                return;
            }, 'void', []))
        }
    },onLeave:function(result){}
})

接着会调用native的init函数将key2:pizzapizzapizzapizzapizz保存,同时调用sub_323C来fork子进程互相ptrace反调试。

通过hook sub_323C函数来绕过反调试。因为此函数是在apk启动过程中oncreate中调用,并且只调用一次,所以这里选择的hook时机为在libfoo.so被加载时进行hook。libfoo.so是apk调用System.loadLibrary("foo")加载的,其底层是通过调用libandroid_runtime.so的android_dlopen_ext来加载的so,注意这里不是通过调用dlopen加载的so。通过hook libandroid_runtime.so的android_dlopen_ext函数,并在libfoo.so加载的时候hook sub_323C函数来绕过反调试。

// hook android_dlopen_ext
var libfoo_loaded_flag = 0;
var android_dlopen_ext_addr = Module.getExportByName("libandroid_runtime.so", "android_dlopen_ext");
Interceptor.attach(android_dlopen_ext_addr, {
    onEnter:function(args){
        if(-1 != args[0].readCString().indexOf("libfoo.so")){
            libfoo_loaded_flag = 1;
        }
    },onLeave:function(result){
        if(libfoo_loaded_flag == 1){
            // hook libfoo.so + 0x323C , pass call ptrace
            var libfoo_module = Module.findBaseAddress("libfoo.so");
            Interceptor.replace(libfoo_module.add(0x323C), new NativeCallback(function(){
                return;
            }, 'void', []));
            libfoo_loaded_flag = 0;
        }
    }
})

上述检测通过后apk还会调用几个java层函数进行root检测,通过frida hook返回false即可绕过。

最后通过调用native层的bar函数验证flag,此函数会通过调用sub_10e0得到key1,并与key2:pizzapizzapizzapizzapizz异或得到flag。

通过frida hook sub_10e0得到返回的key1并与key2异或即可得到flag:making owasp great again

r2pay

这是一道R2con CTF的赛题r2pay 1.0的修改版,难度较1.0降低了,是一个白盒加密

  • 屏幕上有一个生成绿色令牌(又名 r2coins)的主 PIN 码。如果您看到一个红色的 r2coin,那么这个令牌将不会被社区验证。您还需要找出 4 位 PIN 码和使用的盐。标志:r2con{PIN_NUMERIC:SALT_LOWERCASE}

  • 有一个“r2pay 万能密钥”隐藏在层层混淆和保护中。你能打破白盒吗?标记:r2con{ascii(key)}

程序加载libtool-checker.so并调用checkForRoot去检测root,frida hook进行绕过。

接着会加载libnative-lib.so,此so的init_array存在两个函数会创建反调试线程,上一题是通过hook call_array,这里直接通过hook pthread_create阻止反调试线程的创建。同时看到有明显的字符串加密操作。

对于字符串加密可以直接通过unicorn模拟执行init_array中的字符串解密函数,将解密后的字符串dump并修复。修复后可以看到反调试检测和root检测相关字符串。

检测完之后通过将pin码和amount进行拼接后调用native 函数gXftm3iswpkVgBNDUp 生成令牌,并且在令牌之前存在一个判断字节,如果为81则证明此令牌是有效的。

通过hook所有的检测并 主动调用gXftm3iswpkVgBNDUp函数发现会出现崩溃,应该是gXftm3iswpkVgBNDUp函数中还存在反调试,并且会访问无效内存使程序崩溃退出。

frida将所有会崩溃的位置patch 即可正常调用gXftm3iswpkVgBNDUp函数。但是因为pin码长度为4,amount长度为8,所以要想通过主动调用gXftm3iswpkVgBNDUp函数暴力枚举出正确的pin和amount显然是不现实的。

var pthread_create_addr = Module.findExportByName("libc.so", "pthread_create");
Interceptor.replace(pthread_create_addr, new NativeCallback(function(arg1, arg2, arg3, arg4){
    console.log("pthread", arg3);
    var native_module = Module.findBaseAddress("libnative-lib.so");
    if(native_module != null){
        if(!arg3.compare((native_module.add(0x20954))) || !arg3.compare((native_module.add(0x7A660)))){
            console.log("pass: ", arg3);
            
            // patch crash code
            console.log(native_module.add(0x9E7F0).readU32())
            Memory.protect(native_module.add(0x9E7F0), 100, 'rwx')
            native_module.add(0x9E7F0).writeU32(0xD503201F);
            native_module.add(0x9E82C).writeU32(0xD503201F);
            native_module.add(0x9e840).writeU32(0xD503201F);
            Memory.protect(native_module.add(0x9e840), 100, 'rx')
            return 0;
        }
    }
    var pthread_create = new NativeFunction(pthread_create_addr, 'int', ['pointer', 'pointer', 'pointer', 'pointer']);
    return pthread_create(arg1, arg2, arg3, arg4)
}, 'int', ['pointer', 'pointer', 'pointer', 'pointer']))

hook RegisterNatives得到gXftm3iswpkVgBNDUp对应的函数为7D124, 分析gXftm3iswpkVgBNDUp函数发现其存在虚假控制流和控制流平坦化,通过之前写的ollvm去混淆脚本并没有实现有效的去混淆。

findcrypt可以识别出存在AES加密,但是并得到正确的key,可以判断是AES白盒加密。国外有位大佬通过DAF差分故障分析恢复了正确的密钥 :https://www.romainthomas.fr/post/20-09-r2con-obfuscated-whitebox-part2/

validate

最后一题比较简单,就是通过输入base32编码字符串,解码后分别两两一组进行异或。

参考链接:
https://github.com/OWASP/owasp-mastg/tree/master/Crackmes/Android
https://enovella.github.io/android/reverse/2020/09/03/r2pay-android-crackmes-radare2con.html
https://www.romainthomas.fr/post/20-09-r2con-obfuscated-whitebox-part1/
https://www.romainthomas.fr/post/20-09-r2con-obfuscated-whitebox-part2/

posted @ 2023-04-01 02:36  怎么可以吃突突  阅读(540)  评论(0编辑  收藏  举报