Tencent_Android2023初赛复现
0x0 前言 学习的文章链接
https://bbs.kanxue.com/thread-276893.htm
https://bbs.kanxue.com/thread-276896.htm#msg_header_h2_2
https://bbs.kanxue.com/thread-276949.htm
可以看过以上三篇文章再看我的复现,主要是扩展细节和记录我的复现
0x1 获取flag_修复so文件
题目要求游戏获得1000分出现flag,解包apk发现unity打包的游戏,第一步思路通过github上的工具il2cppdumper(如何使用在我的文章D3MUG中有详细操作)生成信息和函数名。然后发现无法修复就需要动态解密后的so文件(调试的时候即可发现不一样,有自解密)。
第一种文章中的frida_hook方法dump so(用的是fallw1nd师傅文章中的代码)
function DumpIL2CPP() {
var libil2cpp = Process.getModuleByName("libil2cpp.so");
WriteMemToFile(libil2cpp.base, libil2cpp.size, 'libil2cpp.so');
}
function main() {
HookLibWithCallback('libil2cpp.so', DumpIL2CPP);
}
setImmediate(main);
第二种文章中的方法使用gg修改器动态dump so
https://gameguardian.net/download 下载链接
注意这个工具需要root以及模块支持
dump下来之后拉入ida会发现很多地址函数爆红,使用010editor工具,在模板中下载elf格式然后f5加载,将两个so对比会发现dump下来的so少了很多,将缺的从原来的so中复制过去即可,拉入ida就正常了最后就是修复函数名和结构体回去,还是在d3mug文章中有写到如何在ida中修复。
0x2 获取flag_反调试
在上面的文章中可以看到有啥反调试,这里记录下我尝试绕过的过程
re.frida.server 改名为fs_64 运行命令 ./fs_64 -l 0.0.0.0:19233 另起cmd转发端口adb forward tcp:19233 tcp:19233 frida 执行命令frida -H 127.0.0.1:19233 -f com.com.sec2023.rocketmouse.mouse -l .\tecent_hook.js
|_|sher师傅的方法发现延时弹窗 hook sleep函数打印调用函数找到检查的关键代码(思路1)
function AntiDebug()
{
var sleep_addr = Module.findExportByName(null, "sleep");
var sleep = new NativeFunction(sleep_addr, "void",["int"]);
Interceptor.attach(sleep,
{
onEnter: function (args)
{
args[0] = ptr(1000000);
console.log("hooked sleep");
console.log('sleep called from:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');
},
onLeave: function (returnValue)
{
}
})
}
AntiDebug();
juice4fun师傅使用github rwProcMem33的工具来进行硬断crc检测的位置(思路2),需要将驱动编译出来刷进手机内核,还需要编译出工具,我只编译出了客户端,服务端编译环境搭不起来,以后买pixel了再试试
https://blog.csdn.net/qq_46832407/article/details/129971512
这是我尝试刷内核时学习的博客和b站视频,我的小米mix2s刷不起来
ida调试名字为as64 运行命令 ./as64 -p 19234 另起cmd转发端口adb forward tcp:19234 tcp:19234
我自己尝试的方法挂起所有线程(思路3)
def suspend_other_thread():
current_thread = idc.get_current_thread()
thread_count = idc.get_thread_qty()
for i in range(0, thread_count):
other_thread = idc.getn_thread(i)
if other_thread != current_thread:
idc.suspend_thread(other_thread)
调试的时候刚断住,ida运行这段python代码挂起所有线程,然后在ida的右边线程窗口一个一个恢复,发现只要是把thread名字挂起即可 一般有3个,缺点就是无法运行游戏了,但是丝毫不影响调试输入mod menu 然后就可以任意输入调试断住关键代码了
0x3 获取flag_getflag
获取flag需要游戏金币超过1000,尝试在so中搜索关键英语coin mouse这些(我一直想如果函数名没有正经的取,直接加大难度)怎么分析上面的文章都很详细,我这里就是把判断1000改成了判断0,进去就打印flag
function main() {
var libil2cpp = Process.getModuleByName("libil2cpp.so");
var insn = libil2cpp.base.add(0x4653CC);
Memory.protect(insn,4,"rwx");
insn.writeByteArray([0x1F,00,00,0x71]);
}
setImmediate(main);
0x4 分析cheat_encrypt_libsec2023_3B9D4
怎么算出是跳转到这个地址的
一直点进函数到这里,不能f5,看汇编,然后这里有一个BR跳转hook这里的x2
主要看13B8DD4的代码位置即可,其他是我调试hook其他位置的代码
function hook_System_Convert__ToUInt64_8763844(){
var libil2cpp = Module.findBaseAddress("libil2cpp.so");
if(libil2cpp){
// console.log("libil2cpp:",libil2cpp);
var System_Convert__ToUInt64_8763844 = libil2cpp.add(0x85B9C4);
var SmallKeyboard$$iI1Ii_4610736=libil2cpp.add(0x465998);
var call_13B8DD4 =libil2cpp.add(0x13B8DD4);
console.log("System_Convert__ToUInt64_8763844 addr:",System_Convert__ToUInt64_8763844);
Interceptor.attach(System_Convert__ToUInt64_8763844,{
onEnter: function(args){
console.log("onenter:",args[0],args[1]);
},onLeave: function(retval){
console.log("retavl:",retval);
}
});
Interceptor.attach(SmallKeyboard$$iI1Ii_4610736,{
onEnter: function(args){
console.log("SmallKeyboard$$iI1Ii_4610736:onenter:",args[0],args[1]);
},onLeave: function(retval){
console.log("SmallKeyboard$$iI1Ii_4610736:retavl:",retval);
}
});
Interceptor.attach(call_13B8DD4,{
onEnter: function(args){
console.log("call_13B8DD4:onenter:",this.context.x2);
},onLeave: function(retval){
console.log("call_13B8DD4:retavl:",this.context.x2);
}
});
var call_13B8DD0 = libil2cpp.add(0x13B8DD0);
Interceptor.attach(call_13B8DD0,{
onEnter: function(args){
console.log("call_13B8DD0:onenter:",this.context.x8,this.context.x2);
},onLeave: function(retval){
console.log("call_13B8DD0:retavl:",this.context.x8);
}
});
}
}
动调查看libsec的基址
hook的跳转地址相减即是
libsec.so+0x31164然后点进函数中到3B9D4函数中
动调可以看见我们的输入我输入的是666666 16进制为 0xa2c2a x0是存储的位置
可以看到混淆csel 然后一个跳转BR,这里BR调试起来可以知道具体怎么走,|_|sher师傅文章中写了具体的静态patch和如何计算跳转地址修改即可,我这里就举一个例子
mov x8,xzr xzr和wzr等于0寄存器 理解成赋值0,与2进行比较,w9赋值0x28 csel先看最后一位cc 判断的条件无符号小于,意思是if(x8<2) x10=xzr else x10=x9 0ff_72c40里面存了地址,根据x10的索引取出地址加上一个固定基址,最后跳转,我这里是已经patch过的。所以patch只需要根据什么的cmp跳转条件来patch跳转判断即可,GE代表大于等于就跳,相当于x8大于等于2就跳转,不然就往下。
代码还是比较清晰的,逆算法就不贴了,师傅们的文章中都有
0x5 分析cheat_encrypt_libsec2023_3A924
总体就是搭建一个java环境,解密函数名,传入我们的输入,apk中没有encrypt,我们动态调试直接可以dump dex下来也可以使用工具脚本
frida-dexdump 命令 frida-dexdump -H 127.0.0.1:19234 -f com.com.sec2023.rocketmouse.mouse
在classes07中
3个反编译工具中 GDA4.06 pro jadx jd-gui
GDA整体反编译的最好,就是逻辑写法反编译会有点错误,复制到android studio不能直接跑起来,break 和 continue的逻辑有问题,也不知道是不是android studio的问题。
jadx相比较差 好的一点就是data反编译的好
将dex转换成jar 用jd-gui反编译 算法的位置效果特别好,反编译到一起,移位也很清晰
第一种方法使用在线网站把字符串都hash出来手动或脚本计算 自己理清控制流。
第二种方法使用andriod studio 调试这串java代码跟踪代码流,就是需要将代码优化一下先,死住的地方还是需要静态重推算一下,如果可以像ida反编译效果那么好就好了拉过来就调试。
第三种方法解混淆,搜了半天BlackObfuscator的解混淆,有师傅会真得出篇wp。
0x6 分析cheat_encrypt_libil2cpp_465AB4
动调可以发现回到了libil2cpp中,后面就是vm位置的分析实在不会。
0x7 最后的xtea
动调取出key即可
0x8 外挂启动判断
var libil2cpp = Process.getModuleByName("libil2cpp.so");
var insn = libil2cpp.base.add(0x465D14);
console.log(hexdump(insn));
console.log("Patching..");
Memory.protect(insn,4,"rwx");
insn.writeByteArray([0x1f,0x20,0x03,0xd5,0x1f,0x20,0x03,0xd5]);