和rs对抗两年多了,作为国内js逆向的天花板之一,是许多js逆向的衣食父母,我这口饭也是rs给的,真心感谢rs公司,给我们带来了这么优秀的反爬产品,让自己的反爬和反反爬水平都得到了提升。

两年前,刚接触js逆向时,接手同事的项目就是rs4天花板,那时候能把人累死。那时还不会AST, 同事也不会,在遇到控制流的时候,为了理解代码逻辑,都是一行一行的,然后记录关键步骤。每个参数的生成,如cookie, url后缀,指纹等等都得记录好多行。不到一个月后,同事离职,最终我扛下了所有,那时真是初生牛犊不怕虎。

一切都还好,直到4代升级到5代, 没有想到用4代那种正则抽取参数的方式,程序挂了,因为之前4代是用正则抽取的,更新到5代之后没法直接用了。单步调试也没有之前方便,不好打条件断点,再用最原始的单步调试就显得很吃力。于是开始想办法,正好同事从蔡老板那了解到AST,并做了一次分享,于是开始学AST,在蔡老板的星球里看到同事做过的一道题目,瞬间悟了,这不就是二叉树操作吗。用了两天的时间把控制流还原好,开始愉快的调试,并可以用AST的办法把参数抽取出来,虽然性能上差了些,但终归能用。

虽然能调试了,但还是卡在了鼠标和键盘这些行为轨迹的生成上,这将近1000行的代码要扣下来无从下手。扣出来的代码也过不了,总是返回400,那段时间是最头疼的时候,每次早上站会的时候,都没什么进展,就很尴尬。后来一个同事来一起帮忙排查问题,同事测试了很多方案,后来测试sekiro的时候,终于200了。这时我突然就明白了,服务器能拿到的就一个字符串,它其实不知道你是咋生成的。在rs中,轨迹这些最终会转成一个数组,不一定要用js代码生成,直接拿浏览器中的轨迹数组随机变化下生成一些数组也可以。经过修改后,代码又开始跑起来了。

程序跑到21年2月,通过率越来越低,抓取速度也越来越慢,最终行为轨迹又升级了,彻底跑不动。这时开始混圈子,认识了哲哥和涛哥,在他们的鼓励下,硬着头皮把1000行的代码扣下来,程序又开始跑起。又经过2到3个月的对抗,经历了20多次小更新,网站的反爬越来越完善,校验越来越完备,自己对于反爬和反反爬也有了理解,水平也得到了哲哥和涛哥的认可。

今年知道了补环境,于是尝试使用补环境来完成, 在完成了zptoken的补环境框架后,普通rs4的补环境变得相当简单, 除了dom树操作遇到问题。想想自己学逆向是先抠算法,再学AST,再学补环境,这条路是走错了。更简单的路径应该是先学补环境,再学AST,之后遇到性能问题的时候才去抠算法。

联系作者

当补环境遇到document.all

在解决jsdom被针对的逆向过程时遇到了这个问题, document.all的类型是undefined, 但却能取到值,js这是真的离谱,当时就直接懵了。脑海中回忆这些年学到的JavaScript知识,怎么也想不到解决的办法,于是问各路大佬,其中当然有猿人学的安澜。今年报了猿人学的课,看了安佬讲的js课后,对js有了新的理解,不再纠结于扣算法。安佬说过js逆向除了扣算法,还有很多方向可以尝试,思路得打开。对自动化也不再那么排斥,当看到安佬的jsdom用法后,大彻大悟,用了一个多星期的时间就搞定了通杀方案,从此再也不用一个个网站的干苦力活。虽然之前哲哥和涛哥和我说过jsdom,自己也用了jsdom,但用的不好。从此之后,遇到混淆难的网站,都是先掏出jsdom试试,真的很香。后来遇到难题了,都会先问问安佬。

当拿着document.all去问安佬时,安佬也是第一次遇到这个问题,思考后给出的回复是,在纯js层面解决不了这个问题,得借助其它办法。V佬这次挖的坑是真的深,这明显就是冲着Node环境来了,只要是Node补环境,都会遇到这个问题,一抓一个准。好在山外有山,人外有人,办法总比困难多。安佬问遍各路大佬,document.all这个检测也在圈子里传开了,风佬专门开了次分享,最终风佬给出的办法是得修改Node才能完美的解决,当然风佬也提过一嘴,V8的不可检测对象也可以解决这个问题,如此看来风佬的知识储备是真的强。最近他和蔡老板开了补环境的课,值得学习,在我看来,光蔡老板的AST还原其实已经值这门课的学费,更不用说加上风佬的补环境。只是自己真的是没时间学这些,这些年报了3门课,夜幕的高级课,肉丝的VIP,猿人学,其中夜幕的高级课就打开过一次,肉丝的课就看了下怎么root, 猿人学就看了js部分,真是没时间学,忙于业务,疲于奔命。而另一位大佬肝总也给出了自己的解决办法,肝总是真的强,还乐于分享,真是吾辈楷模。

目前看来,解决这个问题的办法有如下几种

  1. 直接不补了,有可能网站校验不严格,直接可以过
  2. 扣算法,这样绕过各种检测,直接把值写死,就是扣算法难搞。
  3. 用风佬修改后的Node
  4. V8里的不可检测对象
  5. 请大佬出山帮你解决

联系作者

最近rs上线了针对jsdom的检测,是时候对是否继续使用jsdom的做出决定了。因为有jsdom的存在,直接进入了人均rs的年代,rs公司针对jsdom进行检测也是非常正常的。之前认为在jsdom上做二次开发是个不错的选择,现在看来还是抛弃幻想,趁早放弃吧。

随便翻开jsdom,就发现各种私有变量,多看一下jsdom源码就能发现更多检测点,真是随便检测下都能区分出来。更不用说很多人jsdom用的不对,连userAgent都没换,使用jsdom,就变成了直接往枪口上撞,一抓一个准。

如果非得用jsdom,就只能悄咪咪的用,别声张了。但你得时刻提防着被检测,因为jsdom可以检测的点真的是太多了。

傻瓜化使用jsdom的年代过去了,真要补环境,还是搞一套自己的补环境框架更趁手些。不得不说,js逆向真的越来越难,门槛也越来越高,这何时是个头啊,还得是selenium一把梭香。但说实在话,用selenium,不懂得逆向也是解决不了问题,还得是能看得懂代码才行,因为你总得知道怎么被反爬的。

联系作者

补环境即补充浏览器环境。那为什么要补充浏览器环境呢,因为大多数时候,我们执行js代码都是放在Node中执行。因为Node服务比浏览器更容易部署,所以更愿意用Node。

而Node环境和浏览器存在差异,所以我们需要补充浏览器环境。比如Node没有浏览器相关的对象如window, document等等。另外Node比浏览器多了一些require, module对象,这些也要去除。有同事说,Node没有浏览器中的dom树解析,没有渲染引擎等等,这也是对的。但是从js语言看,一切皆对象,Node是没有window, document等对象。为了让js代码能执行下去,我们只需要补充这些对象即可。例如window对象,有些校验不严格的情况下window = {}也是可以的。

对于Web反爬来说,大多数时候,都是想办法检测是不是Node环境,而这些检测手段,在js语言层面,大都是检测对象是否存在,检测原型链是否和浏览器一致,函数toString是否和浏览器一致。

Node比浏览器多了一些require, module对象,可以使用Node的vm2模块进行规避,vm2模块会构建沙盒环境,在沙盒里面少了很多Node特征。

Node比浏览器少的对象,可以使用proxy技术,缺啥补啥。目前市面上免费的课程,当属志远的补环境框架,讲的是真细。到这时候你会发现,js的语言基础很重要,原型链尤其重要。

有些时候,即便环境补好了,生成的token和浏览器还是不一样,这时需要看代码,对着浏览器进行比较,此时把混淆代码还原后更容易看懂。这就需要用到AST技术了,此时蔡老板的星球就派上用场了。

目前来看, 补环境技术遇到的一个难点是dom树操作,想在js里实现一个dom树操作真心不容易。而jsdom也算是补环境的一种,作为开源十多年的项目,jsdom实现了dom树操作,在jsdom上进行二次开发是个不错的选择。

联系作者

用zp_token完善补环境框架

从志远的补环境教程了解到还有补环境框架这个东西后,立刻打开了新思路,从简单的网站开始,逐渐完善自己的补环境框架。在完成了加速乐和hexin-v后,将新目标指向了zp_token.

去年的时候从卷木木的文章里知道了补环境这个东西,那时玩的就是zp_token, 但那时候的补环境还没有形成一个完整的体系,就是缺啥补啥的一种状态,代码写的不够通用;现在学习了补环境框架后,虽然也是缺啥补啥,但代码更通用,针对新的网站只要稍加修改就能跑起来。

