gitbook/反爬虫兵法演绎20讲/docs/488879.md
2022-09-03 22:05:03 +08:00

158 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 答疑课堂|爬虫反爬虫调试对抗技巧以及虚拟机详解
你好,我是DS Hunter。
因为一些保密需要,我们的课程充满了理论,可能你会认为难以落地。因此啊,我们总要有一讲,擦着泄密的边缘,稍稍讲一点实际的例子,也在课程的基础与理论实践篇快要结束的时候做个总结。
反爬虫领域里的例子就像魔术揭秘一样,一旦了解背后的秘密了,我打赌你会先惊讶一下:哦原来是这样?然后仔细想想,就会再说:哦……不过如此!
不过在后续自己做的时候,还是要咬着牙慢慢自己往下Coding才行,你会有一种“感觉看了一堆明明能直接落地的东西,但无能为力”的感觉,在自己落地试试的时候,似乎也摔得不轻。
是的,这就是反爬。相同的招式对圣斗士是无效的,我们必须自己学会创新才行。我们能做的,就是看历史、学思路。
今天,我会为你总结技术对抗与非技术对抗这两大思路,并一一展开说明。最后,也会给你讲一讲在反爬虫中加密强度最高、最复杂的大招——虚拟机。也就是,对各类反爬虫的手段以及实现方法做一个快速的总结。
## 技术对抗
首先我们要明确,所有技术对抗的本质目标不是加密,而是浏览器bug检测。其余的所有手段都是为了隐藏自己的这个目标。
我们提到过,假币唯一的特征就是与真币不一样。那么爬虫唯一的特征就是与浏览器不一样。哪里不一样?浏览器有bug,爬虫不一定有。你要模拟得一样,需要模拟所有的bug才行。我们都知道,抄别人代码很容易,但是抄别人bug?那可不是一般的困难。
举个例子,我们看这样一个[小bug](http://bugs.jquery.com/ticket/8805):
> 在部分版本的IE8下面,如果一个a标签的innerText与href相同,那么当你修改href的时候,它会自动帮你修改innerText。
>
> 这个很傻的bug意味着什么?
>
> 这意味着你作为一个爬虫,如果对网站声称自己是IE8,那么,他会让创建一个a标签,然后运行相关的js操作DOM。然后,如果你能把这个bug模拟对了,他就相信你是浏览器。如果你不知道有这个bug,你妥妥的按照正常的方式去模拟。那么,你就是爬虫!
但是,请注意,如果你代码赤裸裸写着:if,你是IE8,那么,你要创建一个a标签……等等。你觉得爬虫傻吗,他们不会觉得这个有问题吗?他们看你这么弄肯定觉得奇怪,会真的装个IE8试试啊。然后调试完了,他发现,什么,原来是这样,太阴险了。下次他还会上当吗?
想让一个人上两次当,唯一的办法就是:让他不知道自己上当了。这样,你卖完拐,卖完车,改个担架还能卖给他。
那么,强加密必然是基操了。诶,是不是你这时候忽然意识到了js加密的重要性?而且是不是这时候也明白了,加密不是目标,加密是手段?浏览器bug的检测才是目标。**或者说,各个浏览器的差异,才是反爬虫技术对抗的核心。**
好了,还是要帮你解决一下这个问题,我们回到这个例子本身。你可能会觉得:我知道这个bug了,可是要怎么知道哪些版本的浏览器会触发这个bug呢?注意,不要一个一个检测。有些人可能会有洁癖,一定要知道IE什么版本,UA里面拆除一堆字段,然后逐个判断。注意啊,不需要!
我们可以直接用浏览器bug和UA进行mapping!也就是说,有bug的所有UA如果我都能存起来,这个问题是不是就解决了?我不需要拆解UA啊。那么如何建立这个mapping呢?实际上,你只要预先用这个规则跑一段时间,但是不拦截,直接把埋点发送回来就可以了。一般做反爬的公司,都不是什么小厂,有足够的流量来做这个操作。线上跑个一周半周就可以了。
那么,你可能会问了:那如果对方发现我检测这个,不是提前就有准备了吗!
没错!那么你再想想怎么办才好?
哦,是不是给埋点设置一个强加密就行了?这个破解了也不影响拉接口,爬虫才没心思破解这玩意呢。这时候你是不是就意识到了埋点需要强加密的原因了?
到这里,你应该明白了,反爬的战场是瞬息万变的。很多经验,都是由实战训练出来的;很多选择,等你面对的时候才知道要怎么选。如果你觉得前面有些结论似乎没什么用,不妨反过来想想:如果我反向操作会引发什么问题吗?当你想到了问题所在,自然就明白为什么要这么操作了。
下面我们看几个例子。
### 迭代对抗:hook与反hook
hook这个技术本来是在native世界常用的方法。但是浏览器端,因为js修改的便利性,hook的玩法更加丰富多彩。
我们看这样一段对话:
> A:我的接口需要检测xx信息,并且经过了超强的加密,你信息不对我就不给你调接口!
>
> B:我根本不用看你的加密逻辑。我用xx工具进行操作,hook掉你的检测接口,如果你检测,我就mock一个数据给你,让你以为自己拿到了物理数据,实际是我模拟的!
>
> A:Too young。我用API检测你的软件列表,如果你安装了xx工具,我就不让你运行!
>
> B:你才too young,我hook你的检测API,你问我装没装这个工具,我就说我没装!
>
> A:你才too young,我用native代码实现检测,然后用框架调用,让你无法在框架级别进行hook!
>
> B:你才too young,我hook你调用native的方法,然后塞给你一个我写的函数,说这就是你的native方法!
>
> ……
其实,这个对话还可以不断的扩展下去,这里先赶紧打住。我们可以看到,欺诈与反欺诈检测是一个无休止的问题,只要一方解决了对方出的难题,就可以立刻进行反制。
而大家经常说的:**反爬虫系统最终总是能破解,只是时间问题。**这句话是什么意思呢?其实这句话你可以这么理解:反爬虫系统,对于爬虫来说,相当于一个单机游戏。单机游戏,总有通关的一天。也许是明天,也许是后天,也许是下个月、半年?但是总是过得去的。甚至可以开外挂来过啊。
那么什么游戏不能通关呢?没错,网络游戏不能通关,它可以无休止地升级下去。你装备刚毕业,他改版了。就这样,一直下去,等于没有破解。就像一些模拟战争游戏抢要塞一样,刚抢完没多久,过期了,又要重抢。这就等于没怎么抢到手。
那么对应js里面,这个是什么呢?其实就是对函数的拦截、模拟、切片等等。比如上一课我们提到的eval的攻防,就是一个简单的hook与反hook。
此外,其余的函数也可以用这种互相hook检测的办法。这个和石头剪子布一样,永远是最后出的赢。我们要做的,就是不断地更新,迭代改版。
### Debugger对抗:无限Debugger
反爬虫方反爬虫方针对很多函数,都会设置大量的debugger,甚至在EventLoop里不断设置。
事实上,这样的办法是拦截初级调试者的。爬虫方呢,只要在代理上进行一次拦截,然后批量替换掉所有的debugger即可。这样,当js到达浏览器的时候,debugger就完全消失了。
但是,反爬虫方也可以针对这种批量替换继续出招。例如,检测函数的toString是否包含debugger。如果不包含,就可以认定函数被篡改。事实上,判定函数的toString,是反爬虫方一个很常用的技巧。在一定程度上,这甚至可以当函数签名使用。而一旦有了这样的代码,反爬虫方就不敢随意替换了。
_注意:debugger甚至不一定放在代码里,还可以放在注释里。要知道,Function的toString,是包含注释在内的。这样,不但可以检测debugger在不在,还可以检测debugger的位置,并且不影响自己调试。另外有了这个思路之后,注释内可以隐藏大量的签名。_
### 视觉对抗:阿拉伯文与emoji
很多浏览器在指定区域语言下,多语言支持做得是比较差的。如果线上测试发现指定的浏览器版本支持多语言,并且兼容很好,那么阿拉伯语将成为一个相当恐怖的杀器。
类似这类语言的字符有个很恐怖的地方,就是它们有的时候是从右向左写的。而且对于不懂阿拉伯语的人来说,根本不理解到底为啥代码左右乱跳。此外,一些畸形的字符都可以使用,都可以在视觉上严重影响阅读。例如QQ群里常见烟斗字符等等。
此外,emoji也是一个十分影响观感的一个选项。举个例子,10个狗构成一个变量名,11个狗构成另一个变量名,我相信正常人类是很难在一群狗里面调试下去代码逻辑的。当然,这里同样只是建议轻微使用,不建议大规模铺开。
理由有两个。一方面是兼容性问题。一旦出现,非常难调试。理论上它们可以做变量名,但是实际上哪个浏览器兼容,要以线上测试为准。另一方面,对方只要再走一次minify即可,这个我们前面提到过,破解成本比较低,所以我们大规模铺开的意义不大。因此,emoji这种方法不宜做大招使用。不过,小范围恶心一下对手,也很可能成为压死骆驼的最后一根稻草。
### JSFuck对抗
大名鼎鼎的[JSFuck](http://www.jsfuck.com/),这个发明一度震惊了全世界。js的混乱程度被用到了极致。但是要注意,这种加密方式已经有比较成熟的工具来逆向解析了。此外,代码长度急剧膨胀,简直不能忍受。因此,如果使用,只能放在核心代码上绕一下对手。而且,建议进行自定义,而不是使用标准做法,避免被工具逆向。
### 工具对抗:调试状态检测
判定浏览器调试状态也是一个很关键的做法。在一些浏览器里,你可以有意引发一个异常然后catch住,判定error的信息。你会发现,在Console里运行的函数与直接运行的函数,引发的error有很大差别,这个差别很明显,就不详细展开了。此外,启动Devtools的时候会引发一次size的change,这个也可以持续监听。最后,Console里运行的时候,会有一些Console特有的函数。
这些都可以做判定。具体可以查阅Chrome的帮助文档。
## 非技术对抗
除了正面硬刚的技术对抗,还会有一些角落中的非技术对抗。非技术对抗大部分是心理对抗。当然,在很大程度上,心理对抗都比技术对抗效果好。毕竟,反爬打的不是算法,是人。
### 薪资对抗
薪资对抗,或者福利对抗,都是比较常用的心理战术。例如,随着调试的不断深入,不断给对方报价。
举个例子,爬虫方破解你第一层加密的时候,提示:唉,这算啥啊,月薪三位数都能走到这。再破解一层,就提示:这也就拿个月薪四位数。甚至可以直接报价,乃至于直接留HR邮箱。这并非天方夜谭,很多爬虫工程师技术非常优秀,并且非常不想继续做爬虫了,能转前端多开心啊!专业也对口,HR业绩也完成了。
但是注意:不要在这里鄙视竞对,否则容易惹上法务风险。
### 关键字的作用
很多人都知道,某知名网站在反爬里经常会写一些稀奇古怪的话。这些话非常猖狂,让人破解完忍不住要写一篇博客吐槽他们。注意,你写的博客很快会被搜索引擎收录。而他们有个爬虫帮自己不断检测关键字是否被收录。嗯,没错,你被发现了,爬取时自以为的低调真的只是“自以为”了!他们反爬组当然会写爬虫啊!
因此后续记住,看到反爬代码里的冷嘲热讽,或者一些稀奇古怪的话,无视即可,千万不要当回事。只要多看一眼,你可能就输了。
## 重头:虚拟机
很多大型反爬一定会有一个虚拟机的。但是回到我们刚刚说到的反爬虫思路来看,虚拟机不是目的,只是手段。webassembly也是虚拟机。但是我们在第10讲提到过,webassembly不好用,大家还是会自己实现虚拟机的。而**虚拟机的核心作用,就是对代码进行加密,让破解变得更难。**因为只有难以破解的代码,才能更好地隐藏你的小心机。
### 虚拟机的实现
虚拟机的实现方式,基本上就是看你熟悉哪门语言,就实现对应的解释器就行了。一般不建议使用主流语言,而是尽可能用冷门语言。Lisp或者汇编都是不错的选择。甚至,你自创一门语言,都是可以的。只要你有办法写完解释器。
解释器本身还需要再进行加密。例如,eval,变量名混淆,都可以。甚至更疯狂地说,我们都知道大部分语言都是自举的。也就是说,你的解释器可以用另一个解释器来解释执行,套娃解释。最终,你的竞对一定会被绕晕,根本看不出这是两层解释器。
至于再增加层数,就没必要了。理由是,两层解释器的代码已经大大超出预期。再高可能会因为js过大,影响用户体验了。
**这里强烈安利Lisp解释器。**我不方便透露太多细节,我只能说,某站点的反爬是用Lisp解释器做的,已经跑了六七年了,直到现在,我搜各种破解博客,大家还都在纠结如何破解那个“汇编解释器”的问题。事实上,那是Lisp……他只是简单解释了一颗语法树……
### 虚拟机的部署
实现了虚拟机的功能,我们就要开始部署了。虚拟机的部署有站点直接部署与CDN部署两种。
首先看站点部署。最原始的部署方式一定是由站点直接生成js文件,那么文件头就会有一个虚拟机。因为虚拟机是共用的,所以有些大聪明可能会考虑放到CDN。也不是不能放CDN,但是放了CDN,你的虚拟机就定死了,不能变更了。实际上,这样反而降低了别人的破解难度。
但是不放CDN,又太慢了,因为很难使用缓存机制。怎么办呢?
实际上,如果你是两层虚拟机,那么可以把一层放在CDN,利用缓存。然后再套娃解释一个简单虚拟机。例如,CDN放汇编解释器,然后用汇编解释器解释一个Lisp解释器,Lisp解释器用站点直接下发。这样,就可以实现CDN大量加速,同时代码破解难度也不低。
在这种情况下,我们的代码复杂度会逐渐提升。那么,如果爬虫方不读代码,直接用浏览器运行呢?
谢天谢地那太好了,我们等得就是这一天。一旦爬虫不读代码了,那就意味着,无论发生什么,都是黑盒了,他失去了调试的能力。你可以随机封他IP,封他指纹,增加浏览器bug检测。放心检测就好,虽然他使用了浏览器,但他还是会不断换UA的,假装自己是很多种浏览器的,所以你可以用各种办法随机坑他。而他只会认为自己的浏览器模拟有问题,而不是去调试哪行代码有问题——毕竟他是自己放弃这个能力的。
这能怪谁呢……
好了,今天的答疑课堂就到这里了。希望我的分享可以帮助你了解反爬虫这件事上思路的重要性。而当你没有思路的时候,一定要回到最初的想法:我完成反爬虫这个动作的最终目的是什么呢?如果我反向操作会引发什么问题吗?**回归初心,往往能够帮助你直达目标。**