吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5427|回复: 27
收起左侧

[Web逆向] 某招投标服务平台AST详细解析

  [复制链接]
mysticz 发表于 2025-9-27 11:06

一、前言

网站:

aHR0cHM6Ly9idWxsZXRpbi5jZWJwdWJzZXJ2aWNlLmNvbS94eGZiY21zZXMvc2VhcmNoL2J1bGxldGluLmh0bWw/ZGF0ZXM9MzAwJmNhdGVnb3J5SWQ9ODgmcGFnZT0xJnNob3dTdGF0dXM9MSZ0aW1lX18yNjUyPW40angyRGNEQmpEJTNEJTNEREtEc1RxQklLN0kxS09XRFJtWU9ZRA==

适合AST学习

二、定位参数

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

1.png

不带这个参数的话会返回一个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参数,但是混淆的太多,不利于我们扣代码。因此需要先解混淆

三、还原数字数组

我们先看一下代码结构

2.png

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

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

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

3.png

因此先用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

即如下函数

4.png

解析如下

5.png

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

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

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

6.png

我们取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])
 }

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

7.png

四、还原字符串数组

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

8.png

可以看到如图,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

9.png

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))

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

10.png

五、还原字符串大数组

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

11.png

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

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两个类型。因此我们要分开处理

12.png

继续在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)
        }
    }
}

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

13.png

六、参数time__2652生成

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

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

14.png

只有46行

总结

网站难度不高,就是嵌套的很繁琐。有兴趣的朋友可以试试。

免费评分

参与人数 12威望 +2 吾爱币 +112 热心值 +11 收起 理由
正哥哥 + 1 + 1 谢谢@Thanks!
lwlw2262 + 1 + 1 我很赞同!
daxz + 1 + 1 谢谢@Thanks!
walkingyu + 1 用心讨论,共获提升!
aisrs + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
fengbolee + 2 + 1 用心讨论,共获提升!
shinmei + 1 + 1 用心讨论,共获提升!
Karry5 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
allspark + 1 + 1 用心讨论,共获提升!
涛之雨 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
buluo533 + 1 + 1 用心讨论,共获提升!
BayMaxen + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

lwlw2262 发表于 2025-9-30 08:18
本帖最后由 lwlw2262 于 2025-9-30 08:20 编辑

大佬,我想问一下,这个网站的网址你用的什么加密呀?
我因为工作的关系,需要在类似网站抓取信息,我想向你学习!
buluo533 发表于 2025-9-27 12:50
夭夭灵 发表于 2025-9-27 19:00
头像被屏蔽
aaa74124 发表于 2025-9-27 23:50
提示: 作者被禁止或删除 内容自动屏蔽
whyzl20 发表于 2025-9-28 09:29
不明觉厉。楼主厉害。膜拜大佬。
qq6667298 发表于 2025-9-28 10:45
看看楼主厉害。膜拜大佬。
冥界3大法王 发表于 2025-9-28 12:08
本帖最后由 冥界3大法王 于 2025-9-28 15:34 编辑


感谢分享,学习下。
行则将至 发表于 2025-9-28 14:43
学到了,感谢楼主,回去研究研究
zdmin 发表于 2025-9-28 21:56
看看楼主厉害。膜拜大佬。
cick 发表于 2025-9-29 09:24
这玩意跟看天书一样,抓取招投标数据 自己看有难度,还是得多看多学
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-2-17 13:43

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表