题目来源:https://adworld.xctf.org.cn/
解题环境:kali + frida 12.8.0 + Win10 + IDA PRO 7.0
整体思路
静态分析.dex,应用的核心思路为com.gdufs.xman.MyAPP类中最先执行的initSN函数
其次,当我们在com.gdufs.xman.RegisterActivity中输入注册码,点击注册后,执行saveSN函数
只有当MyApp类中定义的静态字段m=1时,才会执行work函数
而上述三个关键函数,皆为JNI函数,具体实现在libmyjni.so中
那么解题的重心就需要转到so层了
使用IDA打开libmyjni.so,搜索Function name并未找到三个关键函数,说明采用了动态注册,通过frida脚本hook RegisterNatives函数可以得到三个函数在.so文件中的偏移为0x13b1,0x11f9,0x14cd。
分析initSN函数可知该函数主要功能为:打开/sdcard/reg.dat,读取其中的数据与"EoPAoY62@ElRD"进行比较,如果相同则m为1,如果不同则m为0。
再看saveSN函数,该函数的逻辑为:将用户输入的注册码经过一系列加密运算处理后,写入到/sdcard/reg.dat中
work函数的功能主要是当检测到m=1时打印提示信息
至此,题目意图已经非常明显,当我们输入的注册码经过一系列加密处理后为"EoPAoY62@ElRD",那么就是正确的注册码,即此题的flag。
这里暂不考虑加密细节,直接利用frida对加密函数进行重放以爆破得到明文,下面给出完整爆破代码。
//frida -U -f com.gdufs.xman -l demo.js --no-pause
//int fputs(const char *str, FILE *stream);
var fputs_str = null;
function hook_so_libc_fputs(){
var libc_addr = Module.findBaseAddress("libc.so");
var fputs_addr = Module.findExportByName("libc.so", "fputs");
console.log("[libc.so] hooked fputs, fputs_addr="+fputs_addr);
Interceptor.attach(fputs_addr,{
onEnter: function(args){
fputs_str = args[0].readCString();
//console.log("[fputs] str="+fputs_str);
},
onLeave: function(retval){
}
});
}
function algorithm_cracking(){
Java.perform(function(){
var MyApp = Java.use("com.gdufs.xman.MyApp");
var MyApp_instance = MyApp.$new();
var dict = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()";
var ciphertext = "EoPAoY62@ElRD";
var ciphertext_array = new Array("EoP", "AoY", "62@", "ElR");
var plaintext = "";
var flag = 0;
var t,i,j,k,g;
var input;
for (t = 0; t < ciphertext_array.length; t++) {
flag =0;
for (i = 0; i < dict.length; i++) {
if (flag == 1) break;
for (j = 0; j < dict.length; j++) {
if (flag == 1) break;
for (k = 0; k < dict.length; k++) {
input = "" + dict[i] + dict[j] + dict[k];
//console.log(temp);
MyApp_instance.saveSN(Java.use('java.lang.String').$new(input));
if (fputs_str == ciphertext_array[t]) {
console.log("input=" + input + "--output=" + fputs_str);
plaintext = plaintext + input;
console.log("plaintext="+plaintext);
flag = 1;
break;
}
}
}
}
}
for (g = 0; g < dict.length; g++) {
input = "" + dict[g];
MyApp_instance.saveSN(Java.use('java.lang.String').$new(input));
if (fputs_str == "D"){
plaintext =plaintext + input;
break;
}
}
console.log("plaintext="+plaintext);
console.log("algorithm_cracking finished");
});
}
setImmediate(hook_so_libc_fputs);
setTimeout(algorithm_cracking,3*1000);
这里注意一个细节:算法爆破一定要注意输入明文与输出密文之间的对应关系,此题经过多次测试,可以得到以下规律:1明文长度与密文长度等价;2明文以3字符为一组进行加密,即爆破算法以3字符为单元依次进行破解。
运行frida脚本可以得到本题的最终flag为xman{201608Am!2333}
实际运行起来会非常耗时,大概需要20分钟才能爆破完成。
最终小结
本题主要考察了算法逆向方面的知识点,较为简单。