某招投标服务平台AST详解

声明

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

一、前言

网站:

aHR0cHM6Ly9idWxsZXRpbi5jZWJwdWJzZXJ2aWNlLmNvbS94eGZiY21zZXMvc2VhcmNoL2J1bGxldGluLmh0bWw/ZGF0ZXM9MzAwJmNhdGVnb3J5SWQ9ODgmcGFnZT0xJnNob3dTdGF0dXM9MSZ0aW1lX18yNjUyPW40angyRGNEQmpEJTNEJTNEREtEc1RxQklLN0kxS09XRFJtWU9ZRA==

适合AST学习

二、定位参数

抓包可以看到time__2652参数是加密的,每一页都是不同的

20250918225421634-image

time__2652

不带这个参数的话会返回一个ob混淆的js。

通过调用栈可以定位到核心代码

V = m[lK(GF.m)](m[lK(GF.G)](m[lK(GF.k)](m[lK(GF.Q)](m[lK(GF.t)](L[lK(GF.b)](O), '|'), m[lK(GF.Y)](r)), '|'), new Date()[lK(GF.u)]()), '|1')
O = F['ua'](V, !(-0x1c67 + 0x544 + 0x1723))

O即为time__2652参数,但是混淆的太多,不利于我们扣代码。因此需要先解混淆

三、还原数字数组

我们先看一下代码结构

20250918225515590-image

代码结构

可以看到是由一个字符串大数组和许多十六进制小数组组成。

在加密代码中m[lK(GF.m)],由最里面的两个小数组在嵌套外面的大数组组成。因此我们先要还原lK(GF.m)两个十六进制小数组。

通过解析,可以看到都是VariableDeclarator类型初始化是ObjectExpression

20250918225549730-image

解析

因此先用type类型判断下

traverse(ast, {
    VariableDeclarator(path) {
        if (types.isObjectExpression(path.node.init)) {
        }
    }});

接着取id里面的name参数就是赋值参数,然后查找参数的绑定函数

let bingname = path.scope.getBinding(path.node.id.name);
let arrbd = bingname.referencePaths;
for (let i = 0; i < arrbd.length; i++) {
}

接下来找绑定函数的父对象。

arrbd[i].parentPath

20250918225653457-image

函数

解析如下

20250918225722225-image

解析

接下来判断下类型是否是MemberExpression,并取其中的name参数

if (mempath.isMemberExpression()) {
    let propname = mempath.node.property.name
}

我们现在有了值,接下来返回前面把数组我们保存下来

20250918225841641-image

数组

我们取init里面的properties,把keyvalue保存到一个数组里面。

let props = path.node.init.properties;
let obj = {};
for (let i = 0; i < props.length; i++) {
    obj[props[i].key.name] = props[i].value;
}

然后在下面匹配这个数组,并替换。

if (types.isNumericLiteral(obj[propname])) {
     mempath.replaceWith(obj[propname])
 }

可以看到还原后的代码已经变灰,没有调用了.

20250918225939860-image

还原后

四、还原字符串数组

接下来我们还原第二层,字符串的数组还原

20250918230011227-image

字符串的数组

可以看到如图,e赋值到l0调用。而e调用了M和e函数进行偏移解密。

先判断类型,这时我们要限定一下name是e

traverse(ast, {
    VariableDeclarator(path) {
        if (types.isIdentifier(path.node.init, {name: 'e'})) {
        }
    }
});

然后继续找他的绑定函数

let bingname = mempath.scope.getBinding(mempath.node.id.name);
let arrbd = bingname.referencePaths;
for (let i = 0; i < arrbd.length; i++) {
    let mempath = arrbd[i].parentPath;
}

接下来判断下是否是CallExpression类型,并取arguments

20250918230102503-image

CallExpression

if (mempath.isCallExpression()) {
    let argu = mempath.get('arguments') + '';
}

我们把e函数剪切出来放到最上面,并且把检测函数去掉。

b['prototype']['LdIFdg'] = function() {
    var Y = new RegExp(this['JbuAMo'] + this['DNmxyA'])
        , u =Y['test'](this['UULPjk']['toString']())?--this['OEswPC'][0x75d*0x5+-0x17ee+-0xce2]:--this['OEswPC'][0x26fb+0x191*-0x10+-0xdeb];
    return this['MxGFtc'](u);
}
//改为
b['prototype']['LdIFdg'] = function() {
var Y = new RegExp(this['JbuAMo'] + this['DNmxyA'])
    , u = --this['OEswPC'][0x75d * 0x5 + -0x17ee + -0xce2];
return this['MxGFtc'](u);
}

然后直接调用替换即可

let vl = e(argu)
mempath.replaceWith(types.stringLiteral(vl))

最后可以看到混淆代码嵌套调用,因此要进行判断循环

20250918230219363-image

嵌套

五、还原字符串大数组

接下来剩最后一个m数组。因为包含字符串和函数,我们最后处理。

20250918230244993-image

m数组

跟上面一样,先判断类型,在查找绑定,最后写入数组进行查询替换。

VariableDeclarator(path) {
    if (types.isObjectExpression(path.node.init)) {
        let props = path.node.init.properties;
        let obj = {};
        for (let i = 0; i < props.length; i++) {
            obj[props[i].key.value] = props[i].value;
        }
        let bingname = path.scope.getBinding(path.node.id.name);
        let arrbd = bingname.referencePaths.reverse();
        for (let i = 0; i < arrbd.length; i++) {
            let mempath = arrbd[i].parentPath;
            if(mempath.isMemberExpression()){
                let arg=mempath.node.property.value
                if(types.isStringLiteral(obj[arg])){
                    mempath.replaceWith(obj[arg])
                }
            }
        }
    }
}

因为这个数组的FunctionExpression类型下有CallExpressionBinaryExpression两个类型。因此我们要分开处理

20250918230337177-5c497b7365e9a8e7293fa8d547cd937e

两种调用

继续在if(types.isStringLiteral(obj[arg]))下写判断。

我们先判断是否是函数类型。接着取函数的body。然后对CallExpressionBinaryExpression两个类型进行判断。

当为CallExpression类型时取参数和调用,写入到原来的path替换

当为BinaryExpression类型时取left,right和operator,写入到原来的path替换

else if(types.isFunctionExpression(obj[arg])){
    let bd=obj[arg].body.body
    if(bd.length===1&&types.isReturnStatement(bd[0])){
        let argu=bd[0].argument;
        if(types.isCallExpression(argu)){
            let argus=mempath.parent.arguments;
            let calleename=argus[0];
            let newcall=types.callExpression(calleename,argus.slice(1))
            mempath.parentPath.replaceWith(newcall)
        }else if(types.isBinaryExpression(argu)){
            let argus=mempath.parent.arguments;
            let left=argus[0];
            let right=argus[1];
            let newcall=types.binaryExpression(argu.operator,left,right)
            mempath.parentPath.replaceWith(newcall)
        }
    }
}

至此我们就完成了大部分还原

20250918230423667-image

还原

六、参数time__2652生成

还原后我们就可以很明显看到核心代码了

let data=sig(O) + '|' + r() + '|' + new Date()["getTime"]() + '|1'
console.log(ua(data))

20250918230512412-image

核心代码

只有46行

总结

网站难度不高。有兴趣的朋友可以试试。

github:

https://github.com/jwmycz/WxArticleCode

🎀 🌸

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

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

请登录后发表评论

    暂无评论内容