《国产5s盾阉割版》解混淆
提示:仅供学习交流,不得用做商业交易,如有侵权请及时联系作者删除
混淆分析
拿到代码后首先分析代码整体结构,可以看出,整体代码分为三个部分;第一:加密数组(也就是第一个函数,数组太大了),第二:解密函数(也就是第二个函数),第三:逻辑代码。然后映入眼帘的就是满屏的16进制数字,以及从对象中获取变量的操作。那么本次的目的就是尽可能的解除混淆,减少分析的难度。
第一步: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);
运行后效果如下:可以看到原本的十六进制的数字已经被转换为十进制了
第二步:对象中的常量获取
在当前的代码中,可以看到存在很多从某一个对象中某一个属性获取值的代码。比如:
打开目标对象可以到,该对象中的所有值全部都是数字常量,那么第二步就是将 _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);
运行效果如下:
第三步:表达式静态执行
随着上面的混淆解除完之后呢,可以看到代码中存在了很多纯数字的表达式,比如:(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);
运行效果如下:
第四步:字符串解混淆
分析当前的代码可以看出,_0x4141就是解密函数,在源码中又存在很多对该函数的二次封装,并且对传入参数进行了处理。那么我们就需要拿到整个解密函数,方便后续调用。然后,我们需要匹配出所有直接调用了解密函数或者间接调用了解密函数(多层封装)的函数,并且将这些函数保存下来,然后在遍历所有的调用函数的节点,匹配是否在我们保存的函数列表内,如果在,我们则需要拿到第一层传入的参数,然后进行递归运算(根据每一层往下调用的表达式)直到最后一层(也就是具体的解密函数)。
举例说明:
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);
运行结果如下:
可以看到,解除混淆后,可以大大减少分析的难度。
特此声明:本人纯小白,写的代码不好(或许本来就是错的)😂😂,只是分享一下学习心得。OB混淆可以直接使用真哥发的网站,效果比这个好太多了!!非常推荐