相比于加速乐和hexin-v, zp_token的生成需要补充的环境增加不少,从混淆后的代码量也能看出难度。zp_token的混淆代码将近2万5千行,hexin-v就1200左右,加速乐不到1000行。但依然还是对着浏览器,缺啥补啥,只不过zp_token增加了更多的判断。处理完所有缺失的函数和属性后,发现生成的zp_token还是过不了网站的检测,这时就得分析代码。

将近2万5千的混淆代码,用AST去除控制流平坦化后,还有将近2万行,剩下还有好多字符串和花指令可以还原,调试也是能调试,但比较麻烦。手上也没有称手的AST插件来还原这种混淆,过去两年一直忙于业务开发,疲于奔命,在AST还原上,一直只是停留在控制流平坦化的还原,要搞定这种字符串和花指令还是需要花些时间。此时只好请大佬出山,大佬直接将代码还原到了6000行,调试起来方便无比。

有了新代码,对着浏览器进行调试,依然是缺啥补啥,很快就搞好了,生成的zp_token一测试,通过。经过了zp_token的测试,补环境框架完善了好多,下一个目标可以搞搞其它反爬产品。现在发现,做Web反爬是真的不容易,代码客户端都能看到,真的难做。

AST还是值得学习的,这里推荐蔡老板的星球AST入门与实践,另外渔歌的星球Python爬虫应用学习也非常值得学习,渔歌是真大佬,又强又卷,真是没得讲。

联系作者

最近试了下公司网站的反爬,发现用补环境技术半天就可以搞定,如果用jsdom这种伪浏览器,一分钟就可以搞定。于是写下自己这些年来对爬虫的理解,希望对公司的反爬有帮助。

对于一个接口的反爬,可以分两个阶段,一个是请求时做反爬,一个是请求后对请求结果做反爬。

对请求结果做反爬可以有请求结果加密,字体反爬,CSS反爬等等,其中字体反爬和CSS反爬对搜索引擎不友好。

对请求时做反爬,主要是添加请求token, 对请求的token做校验。请求token的生成过程要手机客户端信息,对客户端环境做校验。

对客户端环境做校验主要分为两种,一种是浏览器环境,一种是Node环境。

对于浏览器环境的检测,有Canvas指纹,Webgl指纹,Audio指纹,字体指纹,还有WebRTC指纹等等。WebRTC可以获取到客户端的实际内外网IP,即便浏览器加了代理。对Canvas指纹,Webgl指纹,Audio指纹,字体指纹做校验是为了防止爬虫用程序批量抓取,因为这种批量抓取,指纹大都是一样的。当然对于有经验的爬虫,这些都防不住,指纹可以修改,WebRTC可以禁用。

对于Node环境,主要是检测与浏览器环境的差异,如require, module等关键字是不是存在;还可以检测对象的原型链,如document的原型链是不是和浏览器一致等等;还可以检测函数实现,检测代码是否被格式化,检测异常堆栈,这些都有待反爬工程师去挖掘。

反爬代码写好之后,我们还需对它进行混淆,增加破解难度。目前推荐vmp混淆,但由于vmp性能有问题,只能用在关键算法的加密上,不能用于全部代码。当然自己实现一个vmp有一点难,v_jstools的作者有vmp混淆工具,可以去找找,开源的有Obfuscator混淆,但蔡老板可以一键还原,所以做混淆有点难,目前就没有蔡老板搞不定的混淆(除了vmp)。反爬代码的破解难度不取决于混淆,而取决于反爬代码对环境的检测,检测的越多,越难破解。要知道补环境这种爬虫技术是可以无视混淆的,在补环境过不了反爬时,才需要看混淆代码。

除了环境检测外,还可以增加一些行为事件检测,如鼠标滑动事件,鼠标点击事件,这些都可以增加爬虫的破解难度。

对于token,最好只能请求一次就失效,而不是可以一直请求;对于反爬代码,能做到动态变化最好,这些都会增加爬虫的破解难度。

另外,风控也是反爬的一种,这里就不班门弄斧了。

总体来说,反爬防不住所有人,再难的反爬,也有人能破解,但只要增加了破解反爬的成本,让爬虫放弃抓取,反爬就成功了。

联系作者

20年7月份,做爬虫逆向不到3个月的时候,遇到了一段js反爬, 后来才知道它叫加速乐, 用的混淆是开源的Obsfuscator混淆。那时虽然接手了同事的瑞数项目,但对js逆向还没有深入的研究,还不懂AST, 不懂补环境,甚至还不懂本地调试。

