网站Logo 小李的博客

《国产5s盾阉割版》解混淆

xiaoli
18
2025-07-16

《国产5s盾阉割版》解混淆

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

混淆分析

拿到代码后首先分析代码整体结构,可以看出,整体代码分为三个部分;第一:加密数组(也就是第一个函数,数组太大了),第二:解密函数(也就是第二个函数),第三:逻辑代码。然后映入眼帘的就是满屏的16进制数字,以及从对象中获取变量的操作。那么本次的目的就是尽可能的解除混淆,减少分析的难度。

image-20250716212514522

第一步:16进制数字转换

思路:匹配符合十六进制的数字节点,使用其value替换当前节点

const fs = require('fs');
const babel = require('@babel/core');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const targetFilePath = './test/ast/ctct_bundle.js';
const code = fs.readFileSync(targetFilePath, 'utf-8');
const t = require('@babel/types')

const ast = parser.parse(code, {
    sourceType: 'script',
});
// 16进制数字转换
traverse(ast, {
    NumericLiteral(path) {
        if (path.node.extra && path.node.extra.raw && path.node.extra.raw.startsWith('0x')) {
            const newNode = t.numericLiteral(path.node.value);
            delete newNode.extra;
            path.replaceWith(newNode);
        }
    }
});
const output = generate(ast, {}, code);
const outputFilePath = './test/ast/ctct_bundle.js';
fs.writeFileSync(outputFilePath, output.code, 'utf-8');
console.log('解混淆完成,结果已保存到:', outputFilePath);

运行后效果如下:可以看到原本的十六进制的数字已经被转换为十进制了

image-20250716213426913

第二步:对象中的常量获取

在当前的代码中,可以看到存在很多从某一个对象中某一个属性获取值的代码。比如:

image-20250716213619607

image-20250716213628045

打开目标对象可以到,该对象中的所有值全部都是数字常量,那么第二步就是将 _0x55aec1._0x4daf70 这种代码替换为具体的值

思路:首先获取全部属性都为数字的对象,保存下来,然后在遍历所有的 MemberExpression 节点,如果被调用的对象存在我们保存的对象中的话,就对该表达式进行取值替换

const fs = require('fs');
const babel = require('@babel/core');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const targetFilePath = './test/ast/ctct_bundle.js';
const code = fs.readFileSync(targetFilePath, 'utf-8');
const t = require('@babel/types')

const ast = parser.parse(code, {
    sourceType: 'script',
});
// 存储符合的值
var numericObjects = {};
traverse(ast, {
    // 16进制数字转换
    NumericLiteral(path) {
        if (path.node.extra && path.node.extra.raw && path.node.extra.raw.startsWith('0x')) {
            const newNode = t.numericLiteral(path.node.value);
            delete newNode.extra;
            path.replaceWith(newNode);
        }
    },
    // 获取全部属性都为数字的对象
    VariableDeclarator(path) {
        if (path.node.init && path.node.init.type === 'ObjectExpression') {
            let allNumeric = true;
            let objMap = {};
            for (let prop of path.node.init.properties) {
                if (prop.type === 'ObjectProperty' && prop.value.type === 'NumericLiteral') {
                    let propName = prop.key.name || prop.key.value;
                    objMap[propName] = prop.value.value;
                } else {
                    allNumeric = false;
                    break;
                }
            }
            if (allNumeric && Object.keys(objMap).length > 0) {
                let objName = path.node.id.name;
                numericObjects[objName] = objMap;
            }
        }
    }
});

