- 众所周知,
Javascript
作为一门脚本语言,其加密是不可能的,而解密却十分简单,因为 js 要执行就需要解密,一般使用DevTools
就可以直接看到解密后的代码 - 然而,百度一个叫做
BSK
(basilisk
)的脚本却让我改变了这个看法
一天,我在百度贴吧偶然发现了发帖参数中有个叫_BSK
的东西
这又臭又长的内容让人心生怀疑,怕不是百度又在追踪用户?
抱着这样的想法,我用DevTools
找生成的脚本,然而令人意外的是,并没有找到_(:з」∠)_ 难道DevTools
也会失手?
退而求其次,找到了引入BSK
的地方:https://tb1.bdstatic.com/tb/_/poster/bsk_service_c6680a4.js
注:百度服务器用了文件合并,而我在本文中写的是单独的js地址,且本文提到的js均已上传到github,后文中不再赘述
可以看到,这个bsk_service_c6680a4.js
引入了另一个脚本: https://fex.bdstatic.com/bsk/??dknsaZmLdyKfEeIVbKxn_dcc70f7.js,omzVouOACqkNljzDbdOB_af501e9.js
而后者又可以拆成dknsaZmLdyKfEeIVbKxn_dcc70f7.js
和omzVouOACqkNljzDbdOB_af501e9.js
两部分,为了简洁,我们姑且称它们为1.js
和2.js
如果你打开2.js
,你会发现这个js只干了一件事,传出一个巨大的n维数组,很明显是提供数据的,所以我们把它改个名字,data.js
而打开1.js
,情况就复杂得多了,它调用Function
,构造了一个巨大的IIFE
,而IIFE
中虽然骚操作很多,却没有eval
或是Function
这种代码生成,需要进一步分析
(Time Flip)
好的,现在我们跳过了无趣的代码分析过程,直接给出结果(否则就太冗长了……)
经过分析,和上面说的一样,1.js
根本没有去构造什么代码,而是写!了!一!个!JS!虚!拟!机!
js-in-js
虚拟机什么概念?这可是失传已久的操作啊,上一个做这种操作的 Continuum 连Github都404了啊(虽然Continuum实现难度比这个不知高到哪里去了,也不代表只学过几年前端的人能写出这个脚本来,港真,目测多数人连读都读不懂……)
既然我们知道了这是个虚拟机,那就把它改名叫vm.js
吧,哦,等等,还要反混淆,vm_deobf.js
为了方便各位阅读代码,我还搞了一份简化版:
vm_deobf_simplified.js
,把原vm_deobf.js
中压栈弹栈压栈弹栈的弯弯绕改为简单的return以突出逻辑(顺便还优化了性能,修了BUG……)
好吧,我知道你们懒得读代码(读也不一定能懂……),我来说一说这个虚拟机大体的实现方式
- 预先将要执行的JS转为AST并以某种方式编码在data.js里
- 对AST的每个节点,根据
type
字段选择对应的函数,由函数来负责解析执行 - 自己搞了一个函数及其作用域的实现,顶级作用域是
window
- 虚拟机内代码可以访问
window
的属性,调用虚拟机内外的函数(伪函数) - 输入一个
object
,虚拟机内的代码将最终执行结果写到这个object
上
然而,这还不是它的完全体……
事实上,data.js
传入的巨大n维数组在转成AST的过程中,需要经过一次xor解密
数学知识复习:
- a ^ b == b ^ a
- (a ^ b) ^ c == a ^ (b ^ c)
- a ^ 0 == a
- a ^ a == 0
而xor加/解密,便是在加密时把数据与key
异或,解密时再做一次,便还原了数据
这也不是很强嘛……老师,还能再给力点吗?
当然!事实上,这个虚拟机内运行的代码可以改变自己加密的key
只要调用虚拟机提供的函数_A(key)
,整个函数的key
都会被临时改变,难以进行静态分析
如果更进一步,将_A(key)
与if else
结合起来,我们还可以让一份代码表现出二义性,如果这么做,实际执行的代码就几乎无法分析了
好在,百度程序员并没有做到这个地步,也让我们得以一窥实际执行代码的真容
_A(key)
并没有与if else
联合使用,让我们有了解码的能力
修改vm.js
,让它输出AST,再借助escodegen
生成代码,便得到了raw.js
让我们把raw.js
人工反混淆并简化逻辑,得到deobf.js
,这回终于是TM人能读的代码了……让我们赶快来读一读
**注:**实际运行的代码是有bug的,我在
deobf.js
中一一标出了 <-- 然而百度的人说并没有bug,怕不是我写的decoder有问题?
到了虚拟机里脚本跑了这几百毫秒也没有什么别的,大概三件事:
- 一个,随机了自己的执行顺序;
- 第二个,把机器发帖的特征写入了返回值;
- 第三个,就是我们知道的“收集浏览器特征”。
值得注意的是,这脚本中有一个用canvas
生成用户指纹的函数,实际上并没有被执行,不知是程序员忘了写还是有意为之 <-- 百度的人说是为了性能考虑……然而我认为VM损失的性能比这个多得多……
在这篇文章中,我有意省略了分析代码的过程,一来不让文章读起来太过枯燥,二来没有js知识的人也能看懂
有兴趣的人可以读一读本repo中的脚本(可能有错误,欢迎指正),至少我是涨了些AST相关的经验值
如果有什么问题,欢迎pull request
最后,百度你有这工夫怎么不去修BUG?反馈吧一年四季就只有清缓存刷新一句话,卖大力丸也得换换台词吧(摔