网站Logo 小李的博客

Reese84解混淆及流程分析(一)

xiaoli
1
2025-08-22

Reese84解混淆及流程分析(一)

提示:仅供学习交流,不得用做商业交易,如有侵权请及时联系作者删除

解混淆

  • 先把整体代码拉下来,做大致分析,可以根据折叠后的代码可以看出大致分为两个部分,第一个部分的混淆存在大量的substr函数,而第二部分的就是标准的OB混淆了;

image-20250822195104684

第一部分

image-20250822195138825

image-20250822195123362

可以看到,大致的流程就是经过上面几轮对长字符串解码和特殊计算后,在下面需要混淆的地方使用substr的方式进行操作的,那么思路就很明显了,从头找到第一次使用substr的地方,将这段代码复制下来,然后直接运行就可以了,配合AST,匹配所有substr的函数调用,拿到入参后进行模拟调用还原替换,代码如下:

image-20250822195509799

traverse(ast, {
    CallExpression(path) {
        const { callee, arguments } = path.node;
        if (t.isMemberExpression(callee) && t.isIdentifier(callee.object) && callee.property.name == "substr") {
            let strName = callee.object.name;
            let left = arguments[0].value;
            let right = arguments[1].value;

            // 优先从已执行的 VM sandbox 中取值,避免直接 eval 丢失上下文
            if (Object.prototype.hasOwnProperty.call(sandbox, strName)) {
                const recv = sandbox[strName];
                if (typeof recv === "string") {
                    const result = recv.substr(left, right);
                    console.log("substr =>", strName, left, right, "=", result)
                    path.replaceWith(t.valueToNode(result));
                }
            }
        }
    }
})

运行后输出

image-20250822195555105

第二部分

由于第二部分就是标准的OB混淆,所以直接把解密函数拿下来匹配运行就行了,很简单

image-20250822195701037

var decodeFuncs = new Set();
traverse(ast, {
    VariableDeclarator(path) {
        const { id, init } = path.node;
        if (init) {
            if (init.name === "a1_0xcf60" || decodeFuncs.has(init.name)) {
                decodeFuncs.add(id.name);
            }
        }

    }
})

traverse(ast, {
    CallExpression(path) {
        const { callee, arguments } = path.node;
        if (decodeFuncs.has(callee.name) && arguments.length == 1) {
            var result = a1_0xcf60(arguments[0].value)
            console.log(arguments[0].value, result)
            path.replaceWith(t.valueToNode(result));
        }
    }
})

最后在进行一些字符串的拼接,方便分析

 function foldStringConcat(path) {
    if (!t.isBinaryExpression(path.node, { operator: "+" })) return;

    // 递归收集所有叶子节点
    const parts = [];

    function flatten(node) {
        if (t.isBinaryExpression(node, { operator: "+" })) {
            flatten(node.left);
            flatten(node.right);
        } else {
            parts.push(node);
        }
    }

    flatten(path.node);

    // 如果所有部分都是字符串常量,且不包含潜在转义风险(如反斜杠),则进行合并
    const areAllStrings = parts.every(node => t.isStringLiteral(node));
    if (areAllStrings) {
        // 仅在所有字面量的值中不包含反斜杠时合并,避免将 '\\u'、'\\x' 之类片段拼成无效转义
        const hasBackslash = parts.some(node => typeof node.value === "string" && /\\/.test(node.value));
        if (!hasBackslash) {
            const result = parts.map(p => p.value).join("");
            path.replaceWith(t.stringLiteral(result));
        }
    }
}
traverse(ast, {
    BinaryExpression(path) {
        foldStringConcat(path);
    }
})

效果展示

image-20250822200306348

image-20250822200318153

流程分析

在请求体中我们需要分析的加密值就是P值,解完混淆后可以直接搜到

image-20250822200515384

image-20250822200538788

具体的加密函数就是第一部分最后一个函数,这个就不分析了,抓个包看堆栈跟两步就出来了,这里直接分析一下加密流程