traverse(ast, {
    // 匹配所有的成员表达式进行替换
    MemberExpression(path) {
        if (path.node.object.type === 'Identifier' &&
            path.node.property.type === 'Identifier' &&
            numericObjects[path.node.object.name]) {
            let objName = path.node.object.name;
            let propName = path.node.property.name;
            if (numericObjects[objName][propName] !== undefined) {
                let value = numericObjects[objName][propName];
                path.replaceWith(t.numericLiteral(value));
            }
        }
    },
    // 匹配所有的一元表达式进行替换(用于处理 -_0x55aec1._0x4daf70 这种情况)
    UnaryExpression(path) {
        if (path.node.operator === '-' && path.node.argument.type === 'MemberExpression') {
            let memberExp = path.node.argument;
            if (memberExp.object.type === 'Identifier' &&
                memberExp.property.type === 'Identifier' &&
                numericObjects[memberExp.object.name]) {
                let objName = memberExp.object.name;
                let propName = memberExp.property.name;
                if (numericObjects[objName][propName] !== undefined) {
                    let value = -numericObjects[objName][propName];
                    path.replaceWith(t.numericLiteral(value));
                }
            }
        }
    },
    // 删除无用的对象节点
    VariableDeclarator(path) {
        if (path.node.id.type === 'Identifier' &&
            numericObjects[path.node.id.name]) {
            if (path.parent.declarations.length === 1) {
                path.parentPath.remove();
            } else {
                path.remove();
            }
        }
    },
    // 匹配所有的一元表达式进行替换(用于处理 !! 这种情况)
    UnaryExpression(path) {
        if (path.node.operator === '-' || path.node.operator === '+' || path.node.operator === '!') {
            const result = path.evaluate();
            if (result.confident) {
                path.replaceWith(createLiteralNode(result.value));
            }
        }
    }
});
// 根据值的类型生成不同的节点,用于替换目标节点
function createLiteralNode(value) {
    if (typeof value === 'number') {
        return t.numericLiteral(value);
    } else if (typeof value === 'boolean') {
        return t.booleanLiteral(value);
    } else if (typeof value === 'string') {
        return t.stringLiteral(value);
    } else if (value === null) {
        return t.nullLiteral();
    } else if (value === undefined) {
        return t.identifier('undefined');
    } else {
        return t.valueToNode(value);
    }
}


const output = generate(ast, {}, code);
const outputFilePath = './test/ast/ctct_bundle.js';
fs.writeFileSync(outputFilePath, output.code, 'utf-8');
console.log('解混淆完成,结果已保存到:', outputFilePath);

运行效果如下:

image-20250716214601901

第三步:表达式静态执行

随着上面的混淆解除完之后呢,可以看到代码中存在了很多纯数字的表达式,比如:(8264 * -1 + -1 * 3148 + 11413) 这种,那么这样是不是可以将运行的值替换这个节点呢?

思路:匹配所有的BinaryExpression表达式,利用babel库自带的静态执行方法获取结果然后进行替换

const fs = require('fs');
const babel = require('@babel/core');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const targetFilePath = './test/ast/ctct_bundle.js';
const code = fs.readFileSync(targetFilePath, 'utf-8');
const t = require('@babel/types')

const ast = parser.parse(code, {
    sourceType: 'script',
});
// 存储符合的值
var numericObjects = {};
traverse(ast, {
    // 16进制数字转换
    NumericLiteral(path) {
        if (path.node.extra && path.node.extra.raw && path.node.extra.raw.startsWith('0x')) {
            const newNode = t.numericLiteral(path.node.value);
            delete newNode.extra;
            path.replaceWith(newNode);
        }
    },
    // 获取全部属性都为数字的对象
    VariableDeclarator(path) {
        if (path.node.init && path.node.init.type === 'ObjectExpression') {
            let allNumeric = true;
            let objMap = {};
            for (let prop of path.node.init.properties) {
                if (prop.type === 'ObjectProperty' && prop.value.type === 'NumericLiteral') {
                    let propName = prop.key.name || prop.key.value;
                    objMap[propName] = prop.value.value;
                } else {
                    allNumeric = false;
                    break;
                }
            }
            if (allNumeric && Object.keys(objMap).length > 0) {
                let objName = path.node.id.name;
                numericObjects[objName] = objMap;
            }
        }
    }
});

