声明
本文写于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
。
代码结构
可以看到,代码由四个函数和一个自执行大数组组成。
通过观察如下代码,可以看到其中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 ={});
这两个通用的可以还原如下编码,使代码清晰
接下来还原字符混淆
四、混淆字符还原。
我们把代码复制到ast的解析站https://astexplorer.net/
可以看到var $_DAGJB = lACSb.$_Ce
的type
是VariableDeclaration
,因此,我们需要先过滤VariableDeclaration
traverse(ast,{
VariableDeclarator(path){
})
我们需要先找到lACSb.$_Ce
代码。可以看到是在init
里面,类型是MemberExpression
lACSb.$_Ce
所以我们先判断type
类型是MemberExpression
的在放行
if(types.isMemberExpression(path.node.init)){
}
然后我们在看一下init
的代码,用generator.code
let namecode=generator(path.node.init).code;
console.log(namecode);
可以看到筛选出来了一堆,我们直接namecode==="lACSb.$_Ce"
判断过滤
接下来我们需要找到赋值的地方,可以看到赋值时id的name参数
我们直接取path.node.init.name
,可以看到结果是正确的
name参数
接下来我们找这个参数的绑定,取他的path
作用域
我们定义两个值,并循环取到的path
,在取path
的parent
,顺便打印一下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));
此时我们第一种就还原完成
接下来我们看第二种
我们先前判断了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));
我们到还原代码看一下
可以看到此时已经把字符还原的差不多了。
点击跳转:
暂无评论内容