文章总结: 本文详细分析了快手应用中的NS_xfalcon算法,这是一个基于VMP保护的魔改ChaCha20加密算法。作者通过内存监控、数据流追踪等技术手段,逐步还原了算法的实现细节,包括数据生成过程、固定数据来源、明文处理方式以及算法的多个魔改点,如初始矩阵修改、循环左移数变化、分组处理方式等。文章提供了具体的逆向分析方法和关键代码段,对理解移动应用中的加密算法实现具有参考价值。
综合评分: 86
文章分类: 逆向分析,二进制安全,移动安全
快手__NS_xfalcon算法分析
原创
Kx
Frida and So
2025年10月28日 12:20
上海
声明
本文仅供学习交流,如有侵权请联系删除!
本人微信,QQ不创建任何群聊,不添加任何群聊,不卖课,无任何收费项,勿要上当受骗!
参数
__NS_xfalcon:vmp参数。
文章中提及 固定 一词并不严谨,请多多包涵。
分析
补完环境后,先跑一遍trace,然后根据memcpy对数据进行拆分,如下图可以看到整体一共分为了 4 部分,经过个人测试,前三部分未发现变化,前三部分的生成我还未进行分析,本文仅对第四部分进行分析。
从memcpy处向上查看0x2f从哪里获取的,这个地址已经在vmp内部了,都是从0xe4ff6f30处获取到的,那就对这个地址进行内存读写监控,这些数据都在0x1bc14处被存入,打开ida跳转到该地址会发现还是在vmp内部,使用unidbg下断更会发现不会断在你想断的位置。
在日志中搜索0x1bc14地址,再次向上观察日志就会发现,从内存中取出后都跟0xff进行了&运算,而且是从0x124c4728开始向后取数据,那么继续对0x124c4728进行监控。
"ldrb w0, [x0]" x0=0x124c4728 => w0=0x2f
"and x9, x0, #0xff" x0=0x2f => x9=0x2f
又来到了0xe600处,然后就是跟上面一样分析这个handler,又是熟悉的操作,但是少了一个0x30,它走的handler地址跟0x2f这些是不同的,虽然地址不同,但操作是一样的,继续对0xe4ff6e68地址进行监控。
"ldrb w1, [x21, x9]" x21=0xe4ff6e60 x9=0x8 => w1=0x2f
"strb w1, [x0]" w1=0x2f x0=0x124c4728 => w1=0x2f
"ldrb w1, [x21, x9]" x21=0xe4ff6e60 x9=0x8 => w1=0x30
"strb w1, [x0]" w1=0x30 x0=0x124c4729 => w1=0x30
这次就大有不同了,写入地址,写入形式均有所差异,分为两部分进行追踪,先追踪前 2 字节,然后对后续字节进行跟踪,写入地址分别为0x17d34,0x18034。
data value = 0x000000000000ef2f, PC=RX@0x12017d34[libksxgs.so]0x17d34, LR=RX@0x1201bd5c[libksxgs.so]0x1bd5c
data value = 0x000000000000ef30, PC=RX@0x12017d34[libksxgs.so]0x17d34, LR=RX@0x1201bd8c[libksxgs.so]0x1bd8c
data value = 0xffffffffffffefa9, PC=RX@0x12018034[libksxgs.so]0x18034, LR=RX@0x1201bbc0[libksxgs.so]0x1bbc0
data value = 0xffffffffffffefcf, PC=RX@0x12018034[libksxgs.so]0x18034, LR=RX@0x1201bbc0[libksxgs.so]0x1bbc0
来到0x17d34处,是一个异或操作,未知数就变为了0xef64与0x4b、0x54,先追后面的两个字节,继续内存监控,会发现写入后返回的地址是一致的,跳转到0x218ec中,这次不在vmp里面了,是memcpy从日志中找一下这个点,最后是在127c0000地址处cpy了13920的数据,这一大段里面的数据,后续多次被用到,是固定的。
"ldrh w9, [x26, #0x10]" x26=0x129a80e8 => w9=0x4b
"ldrh w9, [x26, #0x10]" x26=0x129a8120 => w9=0x54
写入地址
data value = 0x000000000000004b, PC=RX@0x1219c2ac[libc.so]0x1c2ac, LR=RX@0x120218ec[libksxgs.so]0x218ec
data value = 0x0000000000000054, PC=RX@0x1219c2ac[libc.so]0x1c2ac, LR=RX@0x120218ec[libksxgs.so]0x218ec
0x18034数据的生成与0xef64有关,跟踪过去就豁然开朗了,0xef64,异或,0xfffefa9,全部串连起来了,现在的目标就是去追踪0xef64与0xcd...这段数据。
直接在日志搜0xffffffffffffef64,然后找一下0x109c的来源,还是直接搜,跳转到0x17dec,一直往上追踪发现这是一个累加操作,而0xcd、0xab、0xab不正是被异或的数据吗,那么完美闭环形成,先0xcd … 累加得到 0x109c,然后转64位无符号整数,前4字节与固定数异或,后续与0xcd…异或,最后得到 2f 30 a9 …
"sub x8, x8, x9" x8=0x0 x9=0x109c => x8=0xffffffffffffef64
[libksxgs.so 0x17de8] 0x12017de8: "add x8, x9, x8" x9=0xfb x8=0xfa1 => x8=0x109c
[libksxgs.so 0x17dec] 0x12017dec: "str x8, [x21, x10]" x8=0x109c x21=0xe4ff6e60 x10=0x18 => x8=0x109c
二阶段
正式开启二阶段,未知数0xcd ..与初始累加数0x9f,这边0x9f为固定值,0xcd...内仅有 4 字节是跟明文相关变化的,从add 0xcd向上翻,可以看到加载后还是要与0xff进行&运算,继续监控该地址后,每一个都要跟,但是最关键跟明文有关的就b5 7e 8d 43这段。
[libksxgs.so 0x0e5f8] 0x1200e5f8: "ldrb w0, [x0]" x0=0x124c472a => w0=0xcd
[libksxgs.so 0x0e5fc] 0x1200e5fc: "ret"
[libksxgs.so 0x1bc0c] 0x1201bc0c: "ldrsh x8, [x26]" x8=0x10472a x26=0x129a7fb8 => x8=0x8
[libksxgs.so 0x1bc10] 0x1201bc10: "and x9, x0, #0xff" x0=0xcd => x9=0xcd
固定数据
先介绍一下固定数据来源,以0xffffffffbaac98df为例,全局搜索最终来到0x089b8,ida跳转到该地址进行反汇编,这个地方就是variant解码函数,这个0x8aa6ce43就是0xc39c9bd508解码后的结果,python代码如下图,注释中写的是日志中两种解码方式,标准算法是0x7f,7,0x80。基本上都是在初始化生成的
"neg x10, x8, lsr #1" x8=0x8aa6ce43 => x10=0xffffffffbaac98df
[libksxgs.so 0x089b8] 0x120089b8: "orr x20, x8, x20" x8=0x80000000 x20=0xaa6ce43 => x20=0x8aa6ce43
明文数据
这个才是重点,直接全局搜然后内存监控,又是异或跟与运算,示例代码段中仅取一处,其中0x2dd345c0通过内存读写一直跟踪最终是在sub_7EE4中读取bit流读出来的,用的是魔改variant。
"ldr x0, [x0]" x0=0x123c3028 => x0=0xb983e281b57e8d43
//示例
0x1200e5f8: "ldrb w0, [x0]" x0=0x123c163c => w0=0x2d
0x1200e5f8: "ldrb w0, [x0]" x0=0x123c3028 => w0=0x6e
0x1201bbc4: "and x9, x0, #0xff" x0=0x2d => x9=0x2d
0x1201bbc4: "and x9, x0, #0xff" x0=0x6e => x9=0x6e
0x12018030: "eor x8, x9, x8" x9=0x2d x8=0x6e => x8=0x43
最后拼接起来是:
0x2dd345c0 ^ 0x6e5e3b75 = 0x438dbe75 我们需要的数
0x2dd345c0 ^ 0xac31c679
对0x6e5e3b75这段数据进行跟踪,监控0x123c3028这段地址,还是在0xe600处写入,像之前一样跳转到0xe600向上看,最关键的一点来了,0x6166393562303838这个数值转utf-8就是af95b088,终于到最后加密了,这个东西就是明文加密生成的。
0x12018030: "eor x8, x9, x8" x9=0x61 x8=0xf => x8=0x6e
0x12018030: "eor x8, x9, x8" x9=0x66 x8=0x38 => x8=0x5e
0x12018030: "eor x8, x9, x8" x9=0x39 x8=0x102 => x8=0x13b //实际取的时候是 0x13b & 0xff
0x12018030: "eor x8, x9, x8" x9=0x35 x8=0x40 => x8=0x75
//后续还有4字节
加密算法
全局搜af95b088,找到后就是逆推算法了,这个算法特征不是很明显,采用的是魔改chacha20,特征不明显的原因在于初始字矩阵,循环左移位数等都被魔改了,如果你运气好的话,在memcpy会看到chacha字样,然后还原算法。
下面就不详细跟了,简单说一下如何在vmp还原这个魔改算法,采用插桩的办法,下面的这 4 个地址就是核心加密轮操作,提取后对比着标准的chacha20进行还原即可。
"0x183b4", "0x18860", "0x185fc", "0x187dc"
算法魔改
最后,介绍一下算法魔改点:
- 明文加密前需要添加
HUDR_sFnX+n5uAUNVsMPNK3DOP5wnti1Lc8Axjy5z88T61A==。 - 加密分组分为
256字节一轮,当分组不满足256字节并且不满64字节直接&0xffffffff,如果大于64字节但不满256字节,第一部分64字节数据会与下一部分异或后&0xffffffff,如果大于256字节,则如下代码段。
a,b,c,d分别为64字节
temp = a ^ 0;temp1 = temp ^ b; temp2 = c ^ temp1; temp3 = d ^ temp2
res = temp3 & 0xffffffff
- 循环左移数为
0x10、0x14、0x18、0x19。 - 初始矩阵魔改,每一轮加密结束后需要更新。
- 明文参与轮操作,
state[a] + state[b] + msg[j]。 - 轮操作获取明文的索引,10次轮操作循环均不一致。
counter也就是state[12]每轮都需要异或,最后一轮异或长度计算出的值。
temp = 总长度 / 4
x8 = 总长度 - (temp * 4)
temp1 = 4 - x8
temp2 = (总长度 + temp1) / 4
以256+256+16字节为例,第一组counter^0x40,第二组counter^0x80,最后一轮^temp2
-
nonce也就是state[14],当轮数为最后一轮时,会替换为0x65412cb0。 -
轮操作结束后,
W_state与state的异或魔改了。
最后展示魔改后算法获得的结果,也是得到想要的0xaf95b088了。
总结
剩余部分后续有能力了会尝试还原vmp。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:Frida and So Kx《快手_NSxfalcon算法分析》