十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

极验4.0滑块验证码逆向实战:一步步还原w参数生成逻辑

极验4.0滑块验证码逆向实战:一步步还原w参数生成逻辑 做爬虫的兄弟十有八九都会撞上极验滑块验证码。尤其到了4.0这个版本你打开任何一个接入它的网站页面里那个滑块看着不起眼但真正想自动化通过它的时候你会发现前端经过混淆的JS逻辑里藏着一个关键的加密字符串——w参数。后端就是靠它来判断你到底是人还是脚本所以搞清楚w参数的生成逻辑基本就等于拿到了这版验证码的入场券。这篇文章我按实际逆向操作路径完整走一遍从搭建Python爬虫环境和浏览器调试工具开始到断点定位、调用栈回溯、用Node还原出w参数的生成函数再到Python端调用并模拟提交。全程是保姆级讲解每一步都能跟着做。适合已经能写基础Python爬虫、但还没接触过前端逆向的朋友如果你正在学爬虫进阶这篇文章可以当作你的第一份极验逆向笔记。我会把那些网上很少讲的坑和为什么也都一并说出来。1. 极验4.0滑块验证码整体运行流程与w参数的角色定位1.1 一次滑块验证交互中发生了什么在动手逆向之前先把业务链路讲清楚。很多人一上来就搜w参数结果搜到一堆代码却不知道它从哪来就是因为对整体流程缺乏概念。一次完整的极验4.0滑块验证大致是这么走的前端从业务服务器拿到初始化参数常见的有 gt、challenge有的场景还会带上 c、s。浏览器加载极验的SDK脚本初始化验证码组件渲染出滑块拼图。用户点击按钮、拖动滑块时浏览器在本地不断采集鼠标轨迹、触摸轨迹、时间戳、浏览器环境指纹等信息。前端JS把这些信息加密、序列化、签名最后封装成一串参数w就是其中最核心的那个。页面通过 ajax.php 这类接口把 w 连同其他参数一起提交给验证服务器。后端解密w校验轨迹是否合理、加密是否完整、环境是否异常最终返回校验结果和二次校验凭证。这个过程我习惯用一个生活类比w就是你到柜台办事时递上去的那套盖章材料。材料里写了你的行动轨迹、办了什么、什么时候办的、带着谁的签名。柜员一看材料齐全就放行材料里有一点不符合常理直接就给你拒了。所以极验的风控核心不在那张拼图图片本身而在于后端能不能从w里读出一套“真人行为”的证据链。理解了这一点你就知道为什么光会拖动滑块不够还得能让后端信任你造出来的w。1.2 w参数为什么是逆向的核心切入口在整份提交参数里w通常是一个体积非常大的加密串比 gt、challenge 这些参数大出好几个量级而且它打包了几乎全部的行为信息。为什么说它是核心切入口三个原因第一w里承载了核心数据。轨迹、时间戳、加密后的行为数据基本都塞在w里后端对它的解密和校验是整个风控体系的主线。只要逆出w的生成逻辑就等于把这条主线握在了手里。第二w的生成有规律可循。它不是某个单独变量凭空冒出来的而是一系列函数层层处理后的输出。我们可以通过断点调试在JS运行时把它的生成过程一步步摘出来。第三其他参数大多是“陪跑”。gt、challenge这类参数来源于接口返回是动态下发的直接获取即可。真正需要你重点构造的就是w。不过也要泼盆冷水拿到了w并不等于百分百通过。极验4.0的校验是组合拳w只是其中最复杂的部分。请求头、IP环境、轨迹合理性、cookie状态都会影响最终结果。所以这篇文章后面会花不少篇幅讲轨迹和请求一致性这些才是决定成功率的上限。2. 逆向前的环境准备与工具链选择2.1 浏览器调试环境配置逆向的第一步不是写代码而是先把浏览器调试工具用溜。我建议用Chrome或者Edge都基于Chromium内核DevTools操作方式一样。你需要熟悉三个面板Sources看JS源码、打断点、看调用栈这是逆向的主战场。Console调试时执行表达式、查看变量、绕过debugger。Network看接口请求、定位提交接口以及观察w出现在哪个请求的Payload里。对新手来说最常见的问题是打开Sources后看到一大坨混淆代码瞬间不知道从哪里下手。这时候不要急记住一个原则先用Network找到提交接口再回头在Sources里搜特征字符串。比如在提交参数的Payload里看到w、gt、challenge那就在JS里搜这些名字定位速度会快很多。DevTools里还有一个特别容易被忽视的功能就是Sources面板左下角的格式化按钮两个花括号{}。点击之后压缩过的JS会展开成可读的格式断点、跳转都会好用很多。2.2 Python与Node.js环境搭建逆向过程中Python负责发起请求和调度Node.js负责运行还原出来的JS加密逻辑两者缺一不可。Python的安装很简单从官方网站下载3.8以上版本即可。安装到选择组件那一步务必勾选 Add Python to PATH不然之后在终端输入python会提示找不到命令。装完打开终端验证python --version如果你电脑上同时装了多个Python版本建议用虚拟环境隔离项目依赖。命令行执行python -m venv venv venv\Scripts\activate # Windows source venv/bin/activate # Mac/LinuxNode.js去官网下载LTS版本一路默认安装。装完验证node -v npm -v编辑器方面我用的是VS Code装一个Python插件打开终端就可以直接跑脚本。这些环境配置对任何Python爬虫项目都是基础以后也用得上。最后安装Python侧需要用到的几个库pip install requests pyexecjsrequests用来发请求pyexecjs用来调用JS。后面我会说明为什么更推荐直接用subprocess调Node但先装上两种方式对比一下你就有体感了。2.3 核心辅助库与抓包工具的优缺点很多新人会问到底用什么工具抓包、用什么方式跑JS我这里直接给一张对比表省得你来回折腾。方案用途优点缺点Chrome DevTools断点调试、定位代码免费、直观、跟浏览器环境一致混淆代码阅读成本高Charles / Fiddler抓包能看完整请求、方便对比HTTPS要装证书移动端麻烦Node.js运行还原出的JS性能好、兼容性强、生态完善需要单独安装环境PyExecJSPython调JS代码简单、几行就能跑性能一般环境配置偶尔抽风py-mini-racerPython调JS内置V8引擎、性能好大型混淆JS有时会报兼容性问题subprocess NodePython调JS最稳定、便于调试每次调用都要起一次Node进程如果你只是临时测试PyExecJS最方便。但实际做爬虫项目我更推荐subprocess调Node因为还原出来的JS通常比较复杂Node环境下跑得稳报错信息也直观。后面第4章我会把两种方案的完整代码都写出来。3. 从断点定位w参数的生成位置3.1 用全局搜索快速定位“w”赋值点准备工作做完现在开始真正进入逆向环节。第一步打开一个接入极验4.0的测试页面。最好是你自己有权限调试或已获授权的站点也可以用极验官方demo总之别拿它去干不该干的事。打开页面后按F12进入DevTools在滑块加载出来之后切到Network面板拖一次滑块找到提交验证的接口一般在URL里包含 ajax.php。点击这个请求在Payload里能看到我们关心的参数w、gt、challenge等等。记住Payload里的参数名后切到Sources面板按CtrlShiftF调出全局搜索输入 w 或 w:。这是一个非常粗糙但非常有效的定位方法。极验的混淆代码再怎么变变量赋值这种基本结构不会消失w后面跟着的一定是生成w的表达式或者调用某个函数。举个例子你搜索 w: 可能看到类似这样的结构var w get_w(track_data, config);或者更混淆的形式var _0x3f2a [w]; var _0x9c8b _0x3f2a[0] _0x4d1e;不管是哪种只要看到w被赋值就在那一行左侧点一下设置断点。然后重新拖动滑块断点触发时代码就会停住你就能看到w生成时的上下文。这里有个心得搜索时不要只搜 w也要搜 w:冒号后面加空格。因为有些代码是对象属性写法比如return { w: xxx }。两种写法一起搜不容易漏。3.2 使用XHR断点与调用栈回溯找到加密入口直接搜w有时候能找到有时候找不到因为混淆工具会把变量名处理得面目全非。这时候就要用XHR断点来兜底。操作方法是在Network面板找到提交接口判断它的URL特征比如包含 ajax.php。然后切到Sources面板右侧展开 XHR/fetch breakpoints点击加号输入 ajax.php 之类的URL片段。之后重新触发滑块验证浏览器会在发送该请求之前自动断住。断住之后重点来了看右侧的 Call Stack 调用栈。调用栈列出来的是一串函数名从最外层的发送请求函数一层层向内展开。你从调用栈的最顶层往下翻凡是跟加密、生成、sign相关的函数都点进去看看。极验的代码虽然混淆但函数命名的规律不容易完全抹掉看到可疑的函数名就直接点进去大概率能靠近w的生成位置。我遇到不少学员卡在这一步因为他们只会在代码里搜索不会看调用栈。其实调用栈就是一条从“请求发出”倒推回“参数构造”的完整路径。把它想象成你从成品包装盒一路拆到原材料仓库每拆一层离核心就更近一步。3.3 定位到核心函数后的格式化与代码梳理当你通过断点或调用栈停留到核心函数附近后先别急着读代码做三件事第一点击格式化按钮把压缩代码展开。第二在Console里执行一些临时表达式观察关键变量的值和类型。第三在函数入口处重新设置断点仔细看一步步执行时变量的变化。遇到debugger语句怎么办混淆代码里经常有反调试的debugger一运行就断住很烦人。解决办法是右键点击断点选择 Edit breakpoint把断点条件改成 false让它自动跳过或者在Console里执行以下代码把debugger重写成无害函数window._debugger Function.prototype;代码梳理阶段我建议不要急着修改任何变量名。混淆代码里变量名改了很容易导致引用错乱正确做法是先通过断点确认哪一行在w生成后被调用理解输入输出再把关键逻辑提炼到自己的脚本里。这一步的核心原则是先看懂执行顺序再动手还原。4. w参数生成链条的逆向分析与Node还原4.1 分析加密函数的结构经过断点调试你会看到w的生成基本离不开这么几条逻辑收集轨迹数组包括鼠标移动的x坐标、y坐标、时间戳。把这些轨迹点按特定格式拼接成字符串。加入时间戳、随机数、浏览器环境相关字段。对拼接结果做加密处理常见的是AES对称加密外层再用摘要或签名算法加固。最后做Base64编码得到最终的w字符串。我这里写一段示意代码模拟的是这类加密链条的常见骨架。注意这只是基于大量逆向实践总结的示例结构不是极验4.0的真实源码真实现场一定要按断点看到的执行顺序来调整拼接顺序这是最容易错的地方。// 示例骨架用于理解逆向还原的思路 const crypto require(crypto); function generateW(tracks, config) { // 1. 轨迹字符串拼接 let trackStr ; for (let i 0; i tracks.length; i) { trackStr tracks[i].x , tracks[i].y , tracks[i].t ;; } // 2. 生成签名 const sign crypto.createHmac(sha256, config.secret) .update(trackStr) .digest(hex) .toUpperCase(); // 3. 拼接最终内容 const plain [ config.gt, config.challenge, Date.now(), trackStr, sign, config.random ].join(|); // 4. AES加密 const key crypto.createHash(md5).update(config.key).digest(); const iv crypto.createHash(md5).update(config.iv).digest(hex).slice(0, 16); const cipher crypto.createCipheriv(aes-128-cbc, key, iv); let encrypted cipher.update(plain, utf8, base64); encrypted cipher.final(base64); // 5. URL编码得到w return encodeURIComponent(encrypted); }你看整个链条其实就是“数据收集 - 拼接 - 加密 - 编码”四步。极验真实代码的复杂度主要体现在两个地方一是混淆程度高变量名没有语义二是拼接顺序、加密算法细节每个版本都可能变。所以逆向的价值不在于背代码而在于能把断点看到的每一步映射到这个框架里。4.2 用Node.js还原w生成脚本当你摸清了加密链条下一步就是把断点里看到的逻辑用Node还原成独立脚本。这里有一个原则能不改就尽量不改。浏览器环境里依赖的window、document、navigator等对象可以在沙箱里补上mock优先让原JS跑起来而不是用Python重写一遍加密算法。下面是一个标准的“跑还原JS”模板。假设你已经把逆向分析后整理好的JS文件保存为 gt_restore.js里面暴露了一个 get_w 函数const fs require(fs); const vm require(vm); // 读取还原后的JS const code fs.readFileSync(./gt_restore.js, utf-8); // 创建沙箱补齐浏览器环境 const sandbox { window: {}, document: { cookie: , createElement: () ({ getContext: () ({}) }) }, navigator: { userAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, language: zh-CN, languages: [zh-CN, zh], platform: Win32 }, location: { href: https://example.com/, protocol: https: }, console: console, setTimeout: setTimeout, clearTimeout: clearTimeout }; vm.createContext(sandbox); vm.runInContext(code, sandbox); // 调用还原出的核心方法 const result sandbox.get_w(tracks, config); console.log(JSON.stringify({ w: result }));为什么要费劲mock这些对象因为JS在浏览器里运行时很多API是浏览器提供的比如document、navigator而Node环境里没有。极验的JS会读取这些对象来判断环境是否正常如果你不mock它可能直接抛异常或者返回一个异常状态。你可能会问mock多少才够我的经验是先跑起来报什么错补什么。最常用的是window、document、navigator、location、localStorage、sessionStorage这些覆盖了绝大多数情况。真实项目里可以逐步增加不需要一开始就写得很全。4.3 在Python中调用还原后的脚本还原脚本跑通后接下来就是把Python和Node串起来。方案一subprocess调Node最稳推荐优先使用。import subprocess import json import os BASE_DIR os.path.dirname(os.path.abspath(__file__)) NODE_SCRIPT os.path.join(BASE_DIR, get_w_runner.js) def get_w(tracks, config): payload json.dumps({ tracks: tracks, config: config }) proc subprocess.run( [node, NODE_SCRIPT], inputpayload, capture_outputTrue, textTrue, encodingutf-8 ) if proc.returncode ! 0: raise RuntimeError(fNode执行失败: {proc.stderr}) result json.loads(proc.stdout) return result[w]这个函数用起来很直观传入轨迹数组和配置项返回w字符串。之后再用requests模拟提交import requests session requests.Session() headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://example.com/, } data { gt: gt, challenge: challenge, w: w, # 其他参数按抓包结果补齐 } resp session.post(https://api.geetest.com/ajax.php, datadata, headersheaders) print(resp.text)方案二PyExecJS写起来更简单但稳定性略差。import execjs with open(gt_restore.js, r, encodingutf-8) as f: js_code f.read() ctx execjs.compile(js_code) w ctx.call(get_w, tracks, config)PyExecJS在Windows上第一次使用可能报错“Could not find a JavaScript runtime”原因是它找不到Node运行时。解决方法是装好Node并把路径加到PATH或者执行import execjs execjs.register(node, /usr/local/bin/node) # 换成你自己的Node路径我的个人习惯是调试阶段用PyExecJS快速验证真正落到爬虫项目里用subprocess。因为爬虫跑起来常常是一天几万次调用subprocess隔离环境更好即使Node脚本崩了也不会拖垮Python主进程。4.4 轨迹模拟与行为特征的补充有了w生成能力还差最后一环制造一条“像人”的拖动轨迹。你可能想直接用pyautogui去真实拖动屏幕不就行了如果你本机有浏览器环境确实可以。但大多数自动化场景是无头环境或者你只是想通过接口直接提交流量这时候就需要用代码生成轨迹。真实的鼠标拖动是什么样人从起点滑到目标位置不会是匀速的。通常先慢、后快、最后减速还伴随着上下方向的微小抖动。如果程序生成的轨迹全是匀速直线后端很容易检测出异常。一个简化但可用的轨迹生成思路如下import random import time def gen_tracks(distance, y10): tracks [] current 0 t 0 mid distance * 0.7 while current distance: if current mid: step random.uniform(0.8, 1.8) else: step random.uniform(0.2, 0.7) current min(current step, distance) x round(current, 1) y_offset round(y random.uniform(-1.5, 1.5), 1) t random.randint(8, 25) tracks.append({x: x, y: y_offset, t: t}) return tracks注意极验收集的轨迹点不是位移累加量而是鼠标每次移动时采样到的实时坐标。所以实际使用时你要根据滑块起点把累计位移转成绝对坐标。有些版本要求记录move事件里客户端的坐标有些则只关注相对位移具体以你逆向出的w拼接逻辑为基准。还有一个容易被忽略的点时间戳。生成轨迹时的时间戳和w内部加密时写入的时间戳以及最终提交请求的时间戳三者最好保持大致一致相差太远会被后端判为伪造。所以实际代码里轨迹的时间间隔、w生成时间、请求发出时间尽量在同一个程序流程里连贯完成。5. 常见请求失败场景与完整排查清单5.1 被识别为机器行为的典型报错与处理即使按上面的流程全部走通你依然可能遇到各种失败。我把常见现象、可能原因和排查方向整理成表方便快速对照。报错或现象可能原因排查方向返回 code10001参数签名校验失败或缺少参数检查w是否完整生成gt/challenge是否取自最新会话一直提示“拼图失败”轨迹太假或w内轨迹与提交内容不一致轨迹加入加速减速核对坐标编码顺序提示“网络环境异常”请求头不完整或网络出口风险高补全UA/Referer检查cookie状态本地浏览器调试正常Node跑报错缺少window/document等浏览器环境在沙箱里mock缺失对象debugger断点无限循环混淆脚本的反调试机制用条件断点或覆盖debugger函数PyExecJS找不到JS运行时没有安装Node或环境变量未配置安装Node手动register运行时表格里的每一条我都实际遇到过。尤其是第一类code10001最容易让新手陷入盲区。80%的情况不是w算法不对而是gt/challenge已经过期或者和当前会话不匹配。记住一个原则请求中的gt/challenge必须是当前页面最新初始化的不能用浏览器里复制来的旧值。5.2 调试中最容易踩的五个坑除了报错还有几个坑属于“不报错但结果不对”的隐蔽问题这里单独拎出来说。第一个坑是版本差异。极验4.0的代码在官网demo、不同行业的业务方、甚至同一业务方的不同页面上都可能不同。你逆向了一个站点的逻辑不代表所有站点通用。每次换目标站点都要重新走一遍定位流程。第二个坑是断点位置漂移。混淆代码每次加载可能在文件名后加版本号函数结构也可能微调。你昨天定位的断点行号今天可能就不适用了。遇到这种情况重点搜索w等特征字符串而不是依赖固定行号。第三个坑是Node与浏览器的环境差异。浏览器里的Crypto接口和Node的crypto模块用法不完全一样有些混淆JS在浏览器里依赖window.crypto生成随机数到Node里就成了undefined。遇到这种问题需要在沙箱里补齐crypto的mock或者引入第三方库来对齐行为。第四个坑是字符编码。提交w参数时如果加密结果里包含、/、这类Base64字符直接放在URL参数里容易被解析错。很多教程会漏掉这一步导致你本地生成w正常提交后却参数错误。解决方法是做URL编码Python里用urlencodeJS里用encodeURIComponent。第五个坑是随机性不足。w生成逻辑里如果有随机数或时间因子而你的实现为了追求复现稳定把随机数固定了那后端一旦做分布统计就会因为“太稳定”而判定异常。正确做法是让随机数真实随机不要用固定种子。5.3 提升成功率的小技巧最后分享几个我在实战中验证过的小技巧可以明显提升通过率。第一发起目标页面请求之前先手动打开页面做一次真实滑动让cookie和接口交互状态更完整。很多站点会记录从初始化到提交的完整状态链凭空直接提交w大概率失败。第二请求头不要只带UA。浏览器真实请求里会有Accept、Accept-Language、Origin、Referer等字段提交时尽量补齐。有些后端会检查Referer是否来自业务页面缺失就会被拦截。第三控制提交频率。同一IP高频率提交验证码的结果会被风控系统标记。哪怕w参数完全正确也可能会因为频率触发限制所以线程数和间隔时间不要调得太激进。第四如果接口要求同时提交c、s这类参数要一并获取并一起提交。它们和gt、challenge共同构成会话状态缺一个都可能校验失败。6. 写在最后逆向不能越过的红线6.1 适用范围与合规边界讲到这里核心流程已经完整走通了。但有些话必须放在最后说。这篇文章讲解的是技术原理和学习思路用途仅限于学习研究以及在你拥有权限或已获授权的系统上做测试。不要用这套能力去批量绕过别人网站的验证码抓取数据、抢票、刷接口、薅羊毛。平台侧的风控规则不是摆设一旦造成影响轻则封号封IP重则涉及法律风险。我自己在带人入门时一直强调技术本身是中性的但使用场景决定了它的价值还是风险。把这个能力用在正经项目里比如给公司自研系统做自动化测试、做竞品公开数据的合规采集、研究风控对抗的演进原理它都是很好的技术积累。用来做损人利己的事迟早翻车。6.2 后续可以深入的方向如果你对这块真的感兴趣逆向完w参数只是个开始。后面还可以往这几个方向深入一是学习AST抽象语法树解析。混淆代码手工分析太慢配合AST工具可以自动还原变量名、展开控制流大大提升逆向效率。二是研究动态代码加载机制。极验的JS会动态变化每次请求下发的内容都可能不同。理解它的加载和更新机制比记死某一段代码更有价值。三是用深度学习做缺口识别。滑块拼图的缺口定位往往需要人工标点用图像识别模型可以自动完成减少整个流程的人工介入。我之前写过用OpenCV找缺口的方法准确率80%左右配合深度模型能到95%以上这块有时间再单独写。四是可以把整个流程封装成一套通用沙箱。维护一个模块化的Node虚拟机环境切换目标站点时只需要替换JS脚本不用改Python主代码工程化之后效率会高很多。最后说一点个人体会。我刚开始逆向极验的时候光定位w参数就卡了两天整个人处在一种焦虑又兴奋的状态里。后来发现最大的障碍不是代码难读而是自己太急躁总想一步到位。后来放慢节奏老老实实打断点、看变量、记录执行顺序反而越走越顺。这种“先慢后快”的节奏我一直沿用到现在。如果你也在逆向途中感到挫败不妨也试一下把心态放平一步步来很多问题其实都会在调试过程里自己露出答案。
返回列表