traverse(ast, {
    // 匹配所有的成员表达式进行替换
    MemberExpression(path) {
        if (path.node.object.type === 'Identifier' &&
            path.node.property.type === 'Identifier' &&
            numericObjects[path.node.object.name]) {
            let objName = path.node.object.name;
            let propName = path.node.property.name;
            if (numericObjects[objName][propName] !== undefined) {
                let value = numericObjects[objName][propName];
                path.replaceWith(t.numericLiteral(value));
            }
        }
    },
    // 匹配所有的一元表达式进行替换(用于处理 -_0x55aec1._0x4daf70 这种情况)
    UnaryExpression(path) {
        if (path.node.operator === '-' && path.node.argument.type === 'MemberExpression') {
            let memberExp = path.node.argument;
            if (memberExp.object.type === 'Identifier' &&
                memberExp.property.type === 'Identifier' &&
                numericObjects[memberExp.object.name]) {
                let objName = memberExp.object.name;
                let propName = memberExp.property.name;
                if (numericObjects[objName][propName] !== undefined) {
                    let value = -numericObjects[objName][propName];
                    path.replaceWith(t.numericLiteral(value));
                }
            }
        }
    },
    // 删除无用的对象节点
    VariableDeclarator(path) {
        if (path.node.id.type === 'Identifier' &&
            numericObjects[path.node.id.name]) {
            if (path.parent.declarations.length === 1) {
                path.parentPath.remove();
            } else {
                path.remove();
            }
        }
    },
    // 匹配所有的一元表达式进行替换(用于处理 !! 这种情况)
    UnaryExpression(path) {
        if (path.node.operator === '-' || path.node.operator === '+' || path.node.operator === '!') {
            const result = path.evaluate();
            if (result.confident) {
                path.replaceWith(createLiteralNode(result.value));
            }
        }
    },
    // 匹配所有的 带有二进制表达式的节点,静态执行进行替换
    BinaryExpression(path) {
        const result = path.evaluate();
        if (result.confident) {
            path.replaceWith(createLiteralNode(result.value));
        }
    }
});
// 根据值的类型生成不同的节点,用于替换目标节点
function createLiteralNode(value) {
    if (typeof value === 'number') {
        return t.numericLiteral(value);
    } else if (typeof value === 'boolean') {
        return t.booleanLiteral(value);
    } else if (typeof value === 'string') {
        return t.stringLiteral(value);
    } else if (value === null) {
        return t.nullLiteral();
    } else if (value === undefined) {
        return t.identifier('undefined');
    } else {
        return t.valueToNode(value);
    }
}


const output = generate(ast, {}, code);
const outputFilePath = './test/ast/ctct_bundle.js';
fs.writeFileSync(outputFilePath, output.code, 'utf-8');
console.log('解混淆完成,结果已保存到:', outputFilePath);

运行效果如下:

image-20250716215107787

第四步:字符串解混淆

分析当前的代码可以看出,_0x4141就是解密函数,在源码中又存在很多对该函数的二次封装,并且对传入参数进行了处理。那么我们就需要拿到整个解密函数,方便后续调用。然后,我们需要匹配出所有直接调用了解密函数或者间接调用了解密函数(多层封装)的函数,并且将这些函数保存下来,然后在遍历所有的调用函数的节点,匹配是否在我们保存的函数列表内,如果在,我们则需要拿到第一层传入的参数,然后进行递归运算(根据每一层往下调用的表达式)直到最后一层(也就是具体的解密函数)。

image-20250716215206770

举例说明:

  function B(_0x48fb81, _0x589fcc, _0x5f448f, _0x2b771d) {
    return _0x4141(_0x5f448f - -326, _0x589fcc);
  }
  // 模拟的多层封装函数
  function A(_0x48fb81, _0x589fcc, _0x5f448f, _0x2b771d) {
    return B(_0x48fb81 + 100, _0x589fcc, _0x5f448f, _0x2b771d);
  }

// 模拟调用
A(1,2,3,4)

