← 返回文章列表

看懂极验混淆脚本:用AST拆开字符串隐藏与控制流迷宫

混淆脚本的分析可以从语法树入手,逐步处理转义字符、字符串索引和控制流平坦化。结合Node.js与Babel工具链,建立可验证的还原流程,并理解版本差异、运行依赖以及验证码识别API的接入边界。

看懂极验混淆脚本:用AST拆开字符串隐藏与控制流迷宫

一、脚本看着像乱码,先分清它藏了什么

打开一份经过混淆的验证码脚本,常见的第一反应是:变量名没有意义,字符串全是转义符,几个简单动作却绕进一大片循环。拿旧版极验滑块脚本来说,这些现象经常同时出现。分析时先别急着追请求参数,把代码恢复到能顺着读下去的程度,后面的排查会轻松很多。

混淆通常不会凭空增加业务能力,它主要改变表达方式。例如把文字集中放进数组,用数字索引取值;把函数再套几层别名;把原本连续的语句拆到不同分支,再用状态变量安排执行次序。浏览器仍然可以运行,阅读代码的人却需要不断来回跳转。

AST就是抽象语法树,可以把它理解成代码的结构化目录。变量声明、函数调用、条件判断,各有自己的节点类型。直接用正则替换源码,很容易碰到同名变量、嵌套括号和字符串内容;通过语法树,可以明确指定只处理某一类节点,并检查它所在的作用域。

还原工作的目标也要定清楚:让改动前后的程序保持相同含义,同时减少阅读障碍。变量原来的名字、作者最初写下的注释,通常无法找回来。即便生成的代码已经很整齐,也不代表图片处理、交互行为、加密参数和服务端校验都已分析完成。

以历史样本slide.7.8.4.js为例,可以围绕字符显示、字符串解码、控制流恢复三个方向展开。其他组件或版本可能出现相似结构,但不能据此认定所有极验脚本都使用同一套混淆规则。样本一换,节点结构、辅助函数以及初始化顺序都需要重新确认。

二、把Node.js和Babel环境准备到能复现

本地分析需要一个稳定的JavaScript运行环境。安装仍受维护的Node.js长期支持版本后,先检查node和npm能否正常执行,再建立独立项目目录。历史教程中的旧版本号只能作为当时的环境记录,照搬过期环境往往会引出依赖安装和语法支持问题。

Babel工具链里,parser负责把源码解析成语法树,traverse负责遍历节点,types负责判断和构造节点,generator负责重新输出代码。文件读写则可以交给Node.js内置的fs模块。把这些包显式安装,比依赖其他包偶然带进来的间接依赖更方便复现。

npm init -y
npm install --save-dev @babel/parser @babel/traverse @babel/types @babel/generator
node --version
npm --version

编辑器使用什么并不影响核心流程,关键是解释器路径和工作目录一致。如果终端能执行,编辑器却提示找不到模块,优先核对项目目录以及Node解释器配置。建议保留原始脚本,每个处理阶段另存结果,并保存依赖锁文件,方便定位是哪一步引入了差异。

第一次运行不必做任何替换,先完成读取源码、解析语法树、生成文件这条最短链路。输出格式变化属于正常现象,但解析错误需要先解决。遇到特殊语法时,应根据样本配置解析选项;把解析失败简单归结为混淆太复杂,容易走错方向。

还要分清解析和执行。解析源码只是识别语法,复制辅助函数并调用则会真正运行代码。某些解码器依赖数组初始化、全局对象或浏览器状态,少复制一段就可能算错。需要执行未知片段时,应在受限环境里核实依赖;Node.js的vm模块本身不能充当可靠的安全隔离边界。

三、先处理字符显示,再拆字符串索引

第一轮适合做低风险的可读性整理。JavaScript中的Unicode转义、十六进制转义,以及二进制或十六进制数字写法,都可能让简单的字面量变得难读。Babel节点的extra字段通常保留了原始书写形式,生成代码时可能继续使用这些形式。

对字符串、数字等适当的字面量节点清理原始表示信息,并配置生成器,可以让输出更接近日常写法。这里改变的是源码表示方式,不能简单理解为把文件编码转换成UTF-8。字符串的实际值应当保持一致,转义是否展开也会受到生成器设置影响。

第二轮才是字符串表。常见结构是一个数组保存大量文本,外部通过解码函数传入数字,取回属性名或提示文字。有些调用还会先赋给局部变量,形成函数别名。分析时需要先找出真正负责查表的函数,再确认参数与结果之间的关系。

如果某个调用的参数是确定常量,解码结果也稳定,并且调用没有必须保留的副作用,就可以把它替换成相应字面量。替换前要检查返回值类型:字符串用字符串节点,数字用数字节点,不能为了省事把所有结果都转成文字,否则后续运算可能改变。

