做了一套工具测试IOS小红薯x-mini-sig算法研究

一直比较喜欢逆向分析的研究,多年前非常喜欢dynamoRIO(好像没怎么更新了也不支持ios mac平台)专门研究学习了很久,到最近尝试用frida进行研究,不过总是遇到很多问题,被针对性检测都是小问题(比如有的软件你的lib出现frida名字就直接崩溃),主要是兼容性问题和效率问题,frida发挥最大作用的地方应该是越狱状态下的调试,在非越狱状态下有的把frida打包启动就直接崩溃了,可能是依赖项太多,注释掉frida相关功能就好了。我觉得效率问题才是最致命的,在手机上可能并不明显,因为手机app厂商考虑的最核心问题是用户体验问题,而mac上就不同了,我用frida曾尝试逆向跟踪imazing结果发现经常无限卡死(我曾相信frida可以从启动开始同时跟踪所有线程),后来我才发现其中的死结:imazing有的线程启动了2秒后定时重启的功能,如果2秒内frida stalker跟踪没有完成(这经常发生因为imazing的保护强度是非常高的)就再开启一个相同的线程,结果就是无限循环最后崩溃。而且你用frida stalker跟踪你需要先确定你找的东西的位置,这就有点像是先有鸡还是先有蛋的问题。

所以,我就尝试是否可以在非越狱状态下开发一套工具,既能跟踪所有函数(对,是所有线程运行时候尽可能多的函数)的调用轨迹,又能跟踪所有堆内存的读写,这样你就几乎一眼看清楚整个程序的全部逻辑(当前,如果数据量特别巨大就需要分段跟踪)。经过查阅相关的学术文献和多年的经验思考,我使用了一种算是比较新的逆向思路去解决这个问题,设计这个工具。

学术界程序分析有静态二进制重写翻译和动态二进制跟踪两大主要方向,我们通常网上看到的逆向类文章其实主要是动态二进制跟踪为主,因为静态翻译看上去比较麻烦,我简单介绍下二进制静态翻译,就是把程序通过静态分析完成所有插桩,但是因为程序间接跳转地址是未知的所以有很多问题,而且x86指令大小长度不定,所以极大概率会造成指令错误解析、不完全解析,但是mac ios上就几乎没有这个问题,因为arm64指令都是4字节的,而且由于苹果系统越来越严格的要求,几乎完全禁止了软件动态修改自身代码jit,这对于静态分析其实是非常有利的。所以我就结合了静态翻译和动态运行结合的思路设计一种程序跟踪工具。首先,通过结合ida的静态分析结果,把需要的程序片段整个静态插桩,这样就几乎不存在效率的问题,实测下来速度影响不超过50%,而frida stalker的速度影响可能是几十倍的降低。

首先,我在mac上测试imazing跟踪结果,终于可以跟踪imazing所有函数调用和堆内存读写了,因为它的主程序也才几十兆,但是它的保护强度却是非常高的,它的一些核心功能调用之前往往需要几万、几十万次的函数调用(有一次我尝试无聊想直接跳到最后直接被作者嘲弄了,看到了一句:dead face),但是我的工具全都实时跟踪下来了,只是由于没有付费证书,一次完整的流程都没有,我只能跳过它的一些次数限制,而一些付费功能我就放弃研究了。

后来,我在ios系统上跟踪,就遇到了新的问题,ios 主程序异常庞大,用ida分析可能需要几个小时,就不得不分段跟踪,但是对于系统调用,我们可以几乎全量跟踪,因为ios系统的一些核心信息全部可以通过Object C的msgsend函数看到,虽然都说android逆向更容易,其实ios程序这种结构也为逆向分析提供了巨大的帮助。比如我最近选择尝试逆向小红薯的x-mini-sig算法。就发现一些有意思的结果:


它的输入就是httpbody+body哈希+x-mini-mua参数 最后在进行sha256哈希,前面的几乎都能用ida清晰看到,但是最后他们用了一些自定义的算法可能是混淆控制流虚拟化之类的,但是强度依然不算高,总共跟踪了1000个函数左右,这和imazing上万、几十万函数不能相提并论。关键是,这些混淆函数模式几乎相同,寄存器也是有限的几个,而且堆栈地址(16b9a5c58)不变,说明他们就是专门为这个一个函数开发了一套混淆虚拟化工具,而且代码的集中度非常高,这些函数的跳转地址固定(ida中这些函数跳转地址混乱看上去很恐怖),其中甚至不再有其他系统调用而仅仅是把哈希地址的前半部分给修改了(前面的哈希结果作为输入)。所以最后我用python 100多行代码就完成了整个算法的unicore模拟调用执行,算法耗时0.1S多。