// 从上诉的例子来说,我们需要拿到第一层传入的参数(1,2,3,4),然后根据A函数中调用B函数时对参数的处理(第一个参数 + 100)进行运算,得出调用B函数时的参数(101,2,3,4),在根据B函数调用解密函数时对参数的处理(第三个参数 - -326,并且只传入第三个参数和第二个参数,保证顺序),得出调用解密函数时的参数为(329,2)
const fs = require('fs');
const babel = require('@babel/core');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const targetFilePath = './test/ast/xxx.js'; // 经过上三步代码还原后的代码
const code = fs.readFileSync(targetFilePath, 'utf-8');
const t = require('@babel/types')

const ast = parser.parse(code, {
    sourceType: 'script',
});

// 解密用到的数组
function _0x4bf8(){}
// 解密函数
function _0x4141(){}
// 重排操作
(function (_0x48ed35, _0x2eb7f1) {
}(_0x4bf8, 0x1d662 * -0xb + 0x4 * 0x1a6 + 0x20ba05))

// 所有的匹配的解密函数(多层嵌套的)
var decodeFuns = {};
// 标记
const finalDecryptFunction = '_0x4141';

// 用于计算表达式值
function evaluateExpression(node, paramMap) {
    try {
        if (t.isIdentifier(node)) {
            if (paramMap.hasOwnProperty(node.name)) {
                return paramMap[node.name];
            }
            console.warn(`未找到标识符 ${node.name} 的值,使用默认值 0`);
            return 0;
        }
        
        if (t.isNumericLiteral(node)) {
            return node.value;
        }
        
        if (t.isStringLiteral(node)) {
            return node.value;
        }
        
        if (t.isBinaryExpression(node)) {
            const left = evaluateExpression(node.left, paramMap);
            const right = evaluateExpression(node.right, paramMap);
            
            if (left === undefined || right === undefined) {
                console.warn(`二元表达式 ${node.operator} 的操作数未定义: left=${left}, right=${right}`);
                return 0;
            }
            
            if (typeof left === 'number' && typeof right === 'number') {
                switch (node.operator) {
                    case '+': return left + right;
                    case '-': return left - right;
                    case '*': return left * right;
                    case '/': return Math.floor(left / right);
                    case '%': return left % right;
                    case '&': return left & right;
                    case '|': return left | right;
                    case '^': return left ^ right;
                    case '<<': return left << right;
                    case '>>': return left >> right;
                    case '>>>': return left >>> right;
                    default: 
                        console.warn(`不支持的二元操作符: ${node.operator}`);
                        return 0;
                }
            } else if (typeof left === 'string' || typeof right === 'string') {
                if (node.operator === '+') {
                    return String(left) + String(right);
                }
            }
            console.warn(`二元表达式 ${node.operator} 的操作数类型不受支持: left=${typeof left}, right=${typeof right}`);
            return 0;
        }
        
        if (t.isUnaryExpression(node)) {
            const argument = evaluateExpression(node.argument, paramMap);
            if (argument === undefined) {
                console.warn(`一元表达式 ${node.operator} 的操作数未定义`);
                return 0;
            }
            if (typeof argument === 'number') {
                switch (node.operator) {
                    case '-': return -argument;
                    case '+': return +argument;
                    case '~': return ~argument;
                    case '!': return !argument ? 1 : 0;
                    default: 
                        console.warn(`不支持的一元操作符: ${node.operator}`);
                        return 0;
                }
            }
            console.warn(`一元表达式 ${node.operator} 的操作数类型不受支持: ${typeof argument}`);
            return 0;
        }
        
        console.warn(`不支持的节点类型: ${node.type}`);
        return 0;
    } catch (error) {
        console.error(`计算表达式失败: ${error.message}`);
        return 0;
    }
}

