自从知道了网站会采集浏览器设备指纹和WebRTC泄漏真实IP后,在浏览器上常年挂着指纹对抗插件,Canvas Fingerprint Defender,Font Fingerprint Defender,WebGL Fingerprint Defender,WebRTC Network Limiter。突然某天发现自己访问某地图网站时返回了假数据(假数据的形式是不返回围栏边界数据,地址可能出现错误),在排查问题后,锁定了Canvas Fingerprint Defender插件和WebGL Fingerprint Defender插件,只要打开这两个插件中的任意一个都会返回假数据。

于是排查Canvas Fingerprint Defender插件,查看这个插件的代码,发现主要是hook了toBlob,toDataURL,getImageData方法。于是尝试自己写了一个hook toDataURL方法,用了V佬的toString保护函数,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(function() {
//'use strict';
console.log('start hook')
var v_saf;
!function(){var n=Function.toString,t=[],i=[],o=[].indexOf.bind(t),e=[].push.bind(t),r=[].push.bind(i);function u(n,t){return-1==o(n)&&(e(n),r(`function ${t||n.name||""}() { [native code] }`)),n}Object.defineProperty(Function.prototype,"toString",{enumerable:!1,configurable:!0,writable:!0,value:function(){return"function"==typeof this&&i[o(this)]||n.call(this)}}),u(Function.prototype.toString,"toString"),v_saf=u}();

var v_new_toggle = false;
var v_console_logger = console.log
var v_console_log = function(){if (!v_new_toggle){ v_console_logger.apply(this, arguments) }}

const myToDataURL = HTMLCanvasElement.prototype.toDataURL;
Object.defineProperties(HTMLCanvasElement.prototype, {
toDataURL: {
value: v_saf(function toDataURL(){
v_console_log("[*] HTMLCanvasElement -> toDataURL[func]", [].slice.call(arguments));
debugger;
return myToDataURL.apply(this, arguments)
})
},
})
// Your code here...
})();

把代码放在油猴里跑,神奇的是,即便在toDataURL啥都没有做,toDataURL生成的结果和修改之前一模一样,依然能被检测到。使用Object.getOwnPropertyDescriptor(HTMLCanvasElement.prototype, ‘toDataURL’)和HTMLCanvasElement.prototype.toDataURL.toString()查看,和未使用hook之前几乎长的一模一样。这就超过了我所学的JavaScript知识了,涉及到知识盲区了。感兴趣的大佬可以试试,解决了的话可以告诉我一声。

一时半会找不到问题所在,于是尝试下载Chromium,照着网上随机Canvas画布的文章, 自己编译了个Chromium, 在browserleaks.com上测试了下,Canvas指纹可以随机了,访问网站也没有给假数据。

联系作者

之前用补环境框架测试过某yd验证码,虽然能过,但存在一点不足,那就是速度太慢了,生成一次参数将近1秒,时间都花在创建沙盒环境和执行无效代码了。这么慢的速度,放在生成环境的话,得需要花费很多CPU资源,于是得想想其它的方案。

最先想到的是扣代码,把参数加密相关的函数缺啥补啥的一个一个拿出来,在使用补环境之前试过这个方法,麻烦是麻烦一些,但总能解决。后来和时光佬提起这个事情,他在时光漫漫星球里写过相关的文章,可以去参考下。在时光佬的帮助下,很快就搞定cb, fp, 轨迹加密等参数,很快就跑起来了。测试了下速度,能做到100ms生成一次参数,是之前补环境的10倍,这速度满足需求了。

奇葩的是,每小时的0到20分钟过不了校验接口,而每小时的20到60分钟就能过去,但浏览器上是任何时候都能过,于是怀疑是b和d接口没请求。于是参考十一姐写的相关文章,把b和d接口加上。不得不说,十一姐写的文章是真的详细,主打一个手把手教程。神奇的是,把b和d接口加上后,还是过不去,这就很离谱了,百思不得其解。后来问了道上的朋友,他们也有类似的遭遇,一直解决不了,既然大家都一样,那就暂时先放一放了。

补环境虽然解决的快,但生成参数的速度真的是太慢了,以后得慎重使用。

联系作者

最近某里滑块更新到227,导致之前的226代码过不去了,于是得排查下原因。在这之前,想着先把混淆代码还原下,这样更方便调试。