x-mini-s1呢,看上去就是根据一个递增的序号进行了他们自定义的一个没有混淆过的sha256哈希,然后同样用上述流程修改了这个哈希的后半部分。对了,我观察到的,这个值就是有限个固定值,那这样加密的意义是什么?

x-mini-mua呢,由于我对于加密算法不是很熟悉,一些细节我没有深究,但是这个参数的计算函数都是ida可解析的明文函数,输入就是系统的一些环境信息的json zlib压缩(789c开头),经过一个秘钥加密,这个秘钥来自一个随机数,看上去还有一个100多字节固定的初始向量吗?

上面这些都是函数调用跟踪和系统调用跟踪(所有线程的,是系统全量跟踪的),下面的是堆内存数据读写跟踪(如果愿意可以跟踪栈内存,因为红薯的混淆算法实际上数据都是在栈上操作的),也是几乎所有线程的。如果要用frida stalker做这种跟踪,速度可能极慢,而且你要先确定跟踪哪个线程,从哪儿开始


理论上来说,可以做到从某个数据出现,然后逐层往上回溯,发现这个数据最终是哪里来的,但是实践中会有遗漏中断,因为有些数据的转移是通过系统调用memmove memcpy完成的,而且为了效率我没有跟踪所有读写内存的指令,会错过一部分,但是大部分情况下,人工检查就可以基本看清数据来龙去脉。

以上仅供技术研究交流,谢谢!

2 个赞

chomper 也是基于 unicorn,支持直接模拟执行出 xhs,可以看看

2 个赞

看起来Ios逆向简单多了啊,安卓上x-mini-mua和x-mini-sig这两个算法Unidbg补环境都不简单。虽然目前算法我已经还原出来了,现在准备试试s1,但是有vmp明显复杂了很多

请问该工具是基于qbdi做的吗

结合ida分析结果,静态重编译插桩打包,运行时候自动日志记录。

堆内存的读写的追踪有效果展示吗,能做到什么程度

看到上面最后那个截图了吗,那就是最后的结果。我首先分析了当前指令是否是堆内存读写(可能误判,主要是用类似学术界污点传播的简单静态分析方法),因为通常情况下栈内存读写太过于频繁(有些公司自研的算法只用栈读写数据就需要另当别论了),然后我进行插入大概10几条指令(这个指令比较关键,因为既要非常简单又要记录数据足够,想象我们平时插入函数hook可能光是保存恢复寄存器都要几十条指令了,这也是通常的方法无法进行大规模内存记录的原因,我能想到更快的更好的方法可能就是就是据说一些cpu支持的指令读写时期内存硬件记录了,但是我从来没试过,也没见人用过)就可以记录下来这个读写的指令地址和内存地址和数据,还有线程id(如果是采用frida那种读写监控,就相当于插入了上百条指令,frida类似的方法是无法应付那些具有海量计算加解密程序的),所以亲测下来几乎可以实时,运行效率会慢50%。

就是针对内存读写和函数调用的汇编指令,塞跳板指令,跳到你的日志函数,执行完日志再返回吗?666

大概是,都是静态重编译,谈不上是函数,因为函数开销太大了(中间不能再去调用其他任何自己的函数或者系统函数就要完成记录,否则太慢了)。因为一般情况下堆内存读写占比不会太大,所以最后总的程序代码膨胀率在2倍或者3倍,但是如果出现严重误判,把所有栈内存读写给记录了那就是几十倍的膨胀,速度也无法接受了。

能记录程序从启动到运行的所有内存和函数记录吗,效率咋样,这里记录的时候会多线程data race吗

是的,mac os 上的iMazing加密强度应该算是很高的了吧,我就是以这个为基础测试的(最开始我误以为可以用frida监控启动后所有线程运行,哪怕是什么都不做也做不到,因为这种动态jit应付imazing这种程序天然不行,直接因为速度慢了一点点而死循环崩溃,后来才发现的),最后记录程序运行从开始到结束堆内存(可能遗漏或错误)读写分线程保存的。