From 158be14d87d6eee404533da059055807d15226b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=B5=E3=81=81?= Date: Sun, 27 Apr 2025 16:17:08 +0900 Subject: [PATCH] add fork MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: ふぁ --- deobf-fork.js | 737 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 737 insertions(+) create mode 100644 deobf-fork.js diff --git a/deobf-fork.js b/deobf-fork.js new file mode 100644 index 0000000..0212701 --- /dev/null +++ b/deobf-fork.js @@ -0,0 +1,737 @@ +const fs = require("fs"); +const t = require("@babel/types"); +const parser = require("@babel/parser"); +const traverse = require("@babel/traverse").default; +const generate = require("@babel/generator").default; +const vm = require("vm"); +const { readFileSync, writeFile, writeFileSync } = require("fs"); +const { exit } = require("process"); +var output = ""; + +let beautify_opts = { + comments: true, + minified: false, + concise: false, +}; +const [_, __, inputPath, outputPath] = process.argv; +const script = readFileSync(inputPath, "utf-8"); + +const AST = parser.parse(script, {}); + +var decryptFuncCtx = vm.createContext(); +var decryptCode = ""; +var decryptFuncName = ""; + +const constantReplacer = { + VariableDeclarator(path) { + const { node } = path; + if ( + !node.id || + !node.init || + (node.init.type != "StringLiteral" && + node.init.type != "NumericLiteral") || + node.id.type != "Identifier" + ) { + return; + } + if ( + (node.init.type == "NumericLiteral" && node.init.value == 0) || + (node.init.type == "StringLiteral" && node.init.value == "") + ) { + return; + } + let binding = path.scope.getBinding(node.id.name); + if (!binding) { + return; + } + for (var i = 0; i < binding.referencePaths.length; i++) { + binding.referencePaths[i].replaceWith(node.init); + } + path.remove(); + }, +}; + +const replaceObjSimple = { + VariableDeclarator(path) { + const { node } = path; + if ( + !node.id || + !node.init || + node.init.type != "ObjectExpression" || + node.id.type != "Identifier" || + node.init.properties.length < 1 + ) { + return; + } + var valid = true; + var map = {}; + for (var i = 0; i < node.init.properties.length; i++) { + var prop = node.init.properties[i]; + if ( + !prop.key || + !prop.value || + prop.key.type != "Identifier" || + (prop.value.type != "NumericLiteral" && + prop.value.type != "StringLiteral") + ) { + valid = false; + break; + } + map[prop.key.name] = prop.value; + } + if (!valid) { + return; + } + path.scope.crawl(); + let binding = path.scope.getBinding(node.id.name); + if (!binding) { + return; + } + for (var i = 0; i < binding.referencePaths.length; i++) { + let refPath = binding.referencePaths[i].parentPath; + if (refPath.node.type != "MemberExpression" || !refPath.node.property) { + continue; + } + let key; + if (refPath.node.property.type == "Identifier") { + key = refPath.node.property.name; + } else { + key = refPath.node.property.value; + } + refPath.replaceWith(map[key]); + } + path.remove(); + }, +}; + +const replaceExprStmts = { + MemberExpression(path) { + const { node } = path; + if ( + !node.property || + node.property.type != "SequenceExpression" || + !node.property.expressions || + node.property.expressions.length < 3 + ) { + return; + } + var callExprIndex = node.property.expressions.length - 1; + if (node.property.expressions[callExprIndex].type != "CallExpression") { + return; + } + var values = []; + var order = []; + for (var i = 0; i < node.property.expressions.length; i++) { + var expr = node.property.expressions[i]; + if (expr.type != "AssignmentExpression" || !expr.right || !expr.left) { + continue; + } + values.push(generate(expr.right).code); + order.push(expr.left.name); + } + let newArgs = []; + for ( + var i = 0; + i < node.property.expressions[callExprIndex].arguments.length; + i++ + ) { + let arg = node.property.expressions[callExprIndex].arguments[i]; + let str = generate(arg).code; + if (str.match(/[A-z]/g) == null) { + newArgs.push(arg); + continue; + } + let key = str.match(/[A-z]/g)[0]; + let index = order.indexOf(key); + str = str.replace(key, values[index]); + if ( + str.match(/[0-9]/g) != null && + str.match(/[0-9]/g).length > 1 && + !str.match(/[A-z"]/g) + ) { + newArgs.push(t.numericLiteral(eval(str))); + continue; + } + str = str.slice(1); + str = str.slice(0, -1); + newArgs.push(t.stringLiteral(str)); + } + path.replaceWith( + t.memberExpression( + node.object, + t.callExpression( + node.property.expressions[callExprIndex].callee, + newArgs + ), + true + ) + ); + }, + // ! same thing except ExpressionStatement, SequenceExpression + // ! example: (a = "7d]D", k = -497, m = -404, C = -368, uo(k - -1644, a - 298, a, m - 199, C - 208)) + SequenceExpression(path) { + const { node } = path; + if (!node.expressions || node.expressions.length < 3) { + return; + } + var callExprIndex = node.expressions.length - 1; + if (node.expressions[callExprIndex].type != "CallExpression") { + return; + } + var values = []; + var order = []; + for (var i = 0; i < node.expressions.length; i++) { + var expr = node.expressions[i]; + if (expr.type != "AssignmentExpression" || !expr.right || !expr.left) { + continue; + } + values.push(generate(expr.right).code); + order.push(expr.left.name); + } + let newArgs = []; + for (var i = 0; i < node.expressions[callExprIndex].arguments.length; i++) { + let arg = node.expressions[callExprIndex].arguments[i]; + let str = generate(arg).code; + if (str.match(/[A-z]/g) == null) { + newArgs.push(arg); + continue; + } + let key = str.match(/[A-z]/g)[0]; + let index = order.indexOf(key); + str = str.replace(key, values[index]); + if ( + str.match(/[0-9]/g) != null && + str.match(/[0-9]/g).length > 1 && + !str.match(/[A-z"]/g) + ) { + newArgs.push(t.numericLiteral(eval(str))); + continue; + } + str = str.slice(1); + str = str.slice(0, -1); + newArgs.push(t.stringLiteral(str)); + } + path.replaceWith( + t.callExpression(node.expressions[callExprIndex].callee, newArgs) + ); + }, +}; + +const replaceWeirdProxyCall = { + MemberExpression(path) { + const { node } = path; + if ( + !node.object || + node.object.type != "Identifier" || + !node.property || + node.property.type != "CallExpression" + ) { + return; + } + if ( + !node.property.callee || + node.property.callee.type != "FunctionExpression" + ) { + return; + } + let values = [ + generate(node.property.arguments[0]).code, + generate(node.property.arguments[1]).code, + generate(node.property.arguments[2]).code, + generate(node.property.arguments[3]).code, + generate(node.property.arguments[4]).code, + ]; + let order = [ + node.property.callee.params[0].name, + node.property.callee.params[1].name, + node.property.callee.params[2].name, + node.property.callee.params[3].name, + node.property.callee.params[4].name, + ]; + let newArgs = []; + for ( + var i = 0; + i < node.property.callee.body.body[0].argument.arguments.length; + i++ + ) { + let arg = node.property.callee.body.body[0].argument.arguments[i]; + let str = generate(arg).code; + if (str.match(/[A-z]/g) == null) { + newArgs.push(arg); + continue; + } + let key = str.match(/[A-z]/g)[0]; + let index = order.indexOf(key); + str = str.replace(key, values[index]); + if ( + str.match(/[0-9]/g) != null && + str.match(/[0-9]/g).length > 1 && + !str.match(/[A-z"]/g) + ) { + newArgs.push(t.numericLiteral(eval(str))); + continue; + } + str = str.slice(1); + str = str.slice(0, -1); + newArgs.push(t.stringLiteral(str)); + } + path.replaceWith( + t.memberExpression( + node.object, + t.callExpression( + node.property.callee.body.body[0].argument.callee, + newArgs + ), + true + ) + ); + }, +}; + +const getStringDeobfFuncs = { + ExpressionStatement(path) { + const { node } = path; + if ( + !node.expression || + node.expression.operator != "!" || + !node.expression.prefix || + !node.expression.argument || + node.expression.argument.type != "CallExpression" + ) { + return; + } + // ! get array func + let binding = path.scope.getBinding( + node.expression.argument.arguments[0].name + ); + if (!binding) { + return; + } + decryptCode += generate(binding.path.node).code + "\n"; + // ! get decrypt func + var bodyIndex = 0; + for (var i = 0; i < node.expression.argument.callee.body.body.length; i++) { + if ( + node.expression.argument.callee.body.body[i].type == + "FunctionDeclaration" + ) { + bodyIndex = i; + break; + } + } + decryptFuncName = + node.expression.argument.callee.body.body[bodyIndex].body.body[0].argument + .callee.name; + path.scope.crawl(); + let binding1 = path.scope.getBinding(decryptFuncName); + if (!binding1) { + return; + } + decryptCode += generate(binding1.path.node).code + "\n"; + decryptCode += generate(node).code + "\n"; + binding1.path.remove(); + binding.path.remove(); + path.remove(); + path.stop(); + }, +}; + +function makeid(length) { + let result = ""; + const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + const charactersLength = characters.length; + let counter = 0; + while (counter < length) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + counter += 1; + } + return result; +} + +const replaceInterceptingFuncNames = { + FunctionDeclaration(path) { + const { node } = path; + if ( + !node.id || + node.id.type != "Identifier" || + node.id.name != decryptFuncName || + !node.body || + !node.body.body || + node.body.body.length != 1 + ) { + return; + } + path.scope.crawl(); + let binding = path.parentPath.scope.getBinding(node.id.name); + if (!binding) { + return; + } + var ID = t.identifier(makeid(10)); + for (var i = 0; i < binding.referencePaths.length; i++) { + binding.referencePaths[i].replaceWith(ID); + } + node.id = ID; + }, +}; + +const deobfStrings = { + CallExpression(path) { + const { node } = path; + + if ( + !node.callee || + node.callee.type != "Identifier" || + !node.arguments || + node.arguments.length < 2 + ) { + return; + } + var valid = true; + for (var i = 0; i < node.arguments.length; i++) { + var arg = node.arguments[i]; + let str = generate(arg).code; + if (arg.type == "StringLiteral" || str == "NaN") { + continue; + } + if ( + arg.type != "UnaryExpression" && + arg.type != "BinaryExpression" && + arg.type != "NumericLiteral" + ) { + valid = false; + break; + } + + if (str.match(/[A-z]/g) != null) { + valid = false; + break; + } + } + if (!valid) { + return; + } + // ! the logic here is we want to get the function this is calling + // ! then we want to keep getting the nested function calls until we get to the final function, aka the decryptFuncName + let code = ""; + path.scope.crawl(); + let binding = path.scope.getBinding(node.callee.name); + if (!binding) { + // ! hopefully no binding will always mean that the function in question is `r`??? + path.replaceWith( + t.valueToNode(vm.runInContext(generate(node).code, decryptFuncCtx)) + ); + return; + } + // ! loop until we get to a place where we can't get a binding (aka hopefully the root function) + while (true) { + if (!binding) { + let a = generate(node).code; + if (a[0] == decryptFuncName) { + a[0] = "asd"; + } + code += a; + break; + } + code += generate(binding.path.node).code + "\n"; + path.scope.crawl(); + binding = binding.path.scope.getBinding( + binding.path.node.body.body[0].argument.callee.name + ); + } + // ! now we should have all the code we need + path.replaceWith(t.valueToNode(vm.runInContext(code, decryptFuncCtx))); + }, +}; + +const deobfuscateStringConcatVisitor = { + BinaryExpression(path) { + let { confident, value } = path.evaluate(); + if (!confident) return; + if (typeof value == "string") { + path.replaceWith(t.stringLiteral(value)); + } + }, +}; + +const getObfioObjs = { + VariableDeclarator(path) { + const { node } = path; + if ( + !node.id || + node.id.type != "Identifier" || + !node.init || + node.init.type != "ObjectExpression" || + !node.init.properties || + node.init.properties.length < 1 + ) { + return; + } + // ! further validation, just incase + let map = {}; + let valid = true; + for (var i = 0; i < node.init.properties.length; i++) { + var prop = node.init.properties[i]; + if (!prop.key || !prop.value || prop.key.type != "Identifier") { + valid = false; + break; + } + if ( + prop.value.type != "FunctionExpression" && + prop.value.type != "StringLiteral" && + prop.value.type != "MemberExpression" + ) { + valid = false; + break; + } + if (prop.key.name.length != 5) { + valid = false; + break; + } + if ( + prop.value.type == "FunctionExpression" && + prop.value.body.body[0].type != "ReturnStatement" + ) { + valid = false; + break; + } + map[prop.key.name] = prop.value; + } + if (!valid) { + return; + } + path.scope.crawl(); + let binding = path.scope.getBinding(node.id.name); + if (!binding) { + return; + } + var ID = t.identifier(makeid(20)); + for (var i = 0; i < binding.referencePaths.length; i++) { + binding.referencePaths[i].replaceWith(ID); + } + obfioObjMap[ID.name] = map; + path.remove(); + }, +}; + +function getArgs(arguments, cutFirst) { + var out = []; + for (var i = cutFirst ? 1 : 0; i < arguments.length; i++) { + out.push(arguments[i]); + } + return out; +} + +const objDeobfMemberExpr = { + MemberExpression(path) { + const { node } = path; + if ( + !node.object || + !node.property || + node.object.type != "Identifier" || + !obfioObjMap[node.object.name] + ) { + return; + } + let map = obfioObjMap[node.object.name]; + let key; + if (node.property.type == "Identifier") { + key = node.property.name; + } else { + key = node.property.value; + } + let value = map[key]; + if (value.type == "StringLiteral") { + path.replaceWith(value); + return; + } + if (value.type == "MemberExpression") { + map = obfioObjMap[value.object.name]; + if (value.property.type == "Identifier") { + key = value.property.name; + } else { + key = value.property.value; + } + value = map[key]; + path.replaceWith(value); + return; + } + output += `FAILED (1): ${generate(node).code}\n\n`; + }, + CallExpression(path) { + const { node } = path; + if ( + !node.callee || + node.callee.type != "MemberExpression" || + !node.callee.object || + !node.callee.property || + node.callee.object.type != "Identifier" || + !obfioObjMap[node.callee.object.name] + ) { + return; + } + let map = obfioObjMap[node.callee.object.name]; + let key; + if (node.callee.property.type == "Identifier") { + key = node.callee.property.name; + } else { + key = node.callee.property.value; + } + let value = map[key]; + // ! replace functions + let retNode = value.body.body[0].argument; + // ! call expression + if (retNode.type == "CallExpression") { + var callExprID; + // ! check if it's a reference to another object + if (retNode.callee.type == "MemberExpression") { + callExprID = retNode.callee; + } else { + callExprID = node.arguments[0]; + } + var args = []; + if ( + node.arguments.length > 1 || + retNode.callee.type == "MemberExpression" + ) { + args = getArgs( + node.arguments, + retNode.callee.type != "MemberExpression" + ); + } + path.replaceWith(t.callExpression(callExprID, args)); + return; + } + // ! BinaryExpression + if (retNode.type == "BinaryExpression") { + path.replaceWith( + t.binaryExpression( + retNode.operator, + node.arguments[0], + node.arguments[1] + ) + ); + return; + } + output += `FAILED (2): ${generate(node).code}\n\n`; + }, +}; + +function evalValue(left, right, op) { + switch (op) { + case "===": + return left == right; + case "!==": + return left != right; + } +} + +const cleanupDeadCode = { + FunctionDeclaration(path) { + const { node } = path; + if ( + !node.id || + node.id.type != "Identifier" || + !node.body || + !node.body.body || + !node.params || + node.params.length < 2 || + node.body.body.length != 1 || + node.body.body[0].type != "ReturnStatement" + ) { + return; + } + path.remove(); + }, + "IfStatement|ConditionalExpression"(path) { + const { node } = path; + if ( + !node.test || + !node.consequent || + node.test.type != "BinaryExpression" || + !node.test.left || + !node.test.right || + node.test.left.type != "StringLiteral" || + node.test.right.type != "StringLiteral" + ) { + // ! handle if(!("x" !== "x")) { } else { } here + if ( + !node.test || + !node.consequent || + node.test.type != "UnaryExpression" || + !node.test.argument || + node.test.argument.type != "BinaryExpression" || + !node.test.argument.left || + !node.test.argument.right || + node.test.argument.left.type != "StringLiteral" || + node.test.argument.right.type != "StringLiteral" + ) { + return; + } + if ( + !evalValue( + node.test.argument.left.value, + node.test.argument.right.value, + node.test.argument.operator + ) + ) { + path.replaceWithMultiple(node.consequent); + return; + } + if (!node.alternate) { + path.remove(); + return; + } + path.replaceWithMultiple(node.alternate); + return; + } + if ( + evalValue(node.test.left.value, node.test.right.value, node.test.operator) + ) { + path.replaceWithMultiple(node.consequent); + return; + } + path.replaceWithMultiple(node.alternate); + }, +}; + +// ! replace the `x = 123` and `y = "asd"` things +traverse(AST, constantReplacer); +// ! replace `const n = {T: 702}` +traverse(AST, replaceObjSimple); +// ! replace mr[(t = "7d]D", o = 896, r(o - 493, t)), mr[(t = "hyP7", r = 661, o = 735, $(t - 252, o - 1061, t, r - 204, o - 6))] +traverse(AST, replaceExprStmts); +/* +replace the stuff that looks like this: +iu[function (n, t, W, r, u) { + return On(n - 247, W, r - 606, r - 38, u - 235); +}(1095, 0, "e9so", 1006, 1053)] +*/ +// ! if this code breaks, I'd assume it's likely because of the static way I'm doing the variable replacement +traverse(AST, replaceWeirdProxyCall); +// ! get the string deobf code and func name, the entry point is the array shifter exprstmt +traverse(AST, getStringDeobfFuncs); +// ! this is a really hacky way to fix this "problem" since I'm doing the string deobf in a bad way +// ! some func names are the same as the main decrypt func name so it'll error when you try to deobf the strings +traverse(AST, replaceInterceptingFuncNames); + +vm.runInContext(decryptCode, decryptFuncCtx); +// ! finally we can decrypt/deobf our strings +traverse(AST, deobfStrings); +// ! now we need to concat strings so we can properly deobf the object obfuscation +// (stolen from pianoman) +traverse(AST, deobfuscateStringConcatVisitor); +let obfioObjMap = {}; +// ! first we need to rename all objects and all uses of those objects +// ! some objects have conflicting names which can mess up this solution, easiest fix is just renaming them +// ! then we will populate the obfioObjMap +traverse(AST, getObfioObjs); +// ! now we can deobf the object obfuscation +traverse(AST, objDeobfMemberExpr); +// ! clean dead code, like the proxy functions we never removed +traverse(AST, cleanupDeadCode); + +writeFileSync("output.log", output, "utf-8"); + +const final_code = generate(AST, beautify_opts).code; + +fs.writeFileSync(outputPath, final_code);