什么是“极验”?
如果你是从事爬虫相关工作的,那么一定对这两个字不会陌生的。
极验是首家「行为式验证」安全技术服务提供商,并以提供验证码服务而闻名。我们日常会登录一些网站,有的网站登录前需要校验验证码,而这个验证码服务很可能就是极验提供的。
我们直接进入正题
如图所示 这个在登陆面前的拦路虎 便是大名鼎鼎的极验滑块验证码了
看到这里 有点基础的小伙伴 应该马上能想到用selenium来模拟人为拖动滑块来解决,这确实是一个不错的方法,在需求量较小的情况下,不失为一个优先的选择,但它的缺点也十分明显:
一是模拟滑动,容易被极验检测到我们使用的是自动化软件,从而导致滑动操作失败;
二是每一次登录任务都需要驱动浏览器,将会导致登录耗时较长。
所以今天我们讲的是另一个方式,硬刚JS,破解每个请求中的加密参数,之后在程序中发送请求得到正确响应,即POST逆向协议的方式来解决它
首先国际惯例第一步先抓包分析
请求参数 Form Data 有多个,包括:appId、loginName、loginPwd、geetest_challenge 等。可以看到,密码被加密成 CN-SA95… 这一长段,另外还有三个以 geetest_ 开头的加密参数,分别是:
•geetest_challenge: 102f7d723ad76e387ad6000f87ff91f8j3
•geetest_validate: 651ecdf62cb1e940e5ea999b6af7fc10
•geetest_seccode: 651ecdf62cb1e940e5ea999b6af7fc10|jordan
从参数命名上,我们能够很清晰地看出,这是极验滑动验证码的加密参数。也即是说,我们点击验证码,拖动滑块这些动作,最终转换为这三个加密参数。我们的主要工作,也在于破解这三个参数。细心的同学可能发现了,其中 geetest_validate 与 geetest_seccode 参数基本相同,只不过 geetest_seccode 多了 |jordan 的字符串后缀。那么,最主要的工作就是解出 challenge/validate 两个参数。
我们接着往上找,可以发现ajax.php这个请求
仔细剖析这个接口 不难发现params里面有 gt、challenge、lang、w 和 callback。其中 w 加密成了一长串,所以我们重点来看这个w参数。
我们找到slide.xxx.js 文件,跳转到 Chrome Sources 菜单栏,查看 JavaScript 代码
剖析出一些关键代码
function tPcX(e) {var SkB = uklgT.yaA()[0][22];for (; SkB !== uklgT.yaA()[16][19]; ) {switch (SkB) {case uklgT.yaA()[0][22]:var t = this;var r = e["DxJq"];SkB = uklgT.yaA()[0][21];break;case uklgT.yaA()[16][21]:r["height"] = r["width"] = 0;t["vjyG"] = r["getContext"]("2d");SkB = uklgT.yaA()[4][20];break;case uklgT.yaA()[4][20]:t["wOTb"] = t["xmDd"] = t["yZRm"] = t["AZ_O"] = 0;t["BnKG"] = r;SkB = uklgT.yaA()[4][19];break;}}}
去除冗余代码后
function tPcX(e) {var t = this;var r = e["DxJq"];r["height"] = r["width"] = 0;t["vjyG"] = r["getContext"]("2d");t["wOTb"] = t["xmDd"] = t["yZRm"] = t["AZ_O"] = 0;t["BnKG"] = r;}
通过对多个 JavaScript 文件,如 slide.js/fullpage.js 等进行反混淆、去冗余,我们对 JavaScript 的调用逻辑清晰了很多。
完成了代码反混淆,还有一个重要的工作,就是如何从 JavaScript 代码中抽离出关键的 JavaScript 代码,这些代码就是请求参数的加密逻辑。我将此过程称为“代码解绑定”。
这个过程并不需要什么技巧,需要的只是耐心,耐心,耐心。跟着 Chrome 浏览器,打断点分析请求的入口与出口,一步步将关键代码剥离出来。
比如,我们需要解出参数 a,那么就抽离出加密参数 a 的代码,封装成一个 get_a() 的函数。
封装了几个需要的 JavaScript 函数,我们可以在 Python 程序中,使用 PyExecJS 库,方便地执行 JavaScript 代码拿到加密参数。以下为一些示例代码:
import execjspath1 = path + '/js-file/get_rp.js'path2 = path + '/js-file/get_gj.js'# RSAdef get_yf():ec = Encrypyed()key = ec.create_secret_key(8)enc_sec_key = ec.rsa_encrpt(key, ec.pub_key, ec.modulus)return enc_sec_key,keydef get_w1(gt,challenge):yf,key = get_yf()test = '''{"gt":"%s","challenge":"%s","offline":false,"product":"bind","width":"300px","protocol":"http://","maze":"/static/js/maze.1.0.1.js","click":"/static/js/click.2.7.6.js","fullpage":"/static/js/fullpage.8.7.3.js","geetest":"/static/js/geetest.6.0.9_orgin.js","type":"fullpage","static_servers":["/","dn-staticdown.qbox.me/"],"pencil":"/static/js/pencil.1.0.3.js","slide":"/static/js/slide.7.5.5.js","voice":"/static/js/voice.1.2.0.js","beeline":"/static/js/beeline.1.0.1.js","aspect_radio":{"click":128,"voice":128,"slide":103,"beeline":50,"pencil":128},"cc":12,"ww":true,"i":"3359!!17234!!CSS1Compat!!50!!-1!!-1!!-1!!-1!!3!!-1!!-1!!-1!!9!!9!!-1!!9!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!9!!-1!!4!!-1!!-1!!-35!!72!!0!!0!!901!!841!!1955!!958!!zh-CN!!zh-CN,zh!!-1!!1!!24!!Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36!!1!!1!!1920!!1080!!1920!!1030!!1!!1!!1!!-1!!Linux x86_64!!0!!-8!!2618e15e5d8c7f174cad42997c4436f2!!07f3564381d09b0994b7e73a99c6d7c7!!internal-pdf-viewer,mhjfbmdgcfjbbpaeojofohoefgiehjai,internal-nacl-plugin!!0!!-1!!0!!12!!Arial,Courier,CourierNew,Helvetica,Times,TimesNewRoman,Wingdings,Wingdings2,Wingdings3!!1559555859986!!-1,-1,1,0,0,0,0,1,57,3,3,3,4,368,368,407,693,693,693,-1!!-1!!-1!!60!!6!!-1!!-1!!14!!false!!false"}'''%(gt,challenge)ec = Encrypyed()iv = b"0000000000000000"enc_text = ec.aes_encrypt(test, key.decode('utf-8'), iv)array = []for byte in enc_text:array.append(byte)enc_text = ec.bytes_to_string(array)return enc_text+yf, keydef get_w2(gt,challenge,key,c,s):passtime = 1230_js = pile(open(path1, 'r').read())rp = _js.call('f3',gt,challenge,passtime)_js1 = pile(open(path2, 'r').read())tt = fun_u('M*d8PjAA3(-95e6D:)lN1NU91W-O/MEM92N*)O,n2_(/Xn(e5((,,-_N(,)(I-*5P4)(db9-5/)1g:AAdbE-NME)qqn(2DM()jFOJb5,)M9QEQ2,)(/*(M57(,(M((b',c,s)text = '''{"lang":"zh-cn","type":"fullpage","tt":"%s","light":"INPUT_0","s":"93e3d79ea85149d4e44244d3df6e16e5","h":"3deef994fc3cbf723df4c186cfe78a27","hh":"152caf38ccf01c64d90d2d0e8526c018","hi":"541c245c0365e2dcb84cef1b1366f775","ep":{"ts":1559840826770,"v":"8.7.3","ip":"192.168.124.11,218.98.33.165","f":"aea0f96bf3e494262bedf6ec7570f83b","de":false,"te":false,"me":true,"ven":"NVIDIA Corporation","ren":"GeForce GTX 1050 Ti/PCIe/SSE2","ac":[],"pu":false,"ph":false,"ni":false,"se":false,"fp":["move",302,315,1559840498541,"pointermove"],"lp":["up",234,339,1559840499124,"pointerup"],"em":{"ph":0,"cp":0,"ek":"11","wd":0,"nt":0,"si":0,"sc":0},"tm":{"a":1559840496633,"b":1559840496768,"c":1559840496768,"d":0,"e":0,"f":1559840496635,"g":1559840496635,"h":1559840496635,"i":1559840496635,"j":1559840496635,"k":0,"l":1559840496635,"m":1559840496735,"n":1559840496745,"o":1559840496773,"p":1559840496832,"q":1559840496832,"r":1559840496906,"s":1559840496930,"t":1559840496930,"u":1559840496930},"by":2},"captcha_token":"bboy","passtime":%s,"rp":"%s"}'''%(tt,passtime,rp)ec = Encrypyed()iv = b"0000000000000000"enc_text = ec.aes_encrypt(text, key.decode('utf-8'), iv)array = []for byte in enc_text:array.append(byte)enc_text = ec.bytes_to_string(array)return enc_text
以上便是整个极验滑动验证码破解的过程,再简单总结一下:
1.请求参数分析
2.代码反混淆
3.代码解绑定
4.滑动轨迹采集
其中,还包括验证码图片的还原,图片缺口的计算等等…