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/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】