// 递归调用解密函数(多层嵌套,A->B->C->解密函数)
function decryptRecursive(funcName, args) {
    if (funcName === finalDecryptFunction) {
        try {
            console.log(`调用最终解密函数: ${funcName}(${args.join(', ')})`);
            const decryptedValue = _0x4141(...args);
            console.log(`解密结果: ${decryptedValue}`);
            return {
                finalFunction: funcName,
                finalArgs: args,
                isDecrypted: true,
                decryptedValue: decryptedValue
            };
        } catch (error) {
            console.error(`执行最终解密函数 ${funcName} 失败: ${error.message}`);
            return null;
        }
    }
    
    if (!decodeFuns.hasOwnProperty(funcName)) {
        console.warn(`函数 ${funcName} 未在解密函数列表中`);
        return null;
    }
    
    const funcInfo = decodeFuns[funcName];
    
    const paramMap = {};
    funcInfo.currentArgs.forEach((paramName, index) => {
        paramMap[paramName] = args[index] !== undefined ? args[index] : 0;
        console.log(`参数映射: ${paramName} = ${paramMap[paramName]}`);
    });
    
    const nextArgs = funcInfo.calleeArgs.map(argNode => {
        const result = evaluateExpression(argNode, paramMap);
        console.log(`计算参数 ${JSON.stringify(argNode)} -> ${result}`);
        return result;
    });
    
    return decryptRecursive(funcInfo.callee, nextArgs);
}

// 第一次遍历,收集可能为解密函数的函数// 第二次遍历:替换函数调用
traverse(ast, {
    CallExpression(path) {
        if (path.node.callee.type === 'Identifier') {
            const funcName = path.node.callee.name;

            if (decodeFuns[funcName] || funcName === finalDecryptFunction) {
                try {
                    const args = path.node.arguments.map(arg => {
                        const result = evaluateExpression(arg, {});
                        console.log(`计算调用参数 ${JSON.stringify(arg)} -> ${result}`);
                        return result;
                    });

                    console.log(`处理调用: ${funcName}(${args.join(', ')})`);

                    const result = decryptRecursive(funcName, args);

                    if (result && result.isDecrypted && result.decryptedValue !== undefined) {
                        console.log(`解密成功: ${funcName}(${args.join(', ')}) -> ${result.decryptedValue}`);
                        path.replaceWith(t.stringLiteral(String(result.decryptedValue)));
                    } else {
                        console.warn(`解密失败: ${funcName}(${args.join(', ')})`);
                    }
                } catch (error) {
                    console.error(`处理调用 ${funcName} 出错: ${error.message}`);
                }
            }
        }
    }
});
// 第二次调用,调用解密函数替换节点
traverse(ast, {
    FunctionDeclaration(path) {
        const funcName = path.node.id.name;
        if (path.node.body && path.node.body.body.length === 1 &&
            path.node.body.body[0].type === 'ReturnStatement' &&
            path.node.body.body[0].argument.type === 'CallExpression') {
            try {
                let currentFuncArgNames = path.node.params.map(arg => arg.name);
                let calleeName = path.node.body.body[0].argument.callee.name;
                decodeFuns[funcName] = {
                    node: path.node,
                    currentArgs: currentFuncArgNames,
                    callee: calleeName,
                    calleeArgs: path.node.body.body[0].argument.arguments
                };
                console.log(`收集解密函数: ${funcName} -> 调用 ${calleeName}(${currentFuncArgNames.join(', ')})`);
            } catch (error) {
                console.error(`处理函数声明 ${funcName} 出错: ${error.message}`);
            }
        }
    }
});


const output = generate(ast, {}, code);
const outputFilePath = './test/电信盾/ast/blog/2.js';
fs.writeFileSync(outputFilePath, output.code, 'utf-8');
console.log('解混淆完成,结果已保存到:', outputFilePath);

运行结果如下:

image-20250716220615488

可以看到,解除混淆后,可以大大减少分析的难度。

特此声明:本人纯小白,写的代码不好(或许本来就是错的)😂😂,只是分享一下学习心得。OB混淆可以直接使用真哥发的网站,效果比这个好太多了!!非常推荐