声明
本文章中所有内容仅供学习交流使用,不用于其他任何目的,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!若有侵权,请联系公众号【逆不逆】删除。
前言
写的不好,大佬们多担待。
此次目标为在不使用AST的情况下对某书web请求头中的x-s参数进行分析,x-s这个参数的算法经过多轮较大的升级,MD5、DES、AES到现在的RC4。
流程分析
参数定位到方式有很多,XHR断点、Request initiator、Hook JSON、关键词搜索等方式都可以辅助定位到x-s是通过seccore_signv2方法生成的,很多大佬都分享了详细的定位方法,这里就不再叙述了。
我们看一下seccore_signv2方法的返回值 "XYS_" + (0,h.xE)((0,h.lz)(JSON.stringify(l))) ,将l转为json字符串再传给h.lz也就是encodeUtf8,再传给h.xE也就是b64Encode,从而得到x-s的值。
再来看l的组成,其中x3就是我们要重点分析的对象,是由window.mnsv2返回的,传入了三个参数c、d、s,c是通过拼接api+params或者payload得到,d和s是通过h.Pu方法生成,是个md5,感兴趣的小伙伴可以自行分析验证。
我们在调用window.mnsv2的位置打上断点

然后我们进到vmp里面,看return

再进到_0x499f96方法里,看return

再进到_0x285423方法,看到了本文需要重点分析的位置

JSVMP在我们所见到的代码形式中目前主要是两种。一种是这样的if/else if,另一种就是控制流,当然还有三目运算或者嵌套三目。
通常情况下我们会在指令执行完之后打印寄存器中的值,但是这里似乎不好断点,那么就退而求其次,在执行执行前输出寄存器中的值,也就是上一条指令执行之后的值。
在这个位置进行条件断点

1 | if(_0x55fb47[_0x228e18] !== undefined){console.log('索引: ', '0x' + _0x29cd7c.toString(16), '值: ', _0x55fb47[_0x228e18])}; false
|
个人习惯是从后往前看日志,大致过一遍,后续再去跟踪具体的值和算法。
这里看到了x3的值、拼接mns0301_之前的值。
1 2 3 | goaKqsgaiOLTRTjHDt0idvA3GYCfR85VE/jxYikI2jnU7z4dMxCs16LN+dVQyp4VtPoXc0fE50HOt8h3Up9nQywKnaRawedQTl2uVu6ToV+tFOgYLAjU0JHKXS7aLEWv1fMCnpsRzRJPUaT3lHxKSndVZlciE0JRIk0OHNRRTO2mZggdKont
mns0301_goaKqsgaiOLTRTjHDt0idvA3GYCfR85VE/jxYikI2jnU7z4dMxCs16LN+dVQyp4VtPoXc0fE50HOt8h3Up9nQywKnaRawedQTl2uVu6ToV+tFOgYLAjU0JHKXS7aLEWv1fMCnpsRzRJPUaT3lHxKSndVZlciE0JRIk0OHNRRTO2mZggdKont
|

继续向上找goaKqsgaiOL……第一次出现的位置。可以看到执行完0x38指令后,出现了这个值。再往上还有一个135字节,或者说是135位的数组,看个人习惯。

再往上是另外一个135字节

继续,看到124字节和11字节,拼起来刚好就是那个135字节

继续,108字节 + 16字节 = 124字节

97 + 11 = 108

44 + 53 = 97

继续往上跟踪可以找到拼接生成这个135字节的若干个字节,涉及到大小端、随机数、时间戳、a1等内容,这里就不一一列举了。
RC4
那么接下来我们尝试分析最后一个135位数组是如何变成字符串的。
回到这个位置,看到上面有个_0x2a1008方法,那么这个方法极有可能就是生成字符串的位置,而它的入参大概率就是135位数组

跟进这个方法,看到又是一个jsvmp

依旧是跟踪return,可以看到_0x5acfa6方法内有两个分支,打上断点可以看到执行的值第二个分支

那么看第二个分支的return,跳转到_0x10439b又是比较熟悉的内容了

依旧是打上断点,看日志

根据之前的经验,这个anonymous大概率就是将135字节转换成字符串的方法,入参就是135字节和字符串'MfgqrsbcyzPQRStuvC7mn501HIJBo2DE'。
这时,聪明的你已经看出来了,其实这是一个自定义码表的base64编码,如果你不确定的话可以将这段代码丢给ai分析,他会告诉你答案。

既然135自己转str的方式找到了,那就该寻找从第一个135字节到第二个135字节的算法了,继续看日志。anonymous、135字节、256字节。

进到anonymous中看看。

让ai解读一下这段代码。

使用python实现,可以直接使用[108, 71, 200, 252, 102, 41……]作为RC4的S盒,可以用将密钥流字节拿出来,在后续直接根据index进行异或处理。再加上之前的Base64,成功完成了初始135字节到RC4的135字节,再到x3的str的步骤。

自定义MD5
4.2.9版的x-s相较于之前又增加了11个字节,所以我们这次主要分析这11个字节的算法。

11字节由3字节加上8字节组成。

8字节又来自前面16字节的前8位。

这16字节来自于另一个16字节与171异或的结果,171则是之前随机数的第一个字节。

16字节加上时间戳变成24字节,然后执行_0x4063db方法,接着就出现了我们要的16字节,那么我们有理由怀疑_0x4063db方法就是生成这16字节的方法,而且这24字节会参与到运算中。

进入_0x4063db,依旧是熟悉的jsvmp,继续根据之前的方案断点看日志。

由于时间戳参与了计算,所以此时数据发生了变化,不过并不影响我们的分析。现在我们要分析的16字节是[108, 162, 79, 57, 42, 170, 142, 32, 59, 62, 117, 94, 228, 79, 247, 26]。

可以看到这16字节是由4个4字节拼接而成的。

继续简单过一遍日志,然后我们能看到下面这3个63位数组,从而可以确定这是个md5。
第一个64位数组。

第二个64位数组。

第三个64位数组。

然后就是根据日志对照标准算法分析差异了,当然你也可以把日志丢去让ai帮你处理、分析。值得一提的是在第64步处理完之后还做了一些处理,并且最后拼接的时候修改了A、B、C、D的顺序。
然后就是用python复现了。

目前4.2.6版本还是可以使用的,没用强制升级4.2.9,说不定明天又出新版本4.2.10、4.3.0了,所以让子弹再飞一会。