CyberTruck Challenge 2019 challenge 3
Challenge3 to unlock car3. “Mr Truck: Unlock me Baby!”
-
50pts: There is an interesting string in the native code. Can you catch it?
-
100pts: Get the secret generated at runtime to unlock the carid=3. Security by obscurity is not a great design. Use real crypto! (hint: check the length when submitting the secret!)
静态key
第一种解法
- 分析题目后发现应该是对原生代码进行一个分析,所以应该会加载.so文件。
- 在MainActivity中找到了对原生库的加载:
- 在android killer中发现其实只有一个原生库:
- 想要找到其中的字符串一个简单的方法就是直接用strings命令,找到了静态flag:
第二种解法
- 而在参考CyberTruckChallenge19中作者使用了Frida动态获取该字符串
- 具体做法是首先打开so(作者使用的是Ghidra,我是用的是IDApro)分析发现里面只有一个函数,查看其C源码:
- 发现v18的值比较特别:
- 首先将一个字符串赋给了v18(其实这里以及可以判断这是静态flag了,不过作者在Ghidra好像没有获取到这个值),然后通过
strlen
函数获取了v18字符串的长度,最终存放在v13中,在while循环里通过比较v12与v13(字符串长度)的大小关系进行循环判断,并且将该字符串与另一个字符进行了异或操作。 - 上述整个过程其实非常像用密钥key对一个值进行加密的过程了。因为这里
strlen()
函数用该字符串做了参数,所以思路就是截获strlen()
函数的参数即可。 - 但是这里存在一个问题:在函数中可能有多次调用
strlen()
函数的地方那么哪一个是我们想要找到的呢? - 作者使用的方法是判断strlen的返回地址,若该返回地址在上述第三方库的内存地址之内,那么就是该第三方库调用的strlen函数
- 编写JS脚本如下:
// 该函数用来确认strlen函数是不是libnative-lib.so库中的那个,因为整个程序可能调用很多strlen
function isAddressInModule(moduleName, address) {
var module = Process.findModuleByName(moduleName)//返回对应的库
return address >= module.base && address < module.base.add(module.size);//base:基地址
}
function challenge3() {
//使用Interceptor.attach拦截目标函数的调用
Interceptor.attach(Module.findExportByName("libnative-lib.so", "Java_org_nowsecure_cybertruck_MainActivity_init"), {
onEnter: function(args) {
// 用第二个Interceptor 来attach libc.so 中的strlen函数
// 用this.strlen指向下一个Interceptor便于后面detach
//this指的是第一个Interceptor
this.strlen = Interceptor.attach(Module.findExportByName("libc.so", "strlen"), {
//如果在Java_org_nowsecure_cybertruck_MainActivity_init中调用了strlen就会进入onEnter
onEnter: function(args) {
// 对感兴趣的strlen输出其参数
//这里的this指的是第二个Interceptor对象所以是strlen函数
//this.returnAddress是strlen函数的返回地址也就是调用stelen下一条指令的地址(应该在libnative-lib.so库地址的范围中)
if (isAddressInModule('libnative-lib.so', this.returnAddress)) {
//如果在这个范围中就输出strlen参数的值
console.log("Challenge 3 key: "+ args[0].readUtf8String(32));
}
}
});
},
onLeave: function(retval) {
// 退出第一个Interceptor时不在监听strlen
this.strlen.detach();
}
});
}
function c3(){
//获取org.nowsecure.cybertruck.MainActivity类的实例
var mainActivity = Java.use("org.nowsecure.cybertruck.MainActivity");
//hook c3的k方法,在k方法中对库进行拦截,并执行k方法
mainActivity.k.implementation = function(){
challenge3();
this.k();//要在implementation中运行
}
}
//frida的main
Java.perform(function(){
c3();
});
- 使用Frida进行hook并加载脚本就可以得到strlen函数的参数啦:
- 点击unlock按钮后:
动态key
- 经过上面的分析我们知道动态key其实就是v8里的值,但该函数没有返回v8,所以不能用常规的方法输出v8的值,这里的思路如下:
- 当该函数返回时,存在栈中的局部变量并没有被清除,这时我们只需要输出栈顶指针更新前到更新后这之间栈的值就可以得到动态的key
- 又因为栈向低地址增长所以在Java_org_nowsecure_cybertruck_MainActivity_init返回后输出一定空间的sp指针之前的值就可能输出v8中存储的值。
- 所以在上面脚本的
onLeave
中输出栈中的情况即可 - 新的
onLeave
如下所示: