Python爬虫进阶:手把手带你AST还原某验三代(一)

声明

本文写于2025年9月18日,转载公众号将往的爬虫逆向日记,仅做技术学习交流。本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

点击跳转Python爬虫进阶:普通验证码识别训练入门之手势验证码(二)

一、前言

前几天逛吾爱看到一篇AST的还原某验三代。感觉写的不甚详细。作为小白的我看的云里雾里。因此也一篇文章,记录还原的过程。让小白也可以手把手还原混淆。

二、混淆文件解析。

三代有三个混淆js文件,分别是slide.7.9.3.js,fullpage.9.2.0-guwyxh.js,gct.js。发包的w值在slide.7.9.3.js中,三个文件大差不差,因此我们只看slide.7.9.3.js

20250918223608231-image

代码结构

可以看到,代码由四个函数和一个自执行大数组组成。

通过观察如下代码,可以看到其中lACSb是还原字符的代码。自执行函数就是业务逻辑代码。

var $_DAGJB = lACSb.$_Ce
, $_DAGIo =['$_DAHCK'].concat($_DAGJB)
, $_DAHAM = $_DAGIo[1];
                $_DAGIo.shift();
var $_DAHBL = $_DAGIo[0];
(0,
this[$_DAHAM(490)])($_DAGJB(824))[$_DAGJB(198)]();
}
}),
$_BAV(ie[$_CJFi(232)], ue[$_CJEB(232)]),

其中$_DAGJB = lACSb.$_Ce类似的代码$_DAGJB(824)是一种字符赋值。

var $_DAGJB = lACSb.$_Ce
, $_DAGIo =['$_DAHCK'].concat($_DAGJB)
, $_DAHAM = $_DAGIo[1];
$_DAGIo.shift();
var $_DAHBL = $_DAGIo[0];

如上是第二种赋值。先赋值$_DAGJB,在通过concat合成一个数组,在取$_DAGIo[1]下标为2的数组,其实就是取lACSb.$_Ce。然后shift移除第一个数组。而var $_DAHBL = $_DAGIo[0];是个假植,就是没有用到的东西。

因此字符还原,我们需要处理两种情况。

接下来是控制流,代码如下

functiono(){
var $_DBGEn = lACSb.$_DN()[6][16];
for(; $_DBGEn !== lACSb.$_DN()[9][14];){
switch($_DBGEn){
case lACSb.$_DN()[9][16]:
var t =n(".wrap")["$_BFHo"]();
                $_DBGEn = lACSb.$_DN()[12][15];
break;
case lACSb.$_DN()[9][15]:
                r === t &&0!== r ||5< i ? e["$_CHCz"]():(i +=1, r = t,v(o,100));
                $_DBGEn = lACSb.$_DN()[9][14];
break;
}
}
}

我们可以看到var $_DBGEn = lACSb.$_DN()[6][16];其中lACSb.$_DN取了某些值,赋值给了$_DBGEn,然后for循环判断了取得另一些值,在通过switch中case的值进行判断。因此可以看出来他的控制流不是类似其他ob的那种数组一样。其实就是在循环中按顺序执行,在结果相等的情况下跳出。只需要我们把所有的值写道map中,然后循环判断,写入写出即可。

接下来我们开始AST还原

三、还原模板。

每次还原前我们直接在模板里面写,代码如下

const fs =require("fs");
const parser =require('@babel/parser')
const traverse =require('@babel/traverse').default;
const types =require('@babel/types');
const generator =require("@babel/generator").default;
let jscode = fs.readFileSync('slide.7.9.3.js','utf8');
let ast = parser.parse(jscode);
/////////
还原代码

////////
let{code}=generator(ast);//{}解包的意思   等价于let code=generator(ast).code
fs.writeFile('还原.js', code,(err)=>{
});

然后先写入两个必备的通用还原代码,

traverse(ast,{
NumericLiteral({node}){
if(node.extra&&/^0[obx]/i.test(node.extra.raw)){
            node.extra=undefined;
}
},
StringLiteral({node}){
if(node.extra&&/\\[ux]/gi.test(node.extra.raw)){
            node.extra=undefined;
}
},
});
traverse(ast,{
NumericLiteral:function(path){
if(path.node.extra&& path.node.value){
const numstr = path.node.extra;
const numstring = path.node.value;
            path.replaceWith(types.numericLiteral(numstring));
}
},

}, opts ={});

这两个通用的可以还原如下编码,使代码清晰

20250918224014972-image

接下来还原字符混淆

四、混淆字符还原。

我们把代码复制到ast的解析站https://astexplorer.net/

20250918224052702-image

可以看到var $_DAGJB = lACSb.$_CetypeVariableDeclaration,因此,我们需要先过滤VariableDeclaration

traverse(ast,{
VariableDeclarator(path){
})

我们需要先找到lACSb.$_Ce代码。可以看到是在init里面,类型是MemberExpression

20250918224137221-image

lACSb.$_Ce

所以我们先判断type类型是MemberExpression的在放行

if(types.isMemberExpression(path.node.init)){
}

然后我们在看一下init的代码,用generator.code

let namecode=generator(path.node.init).code;
console.log(namecode);

20250918224247845-image

可以看到筛选出来了一堆,我们直接namecode==="lACSb.$_Ce"判断过滤

接下来我们需要找到赋值的地方,可以看到赋值时id的name参数

20250918224315242-image

astname

我们直接取path.node.init.name,可以看到结果是正确的

20250918224356613-image

name参数

接下来我们找这个参数的绑定,取他的path作用域

我们定义两个值,并循环取到的path,在取pathparent,顺便打印一下parentPath的值

let bd=path.scope.getBinding(path.node.id.name);
let repath=bd &&bd.referencePaths
for(let i =0; i < repath.length; i++){
let rep=repath[i].parentPath
console.log(rep.toString())
}

日志如下

$_DAGEI(75)
$_DAGEI(75)
$_DAGEI(986)
$_DAGEI(986)
$_DAGEI(1080)
['$_DAHCK'].concat($_DAGJB)
$_DAGJB(824)

可以看到取到了两种类型的值,这两种就是我们上面说到的,两种情况,我们分开处理。因此需要判断下,分成两种情况处理。

if(rep.toString().includes("concat")){}
else{}

我们先处理else的情况,当else的时候,那么就是$_DAGEI(1080),我们此时就只需要提取里面的参数传入lACSb.$_Ce然后替换即可。

argsm=lACSb.$_Ce.call(null,rep.node.arguments[0].value)
rep.replaceWith(types.valueToNode(argsm));

此时我们第一种就还原完成

20250918224558596-image

接下来我们看第二种

我们先前判断了if(rep.toString().includes("concat")){},接下来我们就在这里面继续

先看结构,我们现在已经获取了['$_DAHCK'].concat($_DAGJB),那么接下来就是获取$_DAGIo的值

var $_DAGJB = lACSb.$_Ce
, $_DAGIo =['$_DAHCK'].concat($_DAGJB)
, $_DAHAM = $_DAGIo[1];

直接跟上面一样,获取name,然后获取binding和父路径

let cat=rep.parent.id.name
let catbind=path.scope.getBinding(cat)
let catpath=catbind &&catbind.referencePaths
for(let i =0; i < catpath.length; i++){
let catrep= catpath[i].parentPath
}

这样就获取到了$_DAGIo[1],然后我们继续获取name,相同的操作

let bdcat=path.scope.getBinding(catid);
let pathcat=bdcat &&bdcat.referencePaths
for(let i =0; i <pathcat.length; i++){
let repcat= pathcat[i].parentPath
}

到这一步就已经获取到了所有的参数,然后我们直接取值,替换即可

argsmcat=lACSb.$_Ce.call(null,repcat.node.arguments[0].value)
repcat.replaceWith(types.valueToNode(argsmcat));

20250918224742675-image

替换结果

我们到还原代码看一下

20250918224813659-image

还原完成

可以看到此时已经把字符还原的差不多了。

点击跳转:

Python爬虫进阶:普通验证码识别训练入门之手势验证码(二)

🎀 🌸

📜 重要提示:
如有解压密码:看下载页、看下载页、看下载页。
源码工具资源类具有可复制性: 建议具有一定思考和动手能力的用户购买。
请谨慎考虑: 小白用户和缺乏思考动手能力者不建议赞助。
虚拟商品购买须知: 虚拟类商品,一经打赏赞助,不支持退款。请谅解,谢谢合作!
邻兔跃官网:lt.lintuyue.com(如有解压密码看下载页说明)。

文章版权声明 1、本网站名称:邻兔跃lT
2、本站永久网址:https://lt.lintuyue.com/
3、本站内容主要来源于互联网优质资源整合、网友积极投稿以及部分原创内容,仅供内部学习研究软件设计思想和原理使用,学习研究后请自觉删除,请勿传播,因未及时删除所造成的任何后果责任自负,如有侵权,请联系站长进行删除处理。
4、本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
5、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
6、本站资源大多存储在云盘,如发现链接失效,请联系我们我们会第一时间更新。
© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容