之前虽然还原过221,但现在已经更新到227了,得花时间写很多插件,好在可以站在巨人的肩膀,蔡老板的226还原文章就是一个很好的参考,蔡老板的星球里有很多现成的插件,直接拿来用就完事了。

还原的过程中遇到两个问题,一个是自执行函数作用域的问题,另一个是假节点。秋裤佬写过还原出现作用域异常的解决方案,自执行函数作用域问题迎刃而解。假节点就是条件语句里会有很多数学计算,但其实值永远为true,仔细观察它们的特征后写插件解决即可。每次执行完一个插件后,都替换原文件然后在网站上测试代码是否正常,保证还原的准确性。最终的效果是核心代码合并后只有一个case。还有一些字符串常量没有还原,得花不少时间写,等后续有时间了再写插件还原。

代码还原后,我们就可以调试了。先把代码放到补环境框架中跑一跑,token能生成,但过不去,于是只能慢慢调试,这次调试才发现某里滑块混淆代码的厉害之处,它会根据传进来的参数执行不同的流程,调试起来依然很费劲,不愧是国内混淆js的天花板。对比核心数组和浏览器之间的差异,慢慢调试,最后发现增加FocusEvent, 然后第13位固定写死就能跑通了。这次更新和226相比核心代码几乎没变,只是数组位置变了下,然后轨迹校验变严格了,问题不大。

联系作者

APP抓包入门中,我们有说过有一些复杂的APP,抓包软件是抓不到HTTPS包,这其中的原因有很多,比较常见的如SSL Pinning,还有双向认证,还有一些情况是不走HTTP协议。这里我们来解决最常见的SSL Pinning问题。

SSL pinning简单来说就是证书绑定,SSL证书绑定,即客户端内置了服务端真正的公钥证书。在 HTTPS 请求时,服务端发给客户端的公钥证书必须与客户端内置的公钥证书一致,这样请求才会成功。而使用抓包软件后,抓包软件的公钥证书和客户端内置的公钥证书不一致,这样请求就会失败。

网上有很多解决办法,其中如DroidSSLUnpinning, r0capture, 这里我们使用DroidSSLUnpinning来解决这个问题。

查看DroidSSLUnpinning的使用方法,进入ObjectionUnpinningPlus目录,使用hooks.js脚本即可。需要安装frida使用。frida脚本使用方法有两种,一种attach, 一种spawn。com.example.mennomorsink.webviewtest2 是应用包名,可使用frida-ps -U命令进行查找报名。也可以使用jadx反编译工具,在资源文件AndroidManifest.xml中的第一行,找到package字段即可。有一些frida版本会提示–no-pause无法使用,去掉这个参数即可。

  • 使用方法1 attach : frida -U com.example.mennomorsink.webviewtest2 –no-pause -l hooks.js
  • 使用方法2 spawn : frida -U -f com.example.mennomorsink.webviewtest2 -l hooks.js –no-pause

至于r0capture的使用,可以查看r0capture仓库里的使用文档。这个库功能更强,但要配合WireShark使用。

联系作者

之前有段时间,Node服务的可用性从99.99%跌到了99.3%,502错误明显增加,于是得排查下原因。

在本地打包镜像,启动容器后,把并发打上去,发现PM2守护的进程会时不时重启, 日志如下

1
2
3
PM2      | App [server:1] exited with code [0] via signal [SIGKILL]
PM2 | App [server:1] starting in -cluster mode-
PM2 | App [server:1] online

仔细观察后发现是内存到达上限后会把进程杀死,重启。估计是内存泄漏的原因,一时半会找不到原因,于是只好祭出开始佬写的内存回收插件,加上插件之后内存急剧下降,服务稳如泰山。带来的副作用是内存GC存在时间开销,整体响应时间比原先的服务慢20%,这就得做个取舍。

把插件发布到测试环境后,再次测试,一样稳如狗,于是发布到线上环境,结果还是存在很多502错误,想不到错在哪里。联系网关服务的开发一起排查。网关服务的开发推测是keepalive时间设置的原因。因为请求先到网关,再到服务中,而网关设置的keepalive大于服务设置的keepalive时间时,就会出现网络连接不可用的情况,于是就会认为服务502。

在Express中keepalive时间默认是5秒,改一下keepalive时间即可,配置如下。

1
2
const server = app.listen(port);
server.keepAliveTimeout = 70000;

最终服务可用性达到99.99%,达到目标。

联系作者