image-20250822200742587

展开U8函数,我们发现前面是一些变量及常量定义,下面是往一个数组中添加了很多函数,在最后一个添加的函数中生成了P参数

image-20250822200854506

image-20250822200906781

那么加密流程其实就在push的函数里,一共是二十个左右(不同的站点会存在不同),下面开始分析这些函数

加密函数分析示例

image-20250822201417813

根据上图可以看出,最终的P值其实就是 by经过一系列的计算后得出,by其实就是一个数据,里面存放了各种加密后的指纹,而by就是在push的函数中一点一点添加的

image-20250822201549631

可以看到WA就是qB,而qB的值就是在中间的代码中添加的,那么只需要分析中间的代码即可

image-20250822201720562

具体加密示例

从下图可以看出,qB的值部分来源于JL,而JL往上跟就是Xe经过计算的值,Xe其实就是收集的指纹对象了,那么从JSON.stringify这里开始,到JL=fi这里就是加密流程了

image-20250822201900211

我们把加密流程单独拿出来后,发现其实就是先是经过一个函数生成了一串类似密钥种子的东西,然后在进行指定长度密钥的生成,然后就是数值计算和fromcharcode,base64编码了,那么尝试抠代码,就变成了下面的样子

window = globalThis
var Xe = {
    
}
var S0 = window["Math"]["random"]() * 1073741824 | 0;
var qw = new window["RegExp"]("Trident");
var Jg = new window.RegExp("[\\u007F-\\uFFFF]", "g");
function No(kA) {
    return "\\u" + ("0000" + kA.charCodeAt(0).toString(16)).substr(-4)
}
function jb(VK, yf) {
    var gF = VK;
    var KL = yf;
    return function () {
        var y_ = gF;
        y_ ^= y_ << 23;
        y_ ^= y_ >> 17;
        var l3 = KL;
        y_ ^= l3;
        y_ ^= l3 >> 26;
        KL = y_;
        gF = l3;
        return (gF + KL) % 4294967296
    }
}
var p1 = jb(1650762707, S0);
var Wd = [];
var IA = 0;
while (IA < 3) {
    Wd.push(p1() & 255);
    IA += 1
}
var H1 = Wd;
var qk = H1;
var ca = window.JSON.stringify(Xe, function (nW, tk) {
    return tk === undefined ? null : tk
});
var Yw = ca.replace(Jg, No);
var Tx = [];
var FN = 0;
while (FN < Yw.length) {
    Tx.push(Yw.charCodeAt(FN));
    FN += 1
}
var G4 = Tx;
var un = G4;
var gH = un.length;
var Iv = qk[0] % 7 + 1;
var uH = [];
var tD = 0;
while (tD < gH) {
    uH.push((un[tD] << Iv | un[tD] >> 8 - Iv) & 255);
    tD += 1
}
var rH = uH;
var cu = rH.length;
var N3 = [];
var NZ = 0;
while (NZ < cu) {
    N3.push(rH[(NZ + qk[1]) % cu]);
    NZ += 1
}
var zU = N3;
var jL = [];
for (var nl in zU) {
    var QK = zU[nl];
    if (zU.hasOwnProperty(nl)) {
        var ul = window.String.fromCharCode(QK);
        jL.push(ul)
    }
}
var fi = window.btoa(jL.join(""));
var JL = fi;
console.log(JL)

image-20250822205439370

后续的流程就是将每个函数抠出来即可,在JSON.stringify的调用地方打上断点看指纹信息是否需要随机或者固定即可。解完混淆后很简单,只是需要很多耐心抠代码。川航的这个还没做完,之前做的另外一个站的已经做完了,也是成功返回token,问了真哥后发现返回的token不一定是能用的,priceline这个站我又没找到校验接口😂😂所以开始搞了真哥推荐的川航。SO:以上流程只是我个人意见,不代表一定能用,也可能就是错的,只是记录下学习心得!!

image-20250822205802103

image-20250822205959260