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一个数据给你让你以为自己拿到了物理数据实际是我模拟的
>
> AToo 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的假装自己是很多种浏览器的所以你可以用各种办法随机坑他。而他只会认为自己的浏览器模拟有问题而不是去调试哪行代码有问题——毕竟他是自己放弃这个能力的。
这能怪谁呢……
好了,今天的答疑课堂就到这里了。希望我的分享可以帮助你了解反爬虫这件事上思路的重要性。而当你没有思路的时候,一定要回到最初的想法:我完成反爬虫这个动作的最终目的是什么呢?如果我反向操作会引发什么问题吗?**回归初心,往往能够帮助你直达目标。**