使用模式匹配还原到包含continue的循环结构就不是很好处理了,例如如下的结构:
//444 21
//2 5 9 12 16 21
let p32 = 1,stateVal = 0,p33,p43,p45,p47,p14,p34,p46;
while (1) {
p43 = p32 < 5;
p33 = 1;
while (1) {
//123
p45 = p33 < 5;
stateVal += p32 + p33;
console.log(stateVal);
if (p32 === 1 && p33 === 2) {
p47 = (p33);
++p33;
continue;
}
//133
if (p32 === 1 && p33 === 3) {
p45 = (p32);
++p32;
break;
}
//546
if (p32 === 2 && p33 === 2) {
p45 = (p32);
++p32;
p45 = (p32);
++p32;
break;
}
//514
if (p32 === 4 && p33 === 1) {
debugger;
p14 = 2;
p34 = 1;
while (1) {
p43 = p34 <= 9;
if (!p43) {
break;
}
// p45 = p0["console"][("log")];
// p47 = "请关闭开发者工具" + p34;
// p46 = (p3(p45, p0["console"])(p47));
console.log('请关闭开发者工具',p34);
p45 = (p34);
++p34;
p6 = 508;
}
// p43 = (p9.ࢭﹱ(p12));
// p9.ﱡיּ[("value")] = p43;
debugger;
}
p47 = (p33);
++p33;
}
}
.png%3Ftable%3Dblock%26id%3D2332fd37-e2b0-80c2-b2df-fb3761703caf%26spaceId%3D25900039-5d85-4396-968b-a28f366f1763%26expirationTimestamp%3D1761127200000%26signature%3DTsa8NJmoYCvZ1qwMIkXNdyTjkxri4JM-MBKuM_GBhpo?table=block&id=2332fd37-e2b0-80c2-b2df-fb3761703caf&cache=v2)
这种情况可能就要考虑用图的方式来分析了,通过支配节点的分析来确定图里的循环,再通过相关信息确定循环的回边/起始点/包含点。
可以看出来这段代码,即使使用常规的节点模拟也有点麻烦,如果不清楚是否处于循环中,就没办法提前在循环内部缩点,因为内部包含若干break/continue,如果无法完成内部缩点的话,循环也没办法成功缩点,就会导致这种情况。
如果使用模式匹配,需要多加一层模式识别,以确认
考虑如下一个图:
.png%3Ftable%3Dblock%26id%3D2332fd37-e2b0-800e-820d-f2343b4f3750%26spaceId%3D25900039-5d85-4396-968b-a28f366f1763%26expirationTimestamp%3D1761127200000%26signature%3DZLZwwW115aXO6fNXve0DX8d411lnKUfw7tgH9i5d3_o?table=block&id=2332fd37-e2b0-800e-820d-f2343b4f3750&cache=v2)
这个第二个纠结了很久,最主要的问题是这份代码在里面检测了整个代码块的长度,导致在中间添加log会导致代码失效卡死,另外代码最开始的阿拉伯文也是非常的烧脑。
代码的起点在于喵喵盾没有对末尾的字符串解密函数进行检测,所以我直接在解密函数里插桩把每一个字符串都查出来,改成一个switch输出log看到他调用了一些tostring/search之类的函数。然后我就想到要检测格式化大致的方法除了对文本进行哈希加密然后验算以外,就必须要用到正则中的test/match或者字符串中的search/index/replace等函数我对这些函数进行了一个初步的hook,然后用setnative保护上但还是报错。于是我决定先看看tostring既然我用了这段tostring的hook代码,我也可以直接在tostring中输出每个获得的字符串。这下就得到了它大致验证的内容,除去那些本来就有的native 函数以外,他还验证了两个函数的tostring,以及整段代码的tostring,在函数和代码的部分下断点并且获取到返回的字符串改为switch,并且把原本的阿拉伯文代码块改为自己已经初步解密后的(替换了变量名)的代码块,运行通过。于是这样就可以下log了,接下来的步骤就只剩下根据正确的log流程不断查找自己代码中错误的部分了。循环的内容和前面提过的一样直接用就可以,主要耗时还是在var_name相关的提取上,要把实际代码中var_name占位的地方全部替换为实际值,这样才方便进行后面的缩点,关于缩点没什么好说的,插桩去除虚假分支,然后开始缩点,实际上和第一次相比没有多花很多时间。总之现在只剩下了复杂嵌套逻辑运算符和复杂的循环结构没有实际解决了。关于复杂的循环结构,我想了一下,对于循环里的出口线都遍历到
code
const fs = require('fs');
const babel = require("@babel/core");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const template = require("@babel/template").default;
const t = require("@babel/types");
const FastBitSet = require("fastbitset");
const {IfStatement} = require("@babel/generator/lib/generators/statements");
const {breakStatement} = require("@babel/types");
const {log} = require('console');
const generator = require("@babel/generator").default;
// readPath = './miaomiao/test.js'
readPath = './target.js'
// readPath = './ok.js'
writePath = './result1.js'
let input_code = fs.readFileSync(readPath, {encoding: 'utf-8'});
//如果一个块可以同时被多个不同的结构调用解混淆难度会非常高,这种情况会导致很多结构不好还原。
//如果每个块都是单纯的被源代码中的结构调用则简单许多
let ast = parser.parse(input_code);
function uniVarname(ast) {
let pcount = 0;
let fcount = 0;
function generateUniqueName() {
// if (uid == 5 || uid == 32) debugger;
// console.log(uid);
return `p${pcount++}`;
}
function generateFuncName() {
return `f${fcount++}`;
}
function generatePreproName() {
return `o${fcount++}`;
}
const renameVisitor = {
Scope: {
enter(path) {
const bindings = path.scope.bindings;
const renameMap = new Map();
// 先算好所有映射,避免重名踩踏
for (const [oldName, binding] of Object.entries(bindings)) {
let newName;
/* ---------- 识别“函数”绑定 ---------- */
if (// ① function foo() {}
binding.path.isFunctionDeclaration() || // ② const foo = function() {} | const foo = () => {}
(binding.path.isVariableDeclarator() && binding.path.get('init').isFunction()) || // ③ class Foo {} (如果你也想归为“函数式”命名,可加上)
binding.path.isClassDeclaration()) {
newName = generateFuncName(oldName); // ▶ 你的函数命名策略
}
/* ---------- 识别“对象”绑定 ---------- */ else if (// const obj = { a: 1 }
binding.path.isVariableDeclarator() && binding.path.get('init').isObjectExpression()) {
newName = generateUniqueName(oldName); // ▶ 你的对象命名策略
}
/* ---------- 其他类型 ---------- */ else {
// console.log('Properties');
// debugger//
newName = generateUniqueName(oldName);
}
// if (newName === 'p10')debugger;
renameMap.set(oldName, newName);
}
for (const [oldName, newName] of renameMap) {
console.log(oldName, newName);
path.scope.rename(oldName, newName);
}
}
}, // // 处理对象属性声明
// ObjectProperty: {
// exit(path) {
// if (path.node.computed) return;
// const key = path.node.key;
//
// const keyNode = path.node.key;
// const parentObj = path.findParent(p => p.isObjectExpression());
//
// // 获取对象变量名(适用于:const obj = { key: value })
// const objName = parentObj.parent.id?.name;
// if (!objName) {
// // console.log(key.name, count++);
// const newName = generateUniqueName();
// renamedProps.set(key.name, newName);
// const newKey = t.identifier(newName);
// console.log(key.name, newName);
// path.get('key').replaceWith(newKey);
// } else {
// // 生成新属性名
// const oldKey = t.isIdentifier(keyNode) ? keyNode.name : keyNode.value;
// const newKey = generateUniqueName();
// // console.log(oldKey, newKey);
// // 记录全局映射关系
// const fullPath = `${objName}.${oldKey}`;
// propMap.set(fullPath, newKey);
// // console.log(count++,"count",oldKey);
// // 替换属性键名
// path.node.key = t.identifier(newKey)
// }
//
//
// }
// },
//
// // 追踪对象别名(处理 b = a 的情况)
// AssignmentExpression(path) {
// if (t.isIdentifier(path.node.left) && t.isIdentifier(path.node.right)) {
// const alias = path.node.left.name;
// const original = path.node.right.name;
// objAliasMap.set(alias, original);
// }
// },
//
// // 处理属性访问(核心新增逻辑)
// MemberExpression: {
// exit(path) {
// const {object, property} = path.node;
//
// // 处理静态属性访问(obj.key)
// if (t.isIdentifier(object) && t.isIdentifier(property)) {
// const originalObj = objAliasMap.get(object.name) || object.name;
// const oldKey = property.name;
//
// // 查找映射关系
// const fullPath = `${originalObj}.${oldKey}`;
// if (propMap.has(fullPath)) {
// path.node.property = t.identifier(propMap.get(fullPath));
// }
// }
//
// // 处理字符串字面量访问(obj["key"])
// if (t.isIdentifier(object) && t.isStringLiteral(property)) {
// const originalObj = objAliasMap.get(object.name) || object.name;
// const oldKey = property.value;
//
// const fullPath = `${originalObj}.${oldKey}`;
// if (propMap.has(fullPath)) {
// path.node.property = t.stringLiteral(propMap.get(fullPath));
// }
// }
// }
// },
};
traverse(ast, renameVisitor);
const renameProper = {
ObjectExpression(path) {
// debugger;
if (path.parentPath.type === 'VariableDeclarator')
for (const prop of path.node.properties) {
if (prop.key && prop.key.type === "Identifier") {
const oldName = prop.key.name;
const newName = generatePreproName(oldName); // 你的新命名逻辑
const varname = path.parentPath.node.id.name;
const binding = path.scope.getBinding(varname);
if (binding) {
// debugger;
for (const refPath of binding.referencePaths) {
debugger;
if (refPath.parentPath.type === 'MemberExpression' && refPath.parentPath.node.property.name === oldName) {
refPath.parentPath.node.property.name = newName;
}
}
}
prop.key.name = newName;
}
}
}
}
// traverse(ast, renameProper);
}
//添加括号
function addbraces(ast) {
const visitor = {
IfStatement(path) {
const {consequent, alternate} = path.node;
// 处理 if 的主体(consequent)
if (!t.isBlockStatement(consequent)) {
path.node.consequent = t.blockStatement([consequent]);
}
// 处理 else 的部分(alternate),如果存在且不是 if 或 block,则包裹
if (alternate && !t.isBlockStatement(alternate)) {
path.node.alternate = t.blockStatement([alternate]);
}
},
WhileStatement(path) {
const {body} = path.node;
if (!t.isBlockStatement(body)) {
path.node.body = t.blockStatement([body]);
}
}
}
traverse(ast, visitor);
}
function reverseUnicode(ast) {
const visitor = {
Identifier(path) {
const originalName = path.node.name;
// 检查是否含有 \u 编码
if (/\\u[\da-fA-F]{4}/.test(originalName)) {
try {
// 转换为正常字符
const decoded = originalName.replace(/\\u[\da-fA-F]{4}/g, (match) => String.fromCharCode(parseInt(match.slice(2), 16)));
// 如果合法且与原始不同,就替换
if (decoded !== originalName && t.isValidIdentifier(decoded)) {
path.scope.rename(originalName, decoded);
}
} catch (e) {
// 忽略无法解码的
console.log("unable to decode unicode var name");
}
}
}, StringLiteral(path) {
const {value, extra} = path.node;
if (!extra) {
path.node.extra = {rawValue: value};
}
path.node.extra.raw = JSON.stringify(value);
}
}
traverse(ast, visitor);
}
function tripletoif() {
let var_name = 'p6';
//预处理三目表达式
traverse(ast, {
AssignmentExpression: {
enter: function (path) {
let node = path.node;
// 首先需要判断是不是三目表达式,同时赋值表达式的操作符号需要是 等于号
if (node.right.type === 'ConditionalExpression' && node.operator === "=") {
// debugger;
let assConseqNode = parser.parse("a=1").program.body[0].expression; // if node
let assAltNode = parser.parse("a=1").program.body[0].expression; // else node
let leftNode = node.left;
let condNode = node.right;
// 构造新的 node 三目表达式的左值更改形式
assConseqNode.left = leftNode;
assConseqNode.right = condNode.consequent;
condNode.consequent = assConseqNode;
// 三目表达式的右值更改形式
assAltNode.left = leftNode;
assAltNode.right = condNode.alternate;
condNode.alternate = assAltNode;
path.replaceWithMultiple(condNode);
}
},
}, ReturnStatement: {
enter: function (path) {
let node = path.node;
if (node.argument && node.argument.type === 'ConditionalExpression') {
let assConseqNode = parser.parse("{return a;}", {
allowReturnOutsideFunction: true, attachComment: false
}).program.body[0]; // if node
let assAltNode = parser.parse("{return a}", {allowReturnOutsideFunction: true}).program.body[0]; // else node
let condNode = node.argument;
// 构造新的 node 三目表达式的左值更改形式
assConseqNode.body[0].argument = node.argument.consequent;
// 三目表达式的右值更改形式
assAltNode.body[0].argument = node.argument.alternate;
let ifs = t.ifStatement(node.argument.test, assConseqNode, assAltNode);
path.replaceWithMultiple(ifs)
}
}
}
});
// ast = parser.parse(generator(ast).code);
//处理除了return以外的三目,return在上面处理了
//有个小问题再替换节点的时候尽量去寻找父节点进行替换,替换当前节点会有各种各样的问题,例如这里寻找conditional的话就会有问题。
traverse(ast, {
ExpressionStatement: {
exit: function (path) {
let node = path.node;
if (node.expression.type === 'ConditionalExpression') {
// console.log(generator(node).code)
//只处理包含<>的
if (node.expression.test.type === 'BinaryExpression') {
const ifStatement = t.ifStatement(node.expression.test, t.blockStatement([t.expressionStatement(node.expression.consequent)]), t.blockStatement([t.expressionStatement(node.expression.alternate)]));
path.replaceInline(ifStatement);
}
}
},
}
});
}
function ifToSwitch(ast) {
let codeblocks = [];//BlockStatement;
let count = 0;
let var_name = 'p10';
let temp_name = 't10';
function collectionblocks(ast) {
const visitor = {
WhileStatement(path) {
let {node, scope} = path;
scope.traverse(node, {
IfStatement(path) {
let node = path.node;
if (node.consequent.body[0].type !== 'IfStatement') {
codeblocks.push(node.consequent);
let varstat = t.expressionStatement(t.assignmentExpression('=', t.identifier(var_name), t.valueToNode(count++)));
let tempstat = t.expressionStatement(t.assignmentExpression('=', t.identifier(temp_name), t.identifier(var_name)));
node.consequent = t.blockStatement([tempstat, varstat, t.breakStatement()]);
}
if (node.alternate.body[0].type !== 'IfStatement') {
codeblocks.push(node.alternate);
let varstat = t.expressionStatement(t.assignmentExpression('=', t.identifier(var_name), t.valueToNode(count++)));
let tempstat = t.expressionStatement(t.assignmentExpression('=', t.identifier(temp_name), t.identifier(var_name)));
node.alternate = t.blockStatement([tempstat, varstat, t.breakStatement()]);
}
}
})
}
};
traverse(ast, visitor);
}
function collectionstatus(ast) {
traverse(ast, {
//收集for下的status,并且使用这个status,统一if else以及条件
IfStatement: {
enter: function (path) {
let node = path.node;
let prevpath = path.getPrevSibling();
// console.log(path.toString());
// debugger;
// if(node.test.left&&node.test.left.name === 'var_193'&&node.test.right.value == 111&&node.test.operator==='>')
// debugger;C
// if(node.test.left&&node.test.left.name === 'var_193'&&node.test.right.value === 6&&node.test.operator==='>')
// debugger;
if (node.consequent.body) {
if (node.consequent.body[0] && node.consequent.body[node.consequent.body.length - 1].type === 'ContinueStatement') {
// if(node.test.left.name === 'var_193'&&node.test.right.value === 168&&node.test.operator==='<=')
// debugger;
// console.log(path.toString())
blocklist[index++] = [state, t.blockStatement(node.consequent.body.slice(0, -1))];
node.consequent.body = [t.ExpressionStatement(t.AssignmentExpression('=', t.Identifier(var_name), t.numericLiteral(index))), t.breakStatement()];
} else if (node.consequent.body[0] && node.consequent.body[0].type === 'ExpressionStatement' && node.consequent.body[0].expression.argument.name === 'var_193') {
if (node.consequent.body[0].expression.operator === '++') {
state++;
// debugger;
}
if (node.consequent.body[0].expression.operator === '--') {
state--;
// debugger;
}
}
}
}, exit: function (path) {
let prevpath = path.getPrevSibling();
let node = path.node;
if (node.consequent.body) {
if (node.consequent.body[0] && node.consequent.body[0].type === 'ExpressionStatement' && node.consequent.body[0].expression.argument && node.consequent.body[0].expression.argument.name === 'var_193') {
if (node.consequent.body[0].expression.operator === '++') {
state--;
// debugger;
}
if (node.consequent.body[0].expression.operator === '--') {
state++;
// debugger;
}
}
}
}
},
});
}
collectionblocks(ast);
const visitor = {
WhileStatement(path) {
let node = path.node;
// debugger;
let body = path.toString();
let code = "switch(" + var_name + "){};";
let new_node = parser.parse(code).program.body[0];
for (let i = 0; i <= 1117; i++) {//{'0':var_name,'1':temp_name}
let evelstr = 'let ' + var_name + '=' + i + ';\n' + 'let ' + temp_name + '=' + i + ';\n' + body + '\n' + `let obj = {a:` + var_name + `,b:` + temp_name + '};' + 'obj';
// debugger;
let result_num = eval(evelstr);
// console.log(i, result_num);
// debugger;
//这里可能还需要追踪一下if链的test中var_name的++/--操作,并且一次性在cases最前面添加上一条赋值语句
//但是这里大概率没有在代码块中使用var_name所以先懒得写了。
let tempstat = t.expressionStatement(t.assignmentExpression('=', t.identifier(var_name), t.valueToNode(result_num['b'])));
let innercode = codeblocks[result_num['a']];
innercode.body.unshift(tempstat);
// debugger;
if (innercode.body[innercode.body.length - 1].type !== 'BreakStatement') innercode.body.push(t.breakStatement());
new_node.cases.push(t.switchCase(t.numericLiteral(i), innercode.body));
}
// debugger;
node.body.body = [new_node];
}
}
traverse(ast, visitor);
//把表达式中包含var_name的赋值语句提到前面,不通用
//提到后面把方便处理,但是如果语句块使用了这个值则不能这么写
function uniJmpvar(ast) {
const visitor = {
UpdateExpression(path) {//提前处理一行函数中多次调用var_name的第一次++--
let node = path.node;
let parentObj = path.findParent(p => p.isCallExpression());
if (parentObj.node.callee.name === 'f1' && node.argument.name === var_name) {
parentObj = path.findParent(p => p.isSwitchCase());
if (node.operator === '--') {
parentObj.get('consequent')[0].node.expression.right.value--;
if (node.prefix == true) {
path.replaceWith(t.valueToNode(parentObj.get('consequent')[0].node.expression.right.value));
} else {
path.replaceWith(t.valueToNode(parentObj.get('consequent')[0].node.expression.right.value + 1));
}
}
if (node.operator === '++' && node.prefix == true) {
// debugger;
parentObj.get('consequent')[0].node.expression.right.value++;
path.replaceWith(t.valueToNode(parentObj.get('consequent')[0].node.expression.right.value));
}
}
},
VariableDeclarator(path) {
let node = path.node;
if (node.init && node.init.left && node.init.left.name === var_name) {
let parentObj = path.findParent(p => p.isSwitchCase());
let val = parentObj.get('consequent')[0].node.expression.right;
node.init.left = val;
}
},
BinaryExpression(path) {
let node = path.node;//||node.left && node.left.argument&&node.left.argument.name === var_name
if (node.left && node.left.name === var_name) {
let parentObj = path.findParent(p => p.isSwitchCase());
let val = parentObj.get('consequent')[0].node.expression.right;
node.left = val;
}
},
AssignmentExpression(path) {
let node = path.node;
//防止提取单独表达式和三目表达式中的
//在这里面尽量把表达式转为=表达式
let parentObj = path.findParent(p => p.isSwitchCase());
// parentObj = parentObj.node.consequent;
if (node.left.name === var_name && (node.right.type !== 'NumericLiteral' || node.operator !== '=') && node.right.type !== 'Identifier' && path.parentPath.node.type !== 'ConditionalExpression') {
z = 1;
let varstat = 'let ' + parentObj.get('consequent')[0].toString();
let bodystat = path.toString() + ';'
let endstat = var_name;
let res = eval(varstat + '\n' + bodystat + '\n' + endstat);
node.operator = '=';
node.right = t.valueToNode(res);
}
},
}
traverse(ast, visitor);
const visitor2 = {//
AssignmentExpression(path) {
let node = path.node;
if (node.left.name === var_name && path.parentPath.node.type !== 'ConditionalExpression' && path.parentPath.node.type !== 'ExpressionStatement') {
// if (node.right.value == 910) debugger;
// debugger;
let parentObj = path.findParent(p => p.isExpressionStatement() || p.isReturnStatement());
if (parentObj.type === 'ExpressionStatement') {
parentObj.insertAfter(t.expressionStatement(node));
}
path.replaceWith(t.valueToNode(node.right.value));
}
},
}
traverse(ast, visitor2);
const visitor3 = {
SequenceExpression(path) {
let node = path.node;
if (node.expressions.length === 2 && node.expressions[0].type === 'NumericLiteral') {
// debugger;
path.replaceWith(node.expressions[1]);
}
}
}//去除无效sequence
traverse(ast, visitor3);
}
uniJmpvar(ast);
function eval1(ast) {
const visitor = {
BinaryExpression(path) {
let node = path.node;
if (node.left.type === 'NumericLiteral' && node.right.type === 'NumericLiteral') {
let res = eval(path.toString());
path.replaceWith(t.valueToNode(res));
}
}
}
traverse(ast, visitor);
}
eval1(ast);
}
function deleteFalse(ast) {
let var_name = 'p10';
/*
function copyLogList(loglist) {
const text = JSON.stringify(loglist, null, 2);
const text = loglist.join(", "); // 或使用 "\n" 换行显示
navigator.clipboard.writeText(text)
.then(() => {
alert("loglist 内容已复制到剪贴板:");
})
.catch(err => {
alert("复制失败:");
});
}
document.addEventListener('keydown', function (event) {
if (event.key === 'w' || event.key === 'W') {
copyLogList(loglist);
}
});
*/
// let loglist = [67, 262, 289, 254, 261, 194, 278, 48, 253, 69, 229, 333, 91, 605, 337, 174, 387, 270, 39, 497, 545, 93, 551, 143, 429, 135, 415, 474, 161, 321, 301, 434, 584, 164, 479, 168, 588, 98, 599, 456, 274, 258, 100, 102, 118, 113, 549, 444, 180, 614, 123, 611, 447, 454, 430, 142, 441, 96, 412, 594, 248, 133, 190, 442, 141, 184, 175, 546, 286, 188, 192, 514, 450, 438, 103, 418, 443, 309, 585, 280, 422, 455, 528, 122, 129, 413, 251, 460, 508, 112, 110, 464, 487, 147, 583, 645, 41, 534, 548, 326, 395, 579, 553, 562, 590, 501, 408, 140, 149, 177, 317, 311, 603, 592, 353, 359, 439, 325, 595, 58, 576, 4, 414, 90, 525, 38, 502, 581, 347, 334, 159, 591, 416, 250, 637, 618, 104, 365, 160, 467, 531, 15, 204, 396, 298, 13, 21, 578, 138, 369, 170, 421, 625, 225, 80, 265, 231, 341, 125, 481, 566, 550, 473, 77, 49, 18, 47, 410, 166, 601, 391, 233, 522, 35, 207, 183, 475, 173, 212, 623, 629, 139, 371, 124, 148, 370, 560, 490, 544, 448, 305, 644, 510, 469, 461, 597, 293, 344, 130, 446, 201, 530, 120, 12, 401, 2, 495, 220, 283, 282, 380, 264, 457, 234, 196, 178, 68, 43, 9, 0, 277, 65, 297, 304, 197, 275, 167, 543, 64, 355, 237, 32, 66, 45, 271, 252, 404, 137, 99, 569, 520, 259, 81, 394, 109, 593, 598, 346, 191, 338, 223, 409, 463, 1, 279, 152, 646, 610, 536, 117, 453, 523, 239, 480, 255, 354, 154, 462, 310, 509, 571, 272, 230, 512, 352, 489, 267, 285, 612, 378, 486, 7, 500, 609, 526, 556, 577, 361, 88, 14, 535, 336, 573, 219, 181, 224, 186, 604, 44, 257, 162, 596, 313, 507, 589, 40, 318, 95, 238, 399, 574, 70, 554, 151, 218, 568, 631, 348, 211, 157, 621, 339, 136, 517, 187, 51, 424, 343, 366, 626, 558, 94, 294, 216, 476, 308, 521, 635, 449, 382, 273, 518, 478, 419, 53, 86, 263, 85, 642, 134, 302, 466, 362, 377, 628, 101, 561, 127, 407, 376, 620, 33, 17, 25, 36, 511, 350, 72, 61, 640, 527, 73, 266, 245, 198, 575, 398, 529, 586, 50, 210, 6, 632, 206, 23, 56, 375, 504, 312, 176, 22, 79, 226, 244, 619, 383, 406, 163, 541, 425, 437, 84, 471, 373, 537, 390, 498, 284, 27, 202, 37, 209, 423, 403, 31, 87, 492, 222, 300, 538, 179, 55, 483, 319, 435, 8, 150, 213, 232, 236, 116, 268, 290, 426, 627, 323, 388, 208, 165, 616, 630, 393, 156, 436, 189, 427, 303, 316, 431, 636, 107, 539, 146, 374, 557, 83, 185, 153, 615, 92, 452, 624, 400, 291, 128, 172, 622, 639, 392, 111, 451, 411, 485, 587, 505, 643, 256, 582, 82, 602, 227, 243, 200, 397, 145, 468, 524, 357, 215, 428, 108, 281, 555, 171, 564, 295, 491, 606, 221, 470, 115, 465];
let loglist = [400, 815, 817, 991, 1056, 1016, 1029, 1008, 1042, 980, 1046, 1036, 1090, 2, 109, 1059, 55, 251, 1063, 40, 1065, 92, 231, 224, 626, 644, 84, 267, 236, 219, 590, 211, 85, 586, 238, 1055, 242, 279, 56, 671, 593, 995, 132, 13, 97, 7, 997, 1115, 52, 22, 265, 228, 325, 341, 135, 334, 1033, 35, 15, 1038, 631, 1114, 661, 118, 226, 833, 16, 645, 171, 754, 838, 50, 619, 1096, 707, 233, 485, 1024, 1030, 847, 1023, 746, 808, 243, 630, 750, 863, 596, 947, 825, 622, 921, 234, 332, 1076, 239, 1022, 64, 814, 744, 102, 326, 976, 960, 756, 758, 889, 130, 378, 853, 861, 18, 702, 611, 1, 574, 831, 576, 794, 86, 714, 867, 71, 395, 315, 48, 638, 131, 859, 403, 412, 575, 753, 952, 1082, 642, 190, 953, 280, 1083, 345, 606, 147, 165, 398, 380, 599, 646, 311, 349, 651, 87, 175, 393, 338, 283, 742, 917, 835, 1057, 969, 839, 635, 865, 117, 413, 119, 656, 697, 581, 308, 799, 641, 770, 342, 1060, 779, 640, 842, 314, 140, 868, 566, 716, 0, 221, 212, 424, 248, 435, 449, 655, 383, 447, 652, 434, 386, 542, 1109, 203, 886, 570, 104, 549, 353, 346, 365, 307, 908, 387, 585, 503, 419, 892, 178, 819, 877, 316, 535, 436, 142, 561, 1005, 162, 762, 557, 1107, 356, 783, 681, 476, 912, 49, 121, 1116, 961, 704, 897, 989, 954, 260, 299, 928, 901, 1101, 202, 504, 802, 818, 788, 823, 743, 940, 759, 741, 428, 149, 323, 268, 993, 498, 567, 956, 82, 665, 673, 210, 1040, 396, 939, 124, 3, 1015, 701, 527, 555, 406, 196, 305, 625, 734, 373, 394, 230, 511, 148, 232, 670, 1043, 963, 39, 703, 613, 116, 1048, 935, 298, 205, 1011, 420, 367, 533, 53, 709, 594, 261, 970, 900, 615, 1035, 695, 359, 654, 1093, 539, 292, 771, 198, 1100, 852, 163, 700, 101, 462, 348, 417, 563, 977, 51, 872, 801, 637, 752, 8, 173, 169, 347, 28, 1039, 296, 455, 475, 659, 793, 414, 461, 998, 965, 786, 982, 513, 408, 108, 749, 688, 486, 128, 425, 192, 720, 204, 208, 926, 454, 34, 893, 648, 890, 975, 545, 93, 427, 199, 571, 531, 1078, 88, 235, 948, 74, 1071, 552, 903, 152, 76, 1047, 1110, 822, 110, 469, 668, 662, 187, 806, 660, 304, 971, 518, 1019, 607, 543, 1061, 452, 300, 650, 951, 623, 188, 297, 500, 191, 501, 624, 1079, 846, 816, 445, 44, 42, 679, 855, 256, 803, 821, 1089, 423, 182, 1106, 371, 914, 860, 1014, 986, 864, 748, 1010, 481, 604, 592, 984, 990, 525, 1064, 584, 937, 65, 558, 206, 856, 605, 811, 1091, 271, 453, 588, 458, 180, 274, 804, 1007, 275, 270, 1054, 854, 874, 193, 185, 765, 181, 713, 269, 457, 873, 528, 468, 254, 502, 520, 996, 1099, 195, 534, 517, 201, 170, 105, 725, 515, 1004, 556, 266, 780, 739, 496, 988, 281, 602, 538, 690, 112, 706, 740, 336, 302, 446, 286, 1105, 115, 920, 722, 949, 355, 364, 144, 925, 310, 258, 36, 278, 438, 1087, 474, 361, 904, 751, 731, 68, 415, 489, 1002, 973, 358, 57, 675, 67, 944, 608, 959, 946, 313, 320, 1050, 222, 657, 554, 217, 1012, 176, 876, 1032, 591, 887, 463, 479, 880, 550, 363, 562, 589, 922, 983, 294, 327, 776, 735, 72, 573, 769, 379, 237, 1092, 1068, 218, 293, 322, 450, 905, 509, 174, 257, 10, 262, 508, 381, 766, 54, 30, 376, 473, 730, 207, 526, 167, 366, 1051, 807, 666, 70, 595, 772, 843, 491, 964, 888, 512, 472, 559, 1103, 506, 1025, 797, 775, 247, 351, 306, 494, 1009, 249, 1037, 75, 295, 1085, 796, 958, 372, 583, 862, 1017, 183, 784, 354, 497, 179, 377, 33, 580, 968, 255, 492, 617, 209, 667, 1062, 113, 362, 456, 885, 17, 902, 488, 950, 159, 390, 916, 333, 1108, 649, 898, 330, 37, 967, 339, 95, 639, 79, 111, 896, 244, 627, 437, 955, 106, 553, 1044, 514, 63, 686, 537, 911, 906, 609, 683, 600, 929, 516, 411, 598, 943, 120, 250, 767, 643, 470, 653, 610, 138, 89, 664, 59, 357, 4, 727, 252, 521, 440, 317, 177, 369, 522, 715, 484, 370, 263, 172, 464, 529, 83, 658, 747, 689, 324, 5, 1053, 924, 899, 145, 11, 684, 1073, 1077, 974, 1021, 620, 909, 729, 61, 432, 319, 240, 12, 785, 569, 335, 431, 778, 682, 62, 318, 875, 399, 409, 186, 760, 733, 471, 848, 94, 572, 19, 565, 978, 579, 259, 344, 229, 773, 694, 691, 936, 9, 329, 273, 253, 321, 546, 541, 782, 693, 1020, 895, 1001, 879, 47, 73, 774, 46, 213, 680, 577, 829, 932, 125, 878, 21, 328, 813, 223, 612, 919, 669, 826, 137, 510, 343, 787, 32, 166, 755, 672, 999, 544, 568, 957, 1027, 277, 827, 1095, 402, 143, 1075, 467, 214, 421, 777, 150, 712, 1081, 184, 845, 718, 495, 696, 692, 962, 698, 840, 708, 582, 331, 800, 483, 389, 401, 721, 677, 1052, 907, 158, 931, 530, 1104, 881, 757, 1000, 883, 614, 422, 764, 844, 122, 923, 994, 114, 38, 507, 938, 820, 930, 992, 927, 23, 134, 27, 1069, 480, 768, 1102, 60, 98, 1067, 934, 58, 676, 499, 151, 20, 728, 621, 913, 858, 685, 505, 547, 795, 717, 790, 26, 678, 397, 216, 478, 1006, 1072, 824, 103, 791, 24, 465, 340, 851, 129, 439, 220, 477]
let log4 = {
"1": [false, true],
"17": [true, false],
"18": [false],
"19": [true, false],
"39": [false],
"87": [true],
"112": [false],
"117": [true, false],
"130": [false],
"140": [true, false],
"158": [true, false],
"180": [false, true],
"182": [false, true],
"207": [true],
"221": [false],
"239": [false, true],
"249": [true, false],
"255": [true, false],
"262": [true],
"271": [false, true],
"275": [false, true],
"332": [true],
"379": [false],
"393": [true, false],
"447": [true],
"458": [false, true],
"478": [false, true],
"507": [true, false],
"516": [true, false],
"517": [true, false],
"535": [true],
"539": [false],
"545": [true, false],
"550": [true],
"558": [false, true],
"562": [false],
"574": [false, true],
"584": [true, false],
"596": [true],
"604": [true],
"605": [true, false],
"619": [true, false],
"622": [true],
"635": [true],
"646": [true],
"654": [false],
"744": [false, true],
"748": [true],
"754": [false],
"760": [true, false],
"786": [false, true],
"796": [true],
"814": [false, true],
"842": [true, false],
"847": [false],
"855": [true],
"861": [false],
"886": [false],
"889": [false, true],
"890": [true, false],
"948": [false, true],
"960": [false, true],
"976": [false, true],
"978": [true, false],
"1007": [false, true],
"1057": [false],
"1060": [true],
"1067": [true, false],
"1092": [true],
"1099": [false]
}
// let log4 = {
// "64"
// :
// [
// true
// ],
// "96"
// :
// [
// true,
// false
// ],
// "100"
// :
// [
// false,
// true
// ],
// "103"
// :
// [
// false,
// true
// ],
// "112"
// :
// [
// true,
// false
// ],
// "175"
// :
// [
// false,
// true
// ],
// "180"
// :
// [
// true
// ],
// "185"
// :
// [
// true,
// false
// ],
// "188"
// :
// [
// false,
// true
// ],
// "192"
// :
// [
// false,
// true
// ],
// "207"
// :
// [
// false
// ],
// "223"
// :
// [
// true,
// false
// ],
// "248"
// :
// [
// false,
// true
// ],
// "251"
// :
// [
// false
// ],
// "259"
// :
// [
// true,
// false
// ],
// "293"
// :
// [
// true
// ],
// "304"
// :
// [
// true,
// false
// ],
// "393"
// :
// [
// true,
// false
// ],
// "428"
// :
// [
// false,
// true
// ],
// "438"
// :
// [
// false,
// true
// ],
// "442"
// :
// [
// true,
// false
// ],
// "451"
// :
// [
// true,
// false
// ],
// "475"
// :
// [
// true
// ],
// "490"
// :
// [
// true
// ],
// "550"
// :
// [
// true
// ],
// "561"
// :
// [
// true,
// false
// ],
// "577"
// :
// [
// true,
// false
// ],
// "588"
// :
// [
// true
// ],
// "611"
// :
// [
// true
// ],
// "628"
// :
// [
// true,
// false
// ],
// "642"
// :
// [
// true,
// false
// ]
// };
const visitor = {
SwitchCase(path) {
let node = path.node;
if (node.test && node.test.type === 'NumericLiteral' && !loglist.includes(node.test.value)) {
path.remove()
}
}
}
traverse(ast, visitor);
//去除虚假分支以后把三目运算符中的虚假分支也去除,但是即使通过这种方式去除了没有执行过的虚假分支,还是有可能存在执行过的块,出现在了永远也不会跑的路径上if(a)b:c中bc两个块都存在,但是这个判断语句中永远不会走b,这样也会导致你的控制流没法完全还原
const visitor1 = {
SwitchCase(path) {
let node = path.node;
if (!node.test) return;
let i = node.test.value + '';
let last = node.consequent.at(-2);
if (last && last.expression && last.expression.type === 'ConditionalExpression') {
if (!loglist.includes(parseInt(last.expression.consequent.right.value))) {
// debugger;
// console.log(generator(last).code)
last.expression = last.expression.alternate;
} else if (!loglist.includes(parseInt(last.expression.alternate.right.value))) {
// debugger;
// console.log(generator(last).code)
last.expression = last.expression.consequent;
} else if (log4[i] && log4[i].length === 1) {
if (log4[i][0]) {
last.expression = last.expression.consequent;
} else {
last.expression = last.expression.alternate;
}
}
}
}
}
traverse(ast, visitor1)
//为try下添加一行代码
const visitor2 = {
TryStatement(path) {
let val = path.getSibling(path.key - 1).node.declarations[0].init;
let stat = t.expressionStatement(t.assignmentExpression('=', t.identifier(var_name), val));
path.insertAfter(stat);
}
}
traverse(ast, visitor2);
}
function addlog(ast) {
let pcount = 0;
function generateUniqueName() {
// if (uid == 5 || uid == 32) debugger;
// console.log(uid);
return `temp${pcount++}`;
}
let var_name = 'p10';
// const expr = parser.parseExpression("loglist[p10] = loglist[p10]?[].push(p10)");
// parseExpression 只给你 Expression;想放进 If 的 consequent,要包成 Statement
const visitor1 = {
SwitchCase(path) {
let node = path.node;
let last = node.consequent.at(-2);
if (last && last.expression && last.expression.type === 'ConditionalExpression') {
let new_name = generateUniqueName();
node.consequent.splice(node.consequent.length - 2, 0, t.variableDeclaration('let', [t.variableDeclarator(t.identifier(new_name), t.unaryExpression('!', t.unaryExpression('!', (last.expression.test))))]));
last.expression.test = t.identifier(new_name);
const expr1 = parser.parseExpression("loglist[p4] = loglist[p4]?loglist[p4]:[]");
expr1.left.property.name = node.test.value + '';
expr1.right.consequent.property.name = node.test.value + '';
expr1.right.test.property.name = node.test.value + '';
const expr = parser.parseExpression("loglist[p4].includes(z1)?1:loglist[p4].push(z1)");
expr.test.callee.object.property.name = node.test.value + '';
expr.test.arguments[0].name = new_name;
expr.alternate.arguments[0].name = new_name;
expr.alternate.callee.object.property.name = node.test.value + '';
node.consequent.splice(node.consequent.length - 2, 1, last, t.expressionStatement(expr1), t.expressionStatement(expr));
// node.consequent.splice(node.consequent.length - 1, 0, t.expressionStatement(expr1),t.expressionStatement(expr));
}
}
}
traverse(ast, visitor1)
const visitor2 = {
WhileStatement(path) {
let node = path.node;
// const expr = parser.parseExpression("console['log'](p9)");
const expr = parser.parseExpression("count<1000?loglist.push((count++,p10)):1");
debugger;
node.body.body.unshift(t.expressionStatement(expr));
}
}
// traverse(ast, visitor2);
}
function switchtriple(ast) {
let var_name = 'p10';
const visitor1 = {//把z?p10=1:p10=2 转为p10 = z?1:2;
SwitchCase(path) {
let node = path.node;
let last = node.consequent.at(-2);
if (last && last.expression && last.expression.type === 'AssignmentExpression') {
if (last.expression.left.name === var_name && last.expression.right.type === 'ConditionalExpression') {
const newExpr = t.expressionStatement(t.conditionalExpression(last.expression.right.test, t.assignmentExpression("=", last.expression.left, last.expression.right.consequent), t.assignmentExpression("=", t.cloneNode(last.expression.left), last.expression.right.alternate)));
node.consequent.splice(-2, 1, newExpr);
// path.replaceWith(t.expressionStatement(newExpr));
}
}
}
}
// traverse(ast, visitor1)
const visitor2 = {
ConditionalExpression(path) {
let node = path.node;
if (node.consequent.type === 'ConditionalExpression' || node.alternate.type === 'ConditionalExpression') {
// debugger;
if (node.consequent.type === 'ConditionalExpression' && node.alternate.left.name === var_name) {
debugger
}
if (node.alternate.type === 'ConditionalExpression' && node.consequent.left.name === var_name) {
// debugger;
let parentObj = path.findParent(p => p.isSwitchCase());
let val = parentObj.node.consequent[0].expression.right.value;
parentObj = path.findParent(p => p.isSwitchStatement());
let lastindex = parentObj.node.cases.length;
let varstat = t.expressionStatement(t.assignmentExpression('=', t.identifier(var_name), t.valueToNode(val)));
debugger;
let casestat = t.SwitchCase(t.valueToNode(lastindex), [t.cloneNode(varstat, true), t.expressionStatement(node.alternate), t.breakStatement()]);
parentObj.node.cases.push(casestat);
varstat.expression.right.value = lastindex;
node.alternate = varstat.expression;
debugger;
}
}
}
}//拆分嵌套3分支为2分支
// traverse(ast, visitor2);
//由于没有什么动态修改....这里的分支实际上是假分支直接删除就可以了,正常来说还是需要走流程拆分的
const visitor3 = {
ConditionalExpression(path) {
let node = path.node;
if (node.consequent.type === 'ConditionalExpression' || node.alternate.type === 'ConditionalExpression') {
// debugger;
if (node.consequent.type === 'ConditionalExpression' && node.alternate.left.name === var_name) {
debugger
}
if (node.alternate.type === 'ConditionalExpression' && node.consequent.left.name === var_name) {
// debugger;
let parentObj = path.findParent(p => p.isSwitchCase());
let valstr = generator(parentObj.node.consequent[0]).code;
let teststr = generator(t.expressionStatement(node.alternate.test)).code;
let res = eval(valstr + teststr);
let varstat;
if (res) {
varstat = node.alternate.consequent;
} else {
varstat = node.alternate.alternate;
}
node.alternate = varstat;
}
}
}
}
traverse(ast, visitor3);
}
function finallyCombined(ast) {
let var_name = 'p10';
class nod {
constructor(code, next_num, num, left, right, side) {
// if (typeof next_num !== 'string' && next_num !== undefined) {
// debugger;
// }
this.num = num || 0;
this.code = code;
this.left = left || null;
this.right = right || null;
this.next_num = next_num || undefined;
this.side = side;
}
getType() {
if (this.next_num === '-1') {
return 'double';
} else if (this.next_num === 're' || this.next_num === '40000') {
return 'ret';
} else {
return 'single';
}
}
eq(node) {
if (this.num === node.num && generator(t.blockStatement(this.code)).code === generator(t.blockStatement(node.code)).code && this.next_num === node.next_num && this.side === node.side) {
return true;
}
return false;
}
assign(to, from) {
// debugger;
to.code = to.code.concat(from.code);
to.next_num = from.next_num;
if (from.left) to.left = from.left;
if (from.right) to.right = from.right;
if (from.test) to.test = from.test;
if (from.exit1) to.exit1 = from.exit1;
if (from.exit) to.exit = from.exit;
if (from.loop1) to.loop1 = from.loop1;
}
}
let cyclequeue = {};
function buildGraph(ast) {
let invoketime = {};
let end_node = new nod('40000');
//这里得用40000因为后面我要用的自然循环分析需要使用bitset类似的结构,所以只能用数字不然处理起来不方便,用一个尽可能小的超出下标范围的值即可
cyclequeue['40000'] = end_node;
const visitor = {
SwitchCase(path) {
let {node, scope} = path;
if (!node.test || node.test.type !== 'NumericLiteral') return;
let blocknum = node.test.value;
// if (blocknum === 101) {
// debugger;
// }
if (node.consequent.at(-1).type === 'BreakStatement') {
node.consequent.splice(node.consequent.length - 1, 1);
}
invoketime[blocknum] = invoketime[blocknum] ? invoketime[blocknum] : 0;
if (!cyclequeue[blocknum]) {
let code;
// if (node.consequent.at(-1).type === 'ReturnStatement' || node.consequent.at(-1).type === 'TryStatement') {
// code = node.consequent;
// } else {
// code = node.consequent.slice(0, -1);
// }
let first = node.consequent.at(0);
if (first.type === 'ExpressionStatement' && (first.expression.type === 'AssignmentExpression' && first.expression.left.name === var_name)) {
// debugger;
node.consequent = node.consequent.slice(1, node.consequent.length);
}
let last = node.consequent.at(-1);
if (last.type === 'ExpressionStatement' && (last.expression.type === 'AssignmentExpression' && last.expression.left.name === var_name || last.expression.type === 'ConditionalExpression' && last.expression.consequent.left.name === var_name)) {
code = node.consequent.slice(0, -1);
} else {
code = node.consequent;
}
// debugger;
cyclequeue[blocknum] = new nod(code, undefined, invoketime[blocknum]);
}
//分支语句
scope.traverse(node, {
ConditionalExpression(p) {
let node = p.node;//直接用一个结构存储引用次数,等要初始化的时候直接丢进去
if (node.consequent.left.name === var_name && node.alternate.left.name === var_name) {
cyclequeue[blocknum].next_num = '-1';
cyclequeue[blocknum].left = node.consequent.right.value + '';
cyclequeue[blocknum].right = node.alternate.right.value + '';
cyclequeue[blocknum].test = node.test;
//更新引用次数
invoketime[node.alternate.right.value] = invoketime[node.alternate.right.value] ? invoketime[node.alternate.right.value] + 1 : 1;
invoketime[node.consequent.right.value] = invoketime[node.consequent.right.value] ? invoketime[node.consequent.right.value] + 1 : 1;
//如果下一个节点已经创建则直接更新,如果没有创建则等遍历到那个节点去创建和更新引用次数
if (cyclequeue[cyclequeue[blocknum].left]) {
cyclequeue[cyclequeue[blocknum].left].num = invoketime[cyclequeue[blocknum].left];
}
if (cyclequeue[cyclequeue[blocknum].right]) {
cyclequeue[cyclequeue[blocknum].right].num = invoketime[cyclequeue[blocknum].right];
}
}
}
});
//单独赋值语句
if (cyclequeue[blocknum].next_num !== '-1') {
scope.traverse(node, {
AssignmentExpression(p) {
if (p.node.left.name === var_name) {
//特殊处理,这里有一个try catch块,var的赋值在里面,并且未知,这里靠猜
if (p.node.left.type === 'Identifier' && p.node.right.type === 'Identifier') {
p.node.right = t.valueToNode(12);
}
cyclequeue[blocknum].next_num = p.node.right.value + '';
invoketime[cyclequeue[blocknum].next_num] = invoketime[cyclequeue[blocknum].next_num] ? invoketime[cyclequeue[blocknum].next_num] + 1 : 1;
if (cyclequeue[cyclequeue[blocknum].next_num]) {
cyclequeue[cyclequeue[blocknum].next_num].num = invoketime[cyclequeue[blocknum].next_num];
}
}
}, //teshu
BinaryExpression(p) {
let node = p.node;
if (node.left.type === 'Identifier' && node.right.type === 'NumericLiteral' && node.right.value === 356) {
node.left = t.valueToNode(125);
}
}
})
}
if (cyclequeue[blocknum].next_num === undefined) {
cyclequeue[blocknum].next_num = '40000';
}
if (!cyclequeue[blocknum]) {
cyclequeue[blocknum] = new nod(node.consequent, undefined, invoketime[blocknum]);
}
}
}
traverse(ast, visitor);
// debugger;
}
buildGraph(ast);
function getPred(cyclequeue) {
let Pred = {};
let keys = Object.keys(cyclequeue);
for (let i = 0; i < keys.length; i++) {
if (keys[i] === '40000') continue;
let cur = cyclequeue[keys[i]];
switch (cur.getType()) {
case 'double':
if (!Pred[cur.left]) Pred[cur.left] = [];
Pred[cur.left].push(keys[i]);
if (!Pred[cur.right]) Pred[cur.right] = [];
Pred[cur.right].push(keys[i]);
break;
case 'single':
if (!Pred[cur.next_num]) Pred[cur.next_num] = [];
Pred[cur.next_num].push(keys[i]);
break;
case 'ret':
if (!Pred[cur.next_num]) Pred[cur.next_num] = [];
Pred[cur.next_num].push(keys[i]);
break;
default:
break;
}
}
return Pred;
}
function getSufx(cyclequeue) {
let Sufx = {};
let keys = Object.keys(cyclequeue);
for (let i = 0; i < keys.length; i++) {
let cur = cyclequeue[keys[i]];
if (!Sufx[keys[i]]) Sufx[keys[i]] = [];
// if(cur.value !== '-7')
switch (cur.getType()) {
case 'double':
Sufx[keys[i]].push(cur.left);
Sufx[keys[i]].push(cur.right);
break;
default:
Sufx[keys[i]].push(cur.next_num);
break;
// break;
}
}
return Sufx;
}
function alter_Dom_Comp(cyclequeue, Pred, r, ord) {
let change = true;
let Domain = {};
r = Number(r);
let dom = {};
// 初始化支配者集合
dom[r] = new FastBitSet([r]);
let keys = Object.keys(cyclequeue).map(Number);
for (let i = 0; i < keys.length; i++) {
if (keys[i] !== r) {
dom[keys[i]] = new FastBitSet(keys);
}
Domain[keys[i]] = new FastBitSet();
}
ord = ord.map(Number);
while (change) {
change = false;
for (let i = 0; i < ord.length; i++) {
const u = ord[i];
if (u !== r) {
let T = dom[u].clone();
for (let j = 0; j < Pred[u].length; j++) {
const pred = Pred[u][j];
T.intersection(dom[Number(pred)]);
}
T.add(u); // 将当前节点 u 添加到支配者集合
if (!T.equals(dom[u])) {
change = true;
dom[u] = T.clone(); // 更新支配者集合
}
}
}
}
// for(let u of keys){
// dom[u] = dom[u].array();
// }
return dom;
}
//当前节点的支配节点中包含一个当前节点的前置节点/此前置节点处于DFS遍历顺序中这差不多就是回边的定义了,如果需要检测continue,大概需要保存一个回边列表
function setCyclenode(cyclequeue, initvalue, Dom) {
let rec = [];
function dfs(curnode) {
if (rec.includes(curnode)) return;
rec.push(curnode);
if (cyclequeue[curnode].getType() === 'single') {
if (rec.includes(cyclequeue[curnode].next_num) && Dom[curnode].has(Number(cyclequeue[curnode].next_num))) {
cyclequeue[cyclequeue[curnode].next_num].backedge = curnode;
}
dfs(cyclequeue[curnode].next_num);
} else {
if (cyclequeue[curnode].left) {
if (rec.includes(cyclequeue[curnode].left) && Dom[curnode].has(Number(cyclequeue[curnode].left))) {
cyclequeue[cyclequeue[curnode].left].backedge = curnode;
}
dfs(cyclequeue[curnode].left);
}
if (cyclequeue[curnode].right) {
if (rec.includes(cyclequeue[curnode].right) && Dom[curnode].has(Number(cyclequeue[curnode].right))) {
cyclequeue[cyclequeue[curnode].right].backedge = curnode;
}
dfs(cyclequeue[curnode].right);
}
}
}
dfs(initvalue);
}
function setLoopnode(cyclequeue, initvalue, pred, sufx) {
let rec = [];
function dfs(cur) {
if (rec.includes(cur)) {
return;
}
rec.push(cur);
// if(cur == '6947')debugger;
if (cyclequeue[cur].backedge) {
let loop = Nat_Loop(cyclequeue[cur].backedge, cur, pred, sufx);
let temp = loop.array();
//我猜这里当时是为了处理多个break写的
let exit = Get_Exit(temp, BDom);
cyclequeue[cur].exit = exit;
for (let i = 0; i < temp.length; i++) {
cyclequeue[temp[i]].loop1 = loop;
// if (!cyclequeue[temp[i]].backedge)
cyclequeue[temp[i]].backedge1 = cyclequeue[cur].backedge;
cyclequeue[temp[i]].headers = cur;
cyclequeue[temp[i]].exit1 = exit;
}
}
if (cyclequeue[cur].next_num !== '-1' && cyclequeue[cur].next_num !== '40000') {
dfs(cyclequeue[cur].next_num);
}
if (cyclequeue[cur].left) {
dfs(cyclequeue[cur].left);
}
if (cyclequeue[cur].right) {
dfs(cyclequeue[cur].right);
}
}
dfs(initvalue);
}
//由于自然循环只有n这一个入口,所以当我检索前置结点直到n停止时,就遍历了循环内所有点
function Nat_Loop(m, n, Pred, Succ) {
// let Loop = new Set([m,n]);
let Loop = new FastBitSet([m, n]);
let Stack = [m];
let count = 0;
while (Stack.length > 0) {
let p = Stack.pop();
for (let q of Pred[p] || []) {
// 关键:判断这个前驱 q 是否有路径能通向回边源 n
if (!Loop.has(q)) {
Loop.add(q);
Stack.push(q);
}
}
}
return Loop;
}
//exit不等于回边起点,指自然循环的下一个点
function Get_Exit(Loop, BDom) {
let Exit = undefined;
for (let val of Loop) {
if (Exit === undefined) {
Exit = BDom[val].clone();
} else {
Exit.intersection(BDom[val]);
}
}
Exit = Exit.array();
let ext = -1;
let ans = '0';
for (let val of Exit) {
if (BDom[val].size() > ext) {
ans = val;
ext = BDom[val].size();
}
}
return String(ans);
}
//获取后序遍历顺序
function getPostorder(curnode, sufx) {
let visited = [];
let postlist = [];
function dfs(curnode) {
visited.push(curnode);
if (sufx[curnode]) {
for (let i = 0; i < sufx[curnode].length; i++) {
if (sufx[curnode][i] && (!visited.includes(sufx[curnode][i]))) {
dfs(sufx[curnode][i]);
}
}
}
postlist.push(String(curnode));
}
dfs(curnode);
return postlist;
}
//通过图和初始节点获取图中所有循环节点信息
function decode(initvalue, cyclequeue) {
//前置节点
pred = getPred(cyclequeue);
// 后置节点
sufx = getSufx(cyclequeue);
//逆序
preord1 = getPostorder(initvalue, sufx);
//正序
preord2 = getPostorder('40000', pred);
Dom = alter_Dom_Comp(cyclequeue, pred, initvalue, preord1);
//针对不同的循环decode之后,还是要做单独的处理,因为目前没有很好的办法识别出循环的出口点,break和return无法有效做出区分
//这种情况下对一些循环的还原就没办法确定next_num,所以还是要在初步还原基础循环以及if结构以后,去分析控制流图,大致确定流程以后再去还原。
//控制流起点
// Dom = Dom_Comp(num_count,pred,initvalue);
//控制流终点,这里我不清楚是不是需要预处理一个出口结点,因为很显然可能会有很多出口
//目前能想到的寻找出口节点的方法就是把所有的break收集起来,逆向求最近公共父节点。
BDom = alter_Dom_Comp(cyclequeue, sufx, '40000', preord2);
setCyclenode(cyclequeue, initvalue, Dom);
setLoopnode(cyclequeue, initvalue, pred, sufx);
// debugger;
//循环首节点回边首节点以及回边起点,这里的回边应该针对这个首节点的最后一条回边?
//这里是找出循环内所有结点,这部分可以到缩点的时候做
}
function minusordelete(nodeindex) {
if (cyclequeue[nodeindex].num <= 1) {
delete cyclequeue[nodeindex];
} else {
cyclequeue[nodeindex].num--;
}
}
function tanssingletodouble(nodeindex, nextindex) {
cyclequeue[nodeindex].next_num = nextindex;
cyclequeue[nodeindex].test = undefined;
cyclequeue[nodeindex].left = undefined;
cyclequeue[nodeindex].right = undefined;
}
function addtailstat(codeitem) {
let stat;
if (codeitem.next_num !== '-1' && codeitem.next_num !== '40000') {
stat = t.expressionStatement(t.assignmentExpression('=', t.identifier(var_name), t.valueToNode(parseInt(codeitem.next_num))));
}
if (codeitem.next_num === '-1') {
stat = t.expressionStatement(t.conditionalExpression(codeitem.test, t.assignmentExpression('=', t.identifier(var_name), t.valueToNode(parseInt(codeitem.left))), t.assignmentExpression('=', t.identifier(var_name), t.valueToNode(parseInt(codeitem.right)))));
}
return stat;
}
function combineswitch(addtail, empty) {
let sw = t.switchStatement(t.identifier(var_name), []);
if (!empty) {
for (i in cyclequeue) {
if (i !== '40000') {
if (addtail) {
let stat = addtailstat(cyclequeue[i]);
if (stat) {
cyclequeue[i].code.push(stat);
}
}
cyclequeue[i].code.push(t.breakStatement());
sw.cases.push(t.switchCase(t.valueToNode(parseInt(i)), cyclequeue[i].code));
}
}
} else {
for (i in cyclequeue) {
if (i !== '40000') {
if (cyclequeue[i].next_num === '-1' || cyclequeue[i].num >1) {
cyclequeue[i].code = [];
if (addtail) {
let stat = addtailstat(cyclequeue[i]);
if (stat) {
cyclequeue[i].code.push(stat);
}
}
cyclequeue[i].code.push(t.breakStatement());
debugger;
sw.cases.push(t.switchCase(t.valueToNode(parseInt(i)), cyclequeue[i].code));
}
}
}
}
const visitor = {
SwitchStatement(path) {
path.replaceWith(sw);
path.stop()
}
}
traverse(ast, visitor)
}
//不使用引用次数的问题是缩点的时候如果其他地方也使用了这个点就会很尴尬了
//第二种是像以前的一样把所有点的引用次数统计出来,不断地缩点删减引用,直到队列中只有一个点
//在缩点之前最好是能把虚假分支去了,顺便验证一下,不过虚假分支需要插桩,我感觉不一定好用例如BOSS之类的,可能需要你解一定程度的混淆,然后找到格式化监测点
//缩点过程中确保可以跑,最好是先把单节点缩点做了,然后再观察一下分支的结构test之类的,例如这里的分支结构就是把判断的test拆分到了前几个节点中,合并后需要自己设置一下test节点才方便进行下一步
//一个是要把falselog的地方优化一下,一个是要把break和cyclequeue的末尾那一行优化一下
let size1 = 0;
let count = 0;
let target = 3;//228
let startdouble = 0;
let addtail = true;
while (1) {
// debugger
console.log(Object.keys(cyclequeue).length);
if (Object.keys(cyclequeue).length === size1) {
// if(startdouble === 1) combineswitch(addtail);
if (startdouble === 4){
combineswitch(addtail, false);break;
} else if (startdouble === 0) {
startdouble = 1;
} else if (startdouble === 1) {
//清理空节点,即只包含跳转的语句,直接把链接到跳转节点去
// debugger;
let emptynodes = [];
for (let i in cyclequeue) {//分支节点不能缩,因为有可能有循环会指回这个点
if (cyclequeue[i].next_num !== '-1' && cyclequeue[i].next_num !== '40000') {
if (cyclequeue[i].code.length === 0) {
emptynodes.push(i);
}
}
}
for (let i in cyclequeue) {
if (i === '40000') continue;
// if(i === '123') debugger;
let nextnum = cyclequeue[i].next_num;//123 -> 420 -> 418
if (nextnum !== '-1') {
if (emptynodes.includes(nextnum)) {
cyclequeue[i].next_num = cyclequeue[nextnum].next_num;
}
} else {
if (emptynodes.includes(cyclequeue[i].left)) {
cyclequeue[i].left = cyclequeue[cyclequeue[i].left].next_num;
// debugger;
}
if (emptynodes.includes(cyclequeue[i].right)) {
cyclequeue[i].right = cyclequeue[cyclequeue[i].right].next_num;
// debugger;
}
}
}
for (let i in cyclequeue) {
if (emptynodes.includes(i)) {
delete cyclequeue[i];
}
}
// debugger;
// break;
//点集处理没问题,接下来考虑continue和break的识别还原,到这里初步的if和while还原完毕
decode(445, cyclequeue);//需要对每个调用的函数进行分析,因为入口不同
decode(630, cyclequeue);
debugger;
startdouble = 4;
}
// debugger;
}
size1 = Object.keys(cyclequeue).length;
// debugger;
for (let i in cyclequeue) {
console.log(i)
if (i === "40000") continue;
let nextnum = cyclequeue[i].next_num;
// if (i == '216') debugger;
// console.log(i, nextnum);
// // if (i === '123'|| i === '443'||i === '614') debugger;
// // if(count>target)debugger;
if (cyclequeue[i].getType() === "single") {
let nextnode = cyclequeue[nextnum];
if (nextnode === undefined) continue; //控制流不以40000结束而是在循环里return的循环
if (nextnode.num <= 1) {
//单节点连接单引用合并
cyclequeue[i].assign(cyclequeue[i], cyclequeue[nextnum]);
delete cyclequeue[nextnum];
} else if (nextnode.next_num === "40000") {
cyclequeue[i].assign(cyclequeue[i], cyclequeue[nextnum]);
minusordelete(nextnum);
}
if (startdouble === 4 && cyclequeue[i].next_num === i) {
//理论上来说所有的平坦化节点应该都会被缩为一个单节点把?毕竟她们都使用break离开循环
cyclequeue[i].code = [t.whileStatement(t.valueToNode(1), t.blockStatement(cyclequeue[i].code)),];
cyclequeue[i].next_num = cyclequeue[i].exit2; //出口节点,不过这里的处理是用肉眼看出来的
cyclequeue[i].num--;
if (cyclequeue[i].break)
//如果循环里包含break语句,不包含的话就不用删除出口节点的引用了
cyclequeue[cyclequeue[i].next_num].num -= cyclequeue[i].break;
}
} else if (cyclequeue[i].getType() === "double" && startdouble) {
let left = cyclequeue[i].left;
let right = cyclequeue[i].right;
let l_next = cyclequeue[left].next_num;
let r_next = cyclequeue[right].next_num;
//单if
if ((left === r_next && left !== "40000") || (right === l_next && right !== "40000")) {
let index1, index2, index3, test; //12分别为对应分支的数字下标,3是结尾处添加的新单向跳转分支
if (left === r_next && left !== "40000") {
index1 = right;
index2 = left;
index3 = "left";
test = t.unaryExpression("!", cyclequeue[i].test);
} else if (right === l_next && right !== "40000") {
index1 = left;
index2 = right;
index3 = "right";
test = cyclequeue[i].test;
}
// cyclequeue[i].code.splice(cyclequeue[i].code.length - 1, 1);
cyclequeue[i].code = cyclequeue[i].code.concat(t.ifStatement(test, t.blockStatement(cyclequeue[index1].code)));
// cyclequeue[i].code = cyclequeue[i].code.concat(t.expressionStatement(t.assignmentExpression("=", t.identifier(var_name), t.valueToNode(parseInt(right)))));
tanssingletodouble(i, index2);
minusordelete(index1);
//非缩点的分支就先不在这里删除了
cyclequeue[index2].num--;
}
//if else结构
if (l_next !== "40000" && l_next === r_next) {
cyclequeue[i].code = cyclequeue[i].code.concat(t.ifStatement(cyclequeue[i].test, t.blockStatement(cyclequeue[left].code), t.blockStatement(cyclequeue[right].code)));
tanssingletodouble(i, l_next);
minusordelete(left);
minusordelete(right);
cyclequeue[l_next].num--;
}
if (l_next === '40000' && r_next === '40000') {
cyclequeue[i].code.push(t.ifStatement(cyclequeue[i].test, t.blockStatement(cyclequeue[left].code), t.blockStatement(cyclequeue[right].code)));
cyclequeue[i].next_num = undefined;
}
//if break/return处理复杂的控制流,包含多个break以及continue的控制流
if (startdouble === 4 && cyclequeue[i].loop1) {
//这里写的不是很通用,因为一些分支结构不一定是按照这种模式来的,需要根据具体的图来写
let loopnodes = cyclequeue[i].loop1.array();
let origincode = cyclequeue[i].code; //原始分支代码,应该和下面的跳转代码分开,跳转代码连接起来丢到if里最后添加一个break
//把原始跳转切换为单跳转,然后把跳转代码插入到-2的位置
let branchcode = [];
let deletenodes = [];
if (loopnodes.includes(parseInt(left)) && !loopnodes.includes(parseInt(right))) {
while (cyclequeue[right].next_num !== "-1" && right !== "40000" && right !== cyclequeue[right].backedge1) {
branchcode = branchcode.concat(cyclequeue[right].code);
deletenodes.push(right);
right = cyclequeue[right].next_num;
}
if ((cyclequeue[right] && right === cyclequeue[right].backedge1) || right === "40000") {
if (right === cyclequeue[right].backedge1) {
branchcode = [t.ifStatement(t.unaryExpression("!", cyclequeue[i].test), t.blockStatement(branchcode.concat(t.breakStatement()))),];
if (!cyclequeue[cyclequeue[i].headers].exit2) cyclequeue[cyclequeue[i].headers].exit2 = cyclequeue[right].backedge1; //出口节点,临时补丁不一定是回边
if (cyclequeue[cyclequeue[i].headers].break === undefined) cyclequeue[cyclequeue[i].headers].break = 0; //循环中包含的break数量,不设定为1是因为方便后面设定出口节点引用次数
else cyclequeue[cyclequeue[i].headers].break++;
}
if (right === "40000") {
//
branchcode = [t.ifStatement(t.unaryExpression("!", cyclequeue[i].test), t.blockStatement(branchcode)),];
origincode = origincode.concat(branchcode);
}
// origincode.splice(origincode.length - 1, 1);
origincode = origincode.concat(branchcode);
cyclequeue[i].code = origincode;
tanssingletodouble(i, left);
}
// debugger;
} //如果有两个节点连接到97,97又连接到309,这样实际上只能删除一次309,因为控制流里97只对应了一次309?
if (loopnodes.includes(parseInt(right)) && !loopnodes.includes(parseInt(left))) {
//遇到re语句要推出
while (cyclequeue[left] && cyclequeue[left].next_num !== "-1" && left !== "40000" && left !== cyclequeue[left].backedge1) {
//这里的backedge1也不对,breka循环不一定是直接跳到外层循环底部,只是这里刚好是
branchcode = branchcode.concat(cyclequeue[left].code);
deletenodes.push(left);
left = cyclequeue[left].next_num;
}
if ((cyclequeue[left] && left === cyclequeue[left].backedge1) || left === "40000") {
if (cyclequeue[left] && left === cyclequeue[left].backedge1) {
branchcode = [t.ifStatement(cyclequeue[i].test, t.blockStatement(branchcode.concat(t.breakStatement()))),];
if (!cyclequeue[cyclequeue[i].headers].exit2) cyclequeue[cyclequeue[i].headers].exit2 = cyclequeue[left].backedge1;
if (cyclequeue[cyclequeue[i].headers].break === undefined) cyclequeue[cyclequeue[i].headers].break = 0; else cyclequeue[cyclequeue[i].headers].break++;
}
if (left === "40000") {
//
branchcode = [t.ifStatement(cyclequeue[i].test, t.blockStatement(branchcode)),];
}
// origincode.splice(origincode.length - 1, 1);
origincode = origincode.concat(branchcode);
cyclequeue[i].code = origincode;
tanssingletodouble(i, right);
}
// if(left === '40000' || left === cyclequeue[left].backedge1){
// cyclequeue[left]
// }
// debugger;
}
for (let i in deletenodes) {
minusordelete(deletenodes[i]);
}
}
//循环结构
if ((l_next === i + "" || r_next === i + "") && startdouble === 1) {
if (l_next === i + "") {
// if (cyclequeue[i].code.length === 1) {
// cyclequeue[i].code = [t.whileStatement(cyclequeue[i].test, t.blockStatement(cyclequeue[left].code)),];
// } else {
cyclequeue[i].code.push(t.ifStatement(t.unaryExpression("!", cyclequeue[i].test), t.blockStatement([t.breakStatement()])));
cyclequeue[i].code = [t.whileStatement(t.valueToNode(1), t.blockStatement(cyclequeue[i].code.concat(cyclequeue[left].code))),];
// }
// cyclequeue[i].code = cyclequeue[i].code.concat(t.expressionStatement(t.assignmentExpression("=", t.identifier(var_name), t.valueToNode(parseInt(right)))));
tanssingletodouble(i, right);
minusordelete(left);
} else if (r_next === i + "") {
// if (cyclequeue[i].code.length === 1) {
// cyclequeue[i].code = [t.whileStatement(t.unaryExpression("!", cyclequeue[i].test), t.blockStatement(cyclequeue[right].code)),];
// } else {
cyclequeue[i].code.push(t.ifStatement(cyclequeue[i].test, t.blockStatement([t.breakStatement()])));
cyclequeue[i].code = [t.whileStatement(t.valueToNode(1), t.blockStatement(cyclequeue[i].code.concat(cyclequeue[right].code))),];
// }
// cyclequeue[i].code = cyclequeue[i].code.concat(t.expressionStatement(t.assignmentExpression("=", t.identifier(var_name), t.valueToNode(parseInt(left)))));
tanssingletodouble(i, left);
minusordelete(right);
}
cyclequeue[i].num--; //当前节点少一个指回
}
}
// if (!generator(t.blockStatement(cyclequeue[101].code)).code.includes('p44 = p20 < 16;'))debugger;
}
}
}
function dealStr(ast) {
let str = `function de(str){
let result;
switch(str){
case '
﮷ﮡﯰ':
result = 'log';
break;
case 'ﯚﮒﯤﯲ﮺ﯘﯲﮩ':
result ='__proto__';
break;
case 'ﮓﮡﮡﰀﯬﯙﯽ﯁ﯬ﯁':
result ='constructor';
break;
case 'ﮓﮰﯩ':
result ='call';
break;
case 'ﮰﮡ':
result ='bind';
break;
case 'ﮍﮛﯤﯩ':
result ='apply';
break;
case 'ﮨﮡﯣﯙﰀﮨ':
result ='toString';
break;
case '
ﮡ﮺ﮛﯙ﮺ﯘﯭﯙﮑ':
result ='prototype';
break;
case 'ﰀﮰﯬﯰﯔ﮷':
result ='valueOf';
break;
case 'ﮩ﮺ﮛﮡ﮽ﮑﯘﮎﯬﮡ':
result ='fromCharCode';
break;
case 'ﮓﯰﮓﯲ﮽ﮨ﮷ﮭ':
result ='charCodeAt';
break;
case '
﮺ﰀﰀﯬﯙﯩﯲﯩ':
result ='substring';
break;
case 'ﯰﯩﮩﮗ﯁ﯔ﮷':
result ='indexOf';
break;
case '
﮺ﮩﮓﯲﮑ':
result ='search';
break;
case 'ﮑﮩﯲﰀ﯂﮷ﯪ':
result ='message';
break;
case '
ﱣﯲ ':
result ='is';
break;
case 'ﮓﮰﯩﯰﯙ':
result ='caller';
break;
case 'ﮟﯖﱞﰃﯯﱞﰁﰅﯱﯷﰃ':
result ='(((.+)+)+)+$';
break;
default:
break;
}
console.log(str, result);
return result;
}`
let fun_name = 'p6';
const visitor = {
FunctionExpression(path) {
// p197.caller.length
// debugger;
let node = path.node;
if (path.parentPath.node.type === 'ReturnStatement') {
debugger;
if (node.body.body[2] && node.body.body[2].type === 'ForStatement') {
debugger;
const ast1 = babel.parseSync(str);
const funcExpr = t.functionExpression(
node.id, // 函数名
ast1.program.body[0].params, // 参数
ast1.program.body[0].body, // 函数体
ast1.program.body[0].generator, // 是否是生成器函数
ast1.program.body[0].async // 是否是异步函数
);
path.replaceWith(funcExpr)
// debugger;
// let fnode = node.body.body[2];
// let fpath = path.get('body').get('body')[3];
// let init1 = fnode.init.declarations[0];
// init1.init = t.valueToNode(58);
// let init2 = fnode.init.declarations[1];
// init2.init = t.valueToNode(1);
// // fnode.init.declarations[0].
// const expr1 = parser.parseExpression("console.log('|',ﱞﹲ,'|',ءיּ)");
// fpath.insertBefore(t.expressionStatement(expr1));
//
// debugger;
} else {
path.parentPath.parentPath.get('body')[0].remove();
// debugger;
}
}
}
}
// traverse(ast, visitor);
const visitor1 = {
CallExpression(path) {
let node = path.node;
if (node.callee.type === 'Identifier' && node.callee.name === fun_name) {
let str1 = str + '\n' + `let res = de('` + node.arguments[0].value + `');\nres;`;
let res = eval(str1);
if (res)//419 undefined
path.replaceWith(t.stringLiteral(res));
// debugger;
}
}
}
traverse(ast, visitor1);
}
// function
// while(a)if (a==1){}
addbraces(ast)
uniVarname(ast)
// reverseUnicode(ast)
tripletoif()// p54 = p3(p48, p0[p9((p10 = 1089) + 67320)])();
ifToSwitch(ast)
switchtriple(ast)
deleteFalse(ast)
finallyCombined(ast)
dealStr(ast);
// addlog(ast)
let output_code = generator(ast, {
// comments: false, // false为删除所有注释
// retainLines: false, // 是否保留多余空行: true为保留 false为不保留
// compact: true, // 压缩代码
// minified:true
}).code;
fs.writeFile(writePath, output_code, (err) => {
});const original = String.prototype.search;
function hookedSearch(...args) {
console.log(
[HOOK] called, this, args);
return original.apply(this, args);
}// 关键代码:删除或设为 undefined
Object.defineProperty(hookedSearch, 'prototype', {
value: undefined,
writable: false,
configurable: false,
});
String.prototype.search = hookedSearch;
不用这种方式hook会因为这个触发错误:class A extends String.prototype.search {} // ❌ TypeError: Class extends value does not have valid prototype property
因为只有原始函数才不能被hook,所以这里一定要把value设置为undefined才能抛出错误。
如果更进一步可以使用:
const f = () => {};
// 或者 const f = {};
class A extends f {}
箭头函数来定义
.png?table=block&id=e721439b-67f8-4fde-8e9a-291fca4d6796&cache=v2)
