在家隔离2个多月的时间里,看了很多关于滑动验证码的破解技术博客,大多使用 Selenium、Webdriver 等技术破解的,我也尝试了破解xxxx网站,效率蛮低的,满足不了实时数据更新的需求,然后看到一些高级爬虫前辈直接通过解密 JavaScript 参数破解。效率高并且成功率是100%,但是难度非常的大。
目前,对于这一类的滑动验证码,网上常见的一个破解方法就是根据完整图片和缺口图片的像素差来计算缺口的位置,然后使用 Selenium 自动化测试工具,模拟人手动拖动滑块的过程。这种方法实现较为简单,因为是模拟人去滑动,滑动的轨迹很不好把握,很容易被极验检测到我们使用的是自动化软件,从而导致滑动操作失败;其次是每一次启动都需要驱动浏览器,登录耗时较长,这对于实时性要求较高的数据采集任务来说,是无法满足需求的。
高级爬虫的厉害之处是走的逆向解析JS代码破解:死磕 JavaScript 代码,破解每个请求中的加密参数,之后在程序中发送请求得到正确响应。 这是最直接且最高效的方式,它无需等待浏览器对页面进行渲染,只要解出加密的请求参数,发送请求获取响应即可。用户在浏览器中的操作,如点击验证码、拖动滑块等动作,最终都会转化为请求发送到服务端(全部都是POST请求),服务端对请求参数进行合法性校验,并响应结果。
为什么说很难呢,一是因为我并非从事前端开发工作,对于 JavaScript 代码的调用栈分析不如前端同学熟练;二是因为极验的 JavaScript 代码实在混淆得特别难懂,如何从上万行混淆的 JavaScript 代码中抽离出关键的加密代码,可以相信想象下里面的工作量。可以毫无夸张地说,没有足够的毅力和耐心,很难实现破解。
动手破解
接下来我会大致描述整个破解流程,对于过于细节化的东西,就不在这里瞎丢丢了。
1. 请求参数分析
我以最终的登录方法作为分析入口。输入一个错误的账号密码,然后点击滑动并拖动到正确的位置。此后页面会提示“账户名或密码错误!”。观察浏览器 Network 栏,其实登录这个动作是发出了 Name 为 accLoginPC.do 这个请求。
请求的 URL 是:
https://xxx.xxx/webapi/accLoginPC.do (敏感的URL)。
请求参数 Form Data 有多个,包括:appId、loginName、loginPwd、geetest_challenge 等。可以看到,密码被加密成 CN-S513... 这一长段,另外还有三个以 geetest_ 开头的加密参数,分别是:
- geetest_challenge: 102f7d723ad76e387ad6000f87ff91f8j3
- geetest_validate: 651ecdf62cb1e940e5ea999b6af7fc10
- geetest_seccode: 651ecdf62cb1e940e5ea999b6af7fc10|jordan
从参数命名上,我们能够很清晰地看出,这是极验滑动验证码的加密参数。也即是说,我们点击验证码,拖动滑块这些动作,最终转换为这三个加密参数。我们的主要工作,也在于破解这三个参数。细心的同学可能发现了,其中 geetest_validate 与 geetest_seccode 参数基本相同,只不过 geetest_seccode 多了 |jordan 的字符串后缀。最主要的工作就是解出 challenge/validate 两个参数。
对于其他非极验加密参数,如 loginPwd、jtSafeKey、token 等,均属于XX官网自身的加密逻辑,破解难度不大,这里我们主要关注的是极验参数的破解。
我们再往上找,发现了 ajax.php? 这个请求,这个请求里的响应信息是一个类似 json 串的东西,里面包着 validate 参数。震惊,这不就是我们前文的 geetest_validate 参数吗?
观察其请求参数,发现是 gt、challenge、lang、w 和 callback。其中 w 加密成了一长串。
如此分析,我们逐渐捋清思路,得知每一个请求都需要解出哪些加密参数,我就不在这里花费大量篇幅做细致描述了。
之后针对这些参数名,查看 JavaScript 代码中是如何加密的,然后用python代码解析出来.....
2. 代码反混淆(是一种代码保护机制)
我们点击 slide.xxx.js 文件,跳转到 Chrome Sources 菜单栏,查看 JavaScript 代码。
想查看 JavaScript 代码对请求参数的加密逻辑可没那么容易,极验做了大量的混淆工作,为的是提高我们的破解难度。
可以看到,代码基本不是我们凡人所能看懂的。它对代码进行了 Unicode 编码,并且加上了大量的混淆代码。这导致我们直接搜 challenge 这些关键参数名,是找不到相关代码的。
那么,我们首先将完整的 JavaScript 代码 copy 出来,先到网上找一个 Unicode 编码还原工具,对代码进行 Unicode 解码。解码之后,复制到编辑器中,搜索 challenge,发现了几个关键的参数。
此时的代码依然难以解读,可读性几乎为零,一般的小伙伴都要放弃了,太烧脑了,因为极验对每个关键参数都进行了混淆,混淆的逻辑就是使用类似这种 UtTS 的代码进行替换,逻辑也藏在 JavaScript 代码中,具体就不展开了。我们对其进一步还原。还原之后,代码如下:
代码的可读性也是越来越清晰,还有一个问题是:怎么还有很多这种 var xow_list = uklgT.xow 莫名其妙的代码。我们研究发现,这其实是极验加入的冗余代码,主要目的是为了混淆视听。
列如,对于这样的冗余代码:
还原之后,就变成了以下精简且易读的代码:
通过对多个 JavaScript 文件,如 slide.js/fullpage.js 等进行反混淆,我们对 JavaScript 的调用逻辑清晰了很多。
3. 代码解绑定
完成了代码反混淆,还有一个重要的工作,就是如何从 JavaScript 代码中抽离出关键的 JavaScript 代码,这些代码就是请求参数的加密逻辑。我将此过程称为“代码解绑定”。
这个过程并不需要什么技巧,需要的只是耐心,耐心,耐心。跟着 Chrome 浏览器,打断点分析请求的入口与出口,一步步将关键代码剥离出来。
比如,我们需要解出参数 a,那么就抽离出加密参数 a 的代码,封装成一个 get_a() 的函数。
封装了几个需要的 JavaScript 函数,我们可以在 Python 程序中,使用 PyExecJS 库,方便地执行 JavaScript 代码拿到加密参数。以下为一些示例代码:
后面的内容需要各路大神大显神通了,技术上的交流,不干其它不允许的事哈。