我爱玩碧蓝档案,鏖战了两三天,可以把frida打进去了,平心而论我觉得技术含量还是非常高的,对抗强度最高的一次,学到的非常多,这里记录一下。
起点
和之前il2cpp分析的一样,要想拿到静态的资源表、结构体、函数签名之类的,就需要通过il2cpp中的meta_init,il2cpp dump的原理就是这样,meta_init需要通过global-metadata.dat来获取全局的字符串信息,然后把C#转译出来的Cpp跑起来,这个转移出来的CPP本身就有一定的混淆效果,像是写代码时用到的字符串之类的信息都会被转储到global-metadata.dat中。(这一段准备工作,网上有大量的资料和工具)
而BA的global-metadat是加密了的(通过魔术头和熵值就能分析出来)、直接上手il2cpp.so去分析,也找不到解密的逻辑在哪里,最离谱的是我拿着Unity的源码找特征字符串引用、也是一无所获,找不到关键的逻辑,这就是第一步难关了。
合理地怀疑应该是libtprt.so,一搜发现旧的处理逻辑还有别人的分析,最终发现确实啊 global-metadata.dat是交由这个函数库解密后,提供给il2cpp使用的,接下来的操作就比较骚了(我都想笑了):
-
看见别人分析(25年的帖子、不是同一款游戏),有一些特征字符串,我顺着去在libtprt里面搜,发现真能找到对应的函数。神奇的是BA的global-metadata.dat的魔术头和他分析的版本一样,虽然内容不同。
-
在他帖子里看见,,他用了frida去做各种调试,在我这里是不行的,我没法动态调(这个下文会说BA的动态反作弊、难度真的非常大),最重要的是漏了一个mmap函数,我一想觉得就非常合理啊、载入静态资源分配内存,然后对内存做处理,提供给后续使用。
-
-
而这个函数在我这边一搜索发现:引用数量不多,可以分析!随手找了下真撞见了:有个很明显的魔术头匹配逻辑,最重要的是没多少混淆,往下追就是解密的逻辑,流程:识别魔术头→走解密分支→再做头部抹除,抄了一下解密的逻辑如下(其实也就是AES解密、密钥都写在里面):
1 | #!/usr/bin/env python3 |
魔术头这个:
解密后:
往下一翻就是各种明文字符串,至此也就正式迈出了第一步,不过试了下il2cpp dump是不支持这个版本的unity还是怎么样不知道,我感觉il2cpp dump其实也没那么好用,因为导出的符号表、再倒回IDA里面非常非常慢,大型游戏一堆资源、我又不一定每个函数都看,就不太喜欢那个方案。
所以这里还有第二种办法:Zygisk il2cpp dump,Github搜搜就有,从内存中dump下来,结果是一样的:
虽然我觉得拿到这些静态符号一样都是没卵用,里面可能也有些有意思的东西吧,接下来就是注入、调试。
开日
要日就必须调试、要调试就必须动态、要动态就必然需要通过某些工具、某些手段来调试、这些都是痕迹,反作弊就是阻止工具的使用、拖慢逆向进度就行,工具都附加不上、手动调的话得调到天荒地老,我对frida比较熟就一定要用frida!
前段子分析过别人的作弊手段,其实漏了不少的东西,例如他对libc还做了一点手脚,通过dobbyhook来inlinehook的内容之类的,应该是用来过掉保护的,我写了个Zygisk模块来仿造那个功能,不过策略更加激进一点:我想要把frida-gadget打入游戏内部爽调,像这些静态资源表对我而言其实没有Stalker好用。
首先就是注入Frida啊、肯定失败了。翻看libtprt.so里面又有一堆混淆,CFF、CFG、FLA一堆,梦到什么往里加什么混淆,继续翻看前辈们的日法,自己也写了个去控制流平坦化的,不过只能去CFF,后面两种我还没研究明白。
原理:CFF依赖分发块来执行不同的真实块逻辑、然后真实块尾部回到分发块继续跑下一个真实块,而分发依赖的就是某个寄存器变量,通过binary ninja + unidbg可以看的非常清楚。
所以处理也挺简单的,选定分发器的寄存器变量遍历所有真实块,再通过真实块尾部下一个跳转的区域判定下一个连接的真实块就行。脚本、看以后再说要不要放出来吧,写得很乱,我都不想看第二遍。
处理部分之后全部跑了一遍函数,才发现的CFG、FLA之类的一堆混淆,就摆了、直接硬来吧,结果发现真行:
-
入手点:Frida检测、首先我通过Frida-sever的办法去注入Frida-agent挂掉了,第一时间想到的就是检测127.0.0.1、/data/local/tmp这种特征嘛:
-
一找一堆,有些混淆我是去过了的,有些是没有,不过不影响:
噩梦の起点
看过前几篇博客的话,针对Frida、我看了源码,里面的特征比想象中要多得多,一个一个去改、专门适配这个tprt,我觉得根本就不现实:
只有Frida-gadget的改动量能够接受、不过还是会因为大大小小未知的问题导致无法使用的,今天是以后也肯定会是。
也去尝试了自己改源码编译,后来还是不行,差点就放弃了。他比想象中的问题还要多得多,一定要hook libart、一定要通过libc来初始化,还有几个线程名是在依赖库里面起的,改完源码得把依赖库的源码下载下来编译一轮,再重新编译frida,因为Stalker功能太好用、付出点代价是正常的。
正常人都会选择自己手动来调试更加方便,但我偏不哈哈、后来干脆换了个思路:ZygiskIl2cpp能起作用?ZygiskFrida难道不行?但一样失败了。
写到这里有点困了,有空再继续写吧。总之下半部分都是围绕:攻守互换、我来检测你的检测来的、写了个行为分析ptrace+secommap来专门针对安卓反调试检测,太长了一时半会说不完,有空再继续写。