从同事那里知道本地调试,把代码下载下来后,用正则进行了处理,就开始进行调试,打断点,知道了会对浏览器的一些环境进行检测,会使代码进入死循环。和同事折腾了一个周末的js,准确率到95%, 可以用到生产环境中。

两年过去,学习了AST还原代码,蔡老板也开源了他写的Obsfuscator解混淆工具,工具一跑,900多行的混淆代码只剩300行不到,调试起来轻轻松松。

最近学习了补环境这个新技术,于是用加速乐来测试下。因为补环境是缺啥补啥,而加速乐对环境检测其实挺少的,花了一个小时的补出来的环境就能够生成cookie, 测试了一下,能跑通,真是省心又省力,生产力大大提升。

补环境真不错,无视加密代码和混淆,就是缺啥补啥,现在想来使用jsdom也算是补环境的一种手段。

联系作者

js代码在Node环境中可以执行,但因为与浏览器环境不一致,生成的参数会与浏览器不同,这时就需要用到补环境,补充Node环境相对于浏览器缺少的环境,去除Node环境中相对于浏览器多余的环境。

例如Node环境中,函数toString的时候大都是function () { [native code] } ,而浏览器中会返回整个函数定义

Node环境中有module, Buffer等等,而浏览器中没有.

还有一些会检测原型链,例如浏览器中document.bod有值,但是document.hasOwnProperty(‘body’)却是false.

补环境的时候,proxy是一个非常有用的工具。固定本地代码和浏览器代码,固定随机参数,慢慢对比。

补环境教程推荐志远的补环境视频,那是真的强。

联系作者

在逆向一些复杂的前端代码(如每次请求返回的代码都不一样)时, 能在本地调试就显得非常重要。本地调试,也就是很多人说的脱机。

有很多工具可以复制网站请求,浏览器也可以用来做这件事。右击,然后存储为,就可以把大多数请求都保存下来,然后缺啥补啥。

如果需要保持域名和线上一致,还需要用到Nginx, 修改hosts, 把域名指向本地。

如果需要保持请求的url和线上一致,还需要用到Nginx里的rewrite, 例如把.do的转成.html。

这样一套完成后,就可以开心的在本地调试了。

联系作者

第一次遇到JJEncode, 代码与如下类似,一堆的奇奇怪怪的字符,特征是结尾有两个括号

1
$=~[];$={___:++$,$$$$:(![]+"")[$],__$:++$,$_$_:(![]+"")[$],_$_:++$,$_$$:({}+"")[$],$$_$:($[$]+"")[$],_$$:++$,$$$_:(!""+"")[$],$__:++$,$_$:++$,$$__:({}+"")[$],$$_:++$,$$$:++$,$___:++$,$__$:++$};$.$_=($.$_=$+"")[$.$_$]+($._$=$.$_[$.__$])+($.$$=($.$+"")[$.__$])+((!$)+"")[$._$$]+($.__=$.$_[$.$$_])+($.$=(!""+"")[$.__$])+($._=(!""+"")[$._$_])+$.$_[$.$_$]+$.__+$._$+$.$;$.$$=$.$+(!""+"")[$._$$]+$.__+$._+$.$+$.$$;$.$=($.___)[$.$_][$.$_];$.$($.$($.$$+"\""+$.$_$_+(![]+"")[$._$_]+$.$$$_+"\\"+$.__$+$.$$_+$._$_+$.__+"(\\\"\\"+$.__$+$.__$+$.___+$.$$$_+(![]+"")[$._$_]+(![]+"")[$._$_]+$._$+",\\"+$.$__+$.___+"\\"+$.__$+$.__$+$._$_+$.$_$_+"\\"+$.__$+$.$$_+$.$$_+$.$_$_+"\\"+$.__$+$._$_+$._$$+$.$$__+"\\"+$.__$+$.$$_+$._$_+"\\"+$.__$+$.$_$+$.__$+"\\"+$.__$+$.$$_+$.___+$.__+"\\\"\\"+$.$__+$.___+");\\"+$.__$+$._$_+"\\"+$.__$+$.__$+"\\"+$.__$+$.__$+"\\"+$.__$+$.__$+"\"")())();

结果我想的复杂了,想着用AST去解决,实际上非常简单,只需要把代码放在浏览器console里,把最后的括号去掉,就能还原代码,上述代码可以得到如下代码

1
2
3
4
5
(function anonymous(
) {
alert("Hello, JavaScript" );

})

与JJEncode类似的还有AAEncode, JSFuck,这里就不一一列举了

联系作者