先看该token哪里来的.
注意到下面的请求cookie才第一次使用到__zp_stoken__,推断出是在这个0f35c990.js里生成的.注意该文件以及文件名会在每天晚上0点-1点之间变动一次,固定为每天一次.
下面分析该js.
该js分为两部分,上面是一小段自执行函数,如上图.这里关键控制变量是G,并且是常量16.
下面部分依然大同小异,只是控制变量不再为常量,而是由传入的参数决定.
可能的传入参数,即下图示例中的10768 诸如此类.
这是典型的控制平坦流.面对这种情况,我的思路是,首先要理解,它实际控制变量就一个,这里的be.后面的Ee,Ae,Ne这几个实际是由be经过固定算法得出的.
现在进行第一步,使用ast构建出一个map印射表,使每一个be值对应的执行内容印射出来.
比如当be=10768时,Ee,Ae,Ne对应的值就分别是16 16 10.然后我们去对应的分支查看,可以看到
实际上执行的就是给be下一个赋值,然后循环,直到遇到return节点.
那第一步就是通过模拟这个流程,构建我们自己的map图.
流程就是be=10768,得到be=10410,然后继续...
去除掉无用的break部分,我们得到这样的有效数据.
下一步就是给定指定的入口参数,然后我们模拟这个流程,以return和be=undefined未界,标记一整个函数的执行过程.
我们把只关于be跳转的部分无效信息移除掉,以be=1616为入口为例,得到这样的结果.
[
'be = 1616;r = "cha";p = arguments[1];_ = "len";e = "gth";a = [];l = undefined;h = "At";o = "pus";q = 1;c = 0;u = "rCo";y = _ + e;v = "h";f = "de";s = r + u;i = o + v;g = s + f;m = g + h;t = p[y];S = c;n = S < t;j = S + q;be =
n ? 14700 : 73;',
'if(be===14700);be = 14700;d = a;x = p[m];z = x["call"](p, S);w = d["call"](a, z);S = j;n = S < t;j = S + q;be = n ? 14700 : 73;be = n ? 14700 : 73;',
'if(be===73);be = 73;return [a];'
]
继续进行转化并构造成这样的格式
然后进行常量合并,如观察到_ = "len";e = "gth";y=_ + e;那么自然y="length".当然这一步不是必须的,不过可以方便我们观察代码逻辑.
最终形成这种样式.并以此类推还原出全部算法.
等等等等.
剩下的就是找到token生成的入口位置,然后使用我们自己构建的代码,依次调用即可.
可以看到第一个参数5553,即be=5553是关键入口,第二个第三个是前面请求或者token已过期时系统返回的种子参数.
值得一提的是,部分参数的构成方式,靠肉眼就能看出规律,没事背一下ascii码表是真的有用家人们,比如这里我当时就肉眼看出端倪,a=97总知道的吧,那b自然是98,包括后面俩一样的.这段长度50的代码计算逻辑是这样的.首先是字符串的长度,然后跟字符串对应的ascii码.
48, 98, 57, 50 是"0b92"对应的ascii码,4代表该字符串的长度.同理44代表'booubZKQSgODivfbCd8dHDdK8T7BITcNqfge/Jfh7Oo='这个种子的长度.
以此类推,扣出最终的核心代码即可.
实际的核心代表仅这一段,当然仍然需要一些耐心去仔细补全,不过剩下的确实也没什么难度了吧.
最后总结:
1构建map,还原全部执行流程.
2提炼出有效信息并自行构建易辨认的格式.这里的难点在于对于if语句的重构,逻辑比较绕.
部分陷阱:比如这里的返回值f是undefined,是怎么回事呢?实际上因为v是数组,它是在函数内部对v进行了操作,最后没有返回任何东西,但实际上v已经变化了.对于这种,自己需要手动让它返回v的值.
测试
问:这个初始值是什么?
答:这个是首次自执行该js时拿到的一个时间戳,会保存到window['s']中,后续计算会用到,作用我猜测是计算本js从开始执行到最后生成参数,相隔了多长时间.作用大概是判断你是正常用户(执行时间应该在1秒内)还是搞断点的.如果搞断点逆向的话,可能最后发生出去的时候这个时间已经过了几十分钟.不过实际上似乎并没有什么后果.
可以观察到某部分数组明显是跟时间有关的.
问:怎么生成的结果会变,甚至长度都不是固定的?
答:还是由于上面的这个原因. |