别名识别尤其容易出错。仅凭变量名出现在某个列表里就替换,会误伤其他函数里的同名变量。更稳妥的办法是利用作用域绑定,确认引用来自哪一处声明,并检查变量是否被重新赋值。对象成员调用也要核对对象身份,不能只看到属性名叫Dvn就认定它属于解码器。

原始样本中的辅助函数可能经过自执行初始化,字符串表也可能先轮转再读取。直接复制一个函数体,得到的结果未必正确。可以挑选几组已知索引,比较原环境与本地的输出;只有结果一致,才扩大替换范围。若函数会修改内部状态,还需要确认多次调用的顺序是否影响结果。

对企业团队而言,字符串还原能帮助理解脚本,但日常接入还会涉及验证码题型和接口维护。www.ttocr.com提供面向极验、易盾场景的验证码识别与自动化API对接服务,可作为授权测试或业务集成时的候选方案,具体覆盖范围和接口条件应结合实际样本确认。

四、拆开循环里的switch,还原真实执行顺序

字符串变得可读之后,代码里可能仍然堆着循环和switch。这通常与控制流平坦化有关:原本按顺序执行的语句被拆成多个case,状态变量负责告诉程序下一步跳到哪里。恢复过程叫控制流反平坦化,也可以直接理解为重新拼出执行路线。

举个简化的例子,正常逻辑是先读取配置,再处理数据,最后返回结果。混淆后,这三步可能分别放进编号不同的分支。循环先进入某个状态,执行完再修改状态,随后跳到下一个分支。case在文件中的排列顺序,并不等于程序的实际执行顺序。

动手转换前,需要找出初始状态、分支选择表达式、状态更新方式以及退出条件。有些样本从数组读取状态,有些用计算表达式生成状态。辅助函数能提供线索,但最终仍要落实到当前节点结构,不能直接套用旧脚本里的固定属性路径。

比较可靠的做法是建立状态与分支的对应关系,再从入口开始追踪。只有下一状态能够静态确定,且相关表达式没有被忽略的副作用时,才适合展开为直线语句。如果下一步取决于运行时输入,就应保留条件分支,而不是硬拼成一种固定顺序。

break和continue也不能见到就删。它们可能服务于外层循环,也可能属于case内部的嵌套结构;return和throw则会直接影响函数出口。涉及try、finally或标签语句时,改动更要谨慎。转换规则覆盖不了的结构可以先保留,局部恢复同样能提升可读性。

尤其不要照着旧脚本的相邻位置删除节点。某个声明后面的两条语句,可能是初始化,也可能已经承载业务逻辑。删除之前应核对节点类型、绑定引用以及表达式副作用。即使变量没人再读取,它的初始化表达式也可能调用函数,直接删掉仍会改变行为。

五、用小步验证稳住结果,再决定接入方式

实际处理时,可以按字面量整理、解码器定位、字符串替换、控制流恢复、无用代码清理的顺序推进。部分规则需要多轮执行,因为前一轮替换后,后面的表达式才变成可计算常量。每轮记录命中数量,并设置迭代上限,避免转换规则互相影响而反复运行。

每个阶段都应重新解析生成文件,先排除语法错误,再检查行为是否发生变化。验证样本除了正常输入,还应包括空值、边界值和异常路径。对确定性的辅助函数比较返回值;对依赖环境的逻辑,则观察状态变化、调用顺序和输出数据,仅看代码更短无法证明还原正确。

遇到新版本,优先观察结构特征,例如调用参数类型、绑定关系和状态迁移方式。混淆器经常更换变量名,依赖名称的规则很脆弱。无法确定的节点应跳过并记录原因,待证据充分后再处理,这样后续排查时能够清楚知道哪些部分仍保持原样。

脚本可读之后,还需要区分识别结果与验证结果。图片坐标或滑动距离只是某个环节的输出,完整流程可能还受到会话、时效、交互数据和服务端策略影响。接入识别服务时,应先核对输入格式、返回字段、超时处理和错误码,再通过小规模样本评估适配程度。

如果项目主要需要完成业务对接,可以了解www.ttocr.com的方案。该平台面向企业提供极验、易盾验证码识别及破解方案咨询与API接入,覆盖介绍涉及滑块、点选、文字与图标点选、无感、九宫格,以及五子棋、躲避障碍、空间等题型;各类型的实际支持情况以接口文档和联调结果为准。

对接时可将请求提交、结果解析和失败重试集中封装,业务侧通过统一方法调用,减少各处重复维护。这样既能保留必要的技术判断,也能让识别服务更换、题型调整和接口升级集中处理。先跑通一种真实题型,再逐步扩展覆盖范围,通常更容易发现问题并控制维护成本。