Node服务高并发高可用之容器化与负载均衡中,我们已经解决了QPS如何满足用户需求的问题,这一篇我们再讲讲限流,以及如何使用限流来提高我们服务的可用性。在使用node-rate-limiter-flexible服务限流中,已经提过一次限流,这里我们继续说一说。

限流有很多用途,比如很多道友会提供接口给人用,为了限制对方调用的调用频率,此时就可以加上限流功能。

比如有个网站每秒只支持100个人访问,结果有同时有10000个人去访问,如果没有限流,可能网站就会奔溃了。

比如一个接口每秒只能支持10QPS, 但调用方因为突发流量,每秒调用100次,那这时候第10次~20次请求就要1~2秒才响应,第20~30次请求就要2~3秒才响应,第90~100次请求就要9~10秒才响应,导致接口超时严重,运维就会找过来了。

如果加上限流功能,这上面的问题就都解决了。当访问数量超过支持的数量时,就直接告诉对方,你被限流了,这样能保证自己的服务不会奔溃。

Node里有很多限流模块,我这里使用node-rate-limiter-flexible,它不需要引入额外模块如Redis等做限流,只是在每个Node进程里做限流,满足我的需求。一个简单的配置如下

1
2
3
4
5
6
7
8
9
10
11
const burstyLimiter = new BurstyRateLimiter(
new RateLimiterMemory({
points: 3,
duration: 1,
}),
new RateLimiterMemory({
keyPrefix: 'burst',
points: 5,
duration: 5,
})
);

它是用了两个限流配置,第一个的令牌用完了,就会去用第二个的。如下就是一个QPS是4,支持突发流量到8的配置,相当好用。第一个配置每一秒生成3个令牌,第二个配置每5秒生成5个令牌,那么5秒内就一共可以生成3 * 5 + 5 = 20个令牌,相当于这5秒内的QPS是4,而它的突发流量是8,也就是第一秒内如果进来8个请求,它也不会触发限流。

在服务高可用方面,除了限流,还有熔断,服务降级,我也只听过,没用过。我这里只使用限流功能就够了,其它就不管了。

联系作者

Node服务高并发之PM2使用中,我们介绍了如何使用PM2将单台机器上Node服务性能最大化,这一篇我们讲一讲当单台机器的QPS无法满足需求时,如何解决。

其中一个解决办法是提高单台机器的CPU核数,比如之前是2核(即2C)的,提高到4核,再不够提高到8核,再不够继续提高。虽然这也能解决问题,但这意味着这个服务是单点的,假设服务器宕机了,服务就挂了。

这里我更赞成使用多台服务器,比如原先单台需要32核服务器,我们可以分成申请8台4核服务器。这样当其中一台服务器宕机了,还能有7台服务器提供服务。这里我提一句,有道友申请4C/8G的服务器用来做Node服务,大可不必,一般Node token生成服务都是CPU密集型,内存占用不会太多,有这个钱还不如申请两台4C/4G的服务器。

现在我们就需要涉及到如何讲代码发布到这多台服务器中,其中一个办法先到每台服务器上安装需要的Node环境,然后再上传代码。另一个更好办法是容器化,例如使用Docker技术,将需要的Node环境和代码一起打包到镜像中,然后把镜像上传到服务器中。

既然涉及到多台服务器,就得加上负载均衡,让请求均匀分发到各台服务器上。简单点就用Nginx做负载均衡,还有一些更复杂的API网关如Envoy, APISIX等等。

具体如何使用我也没有深入了解,毕竟公司有现成的一套容器化和API网关,还有动态迁移(容器所在的物理机宕机时,在其它物理机重新启动一个新容器提供服务),动态库容(CPU负载过高时,扩容)等功能,直接用就完事, 美滋滋了。

联系作者

最近和yd验证码看对眼了,于是决定锤一锤它。在锤它之前先看了十一姐y小白的文章,再参考小白佬的例子,就开始干活了。具体要搞定哪一些参数,直接看十一姐的文章好了,十一姐的文章目前是我看到的最详细的,实在是太细了,我这里就只讲讲我遇到的一些坑点。

代码有一万多行,ob风格的混淆,但因为蔡老板的星球一直提供ob解混淆脚本的原因,我已经是个ob废物了,用脚本跑了下,没有还原出来,估计是版本不一致的原因,于是只好硬刚了。在掏出补环境框架之前,先尝试手动抠了一份生成cb参数的代码,虽然生成的参数能用,但还是有些麻烦,于是决定用补环境来解决。先拿官网的例子练练手,因为官网参数会少一些,会更简单。站在大佬们的肩膀上,很快就搞定了。

然后去测试具体网站,这时就开始遇到问题了,先是生成的fp参数不能用,慢慢排查后才发现location还用的是官网的地址,改过来后就好了。然后又遇到个现象,能获取两次验证码,第三次就不行了,隔一段时间又能获取两次验证码,第三次就不行了,百思不得其解,问了一圈道友都没有遇到过这种情况。于是只好一个个接口,一个个参数慢慢排查,排查了好几天,最后发现是验证码校验接口cb参数没有动态生成,就离谱。

明明记得这个参数改成动态生成了,但事实就是没改,估计是记忆错乱了,把改过官网的代码记成具体网站了。除了这个问题,全程再没有遇到其它坑点。

像这种代码是固定的网站,是真的好搞,补环境也是真的香,即便现在所有接口都跑通了,我都不知道它这混淆代码写的都是些啥。

最近十一姐和时光佬搞了个星球,值得一看。

联系作者

两年前,去了某里的同事发来一个滑块,是他们所在的团队维护的,让我试试能不能通过校验。但那时我对补环境还不是很熟悉,某里的控制流也没能还原,硬刚太累了,业务上也没有需求,就只好放在收藏夹中吃灰。

去年学习了蔡老板的9大节点合并算法,能还原221版本的滑块代码,但代码量实在是太多了,一个case里塞进去2万多行代码,没有耐心看下去,于是继续吃灰。

今年,某里滑块已经有人均的趋势,圈子里已经卷到飞起,00后已经开始乱杀。自己业务上也没有前些年紧张,于是可以腾出手来搞一搞。此前已经能生成226的签名,但就是过不去,也不知道错在哪里。最近正好时光佬也在搞滑块,开始佬也写了篇分析226的文章,在他们的帮助下很快就搞好了普通226滑块。此时我才知道,上一次生成226签名时就差临门一脚。

真正的挑战来自于某里系旗下网站的滑块,这些滑块圈子里一般称之为x82y。虽然代码和226是一样的,但校验更严格。此时才真正开始看226的代码,和浏览器对比,大大小小有35处不一致的地方,只好一个一个排查,排查的过程中,才知道自己的补环境框架的缺陷,才知道自己的补环境方法不合理的地方,是时候回炉重造了。好在目前应付普通226滑块就够用了,x82y等有需要了再慢慢搞了。

其实不补环境,扣算法的话,也是可以的,某里滑块离谱的是代码是固定的,能够通过AST还原代码的话,搞起来容易很多。

一直都只在国内反爬圈里打转,某里滑块是迄今为止遇到的检验最多的js反爬了,如果想测试自己补环境框架的强度,某里滑块是首选。

联系作者

和道友交流的时候,发现他对Node服务高并发一知半解,于是和他讲了下自己的理解,他说受益匪浅,于是写文章记录下来,希望能帮助到其他人。

当我们用 Express 搭建好一个 token 生成服务给其他开发调用时,我们就要开始考虑服务的性能,考虑服务的并发能力。同时也要考虑服务如何方便部署,方便更新程序。而由于 token 生成服务都是 CPU 密集型程序,所以只能通过提高进程数,提高 CPU 核数来提高并发能力。

刚来公司的时候,看到接手的 token 生成服务是这样部署的,在连续的几个端口中,比如5000,5001,5002,每个端口用 Node 启动一个 Express 服务,客户端调用的时候,随机取其中一个端口访问。服务需要更新的时候,得一个一个 Node 进程杀死后再重新启动,过程很不友好。

正好之前做过一点前端开发,前后端分离时用到过 PM2 来管理Node进程, 于是将这些服务都用 PM2 来管理,并在全组范围内推广使用 PM2,现在我们所有的 token 生成服务都用 PM2 来管理,就挺好。

PM2 很好用的一点是可以设置进程超过一定内存后就重启,通过配置 –max-memory-restart 参数即可实现。这个功能就很实用,因为 JavaScript 的闭包使用真的很容易内存泄漏,想找到原因不容易。为了不影响服务器上的其它进程,得不断重启Node进程。

单台机器上,CPU核数和进程数一致,此时并发性能最高。如果这样了还不能满足业务需求,只能堆机器,在多台机器上部署。

联系作者