Created
April 25, 2023 00:08
-
-
Save msm-code/c0e401753ee5a8857ebe291265777bc1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const parser = require("@babel/parser"); | |
const traverse = require("@babel/traverse").default; | |
const t = require("@babel/types"); | |
const generate = require("@babel/generator").default; | |
const beautify = require("js-beautify"); | |
const { readFileSync, writeFile } = require("fs"); | |
const vm = require("vm"); | |
const decryptFuncCtx = vm.createContext(); | |
const constantFold = { | |
"BinaryExpression|UnaryExpression"(path) { | |
const { node } = path; | |
if ( | |
t.isUnaryExpression(node) && | |
(node.operator == "-" || node.operator == "void") | |
) | |
return; | |
let { confident, value } = path.evaluate(); | |
if (!confident || value == Infinity || value == -Infinity) return; | |
let actualVal = t.valueToNode(value); | |
path.replaceWith(actualVal); | |
}, | |
}; | |
const deobfuscateEncodedStringVisitor = { | |
StringLiteral(path) { | |
if (path.node.extra) delete path.node.extra; | |
}, | |
}; | |
const splitDeclarators = { | |
VariableDeclaration(path) { | |
if (path.node.kind === 'var' || path.node.kind === 'let' || path.node.kind === 'const') { | |
const declarators = path.node.declarations; | |
if (declarators.length > 1) { | |
declarators.forEach(declarator => { | |
const declaration = t.variableDeclaration(path.node.kind, [declarator]); | |
path.insertBefore(declaration); | |
}); | |
path.remove(); | |
} | |
} | |
} | |
}; | |
function maybeAddToScope(path, funcName, vm, ctx) { | |
var bind = path.scope.getBinding(funcName); | |
if (bind) { | |
vm.runInContext(generate(bind.path.node).code, ctx); | |
} | |
} | |
const callAndApplyToDirectCall = { | |
CallExpression(path) { | |
const node = path.node; | |
const callee = node.callee; | |
if (callee.type == "MemberExpression") { | |
if (callee.property.name == "call") { | |
const args = path.node.arguments.slice(1); | |
const newT = t.callExpression(t.identifier(callee.object.name), args); | |
path.replaceWith(newT); | |
} | |
else if (callee.property.name == "apply") { | |
const args = path.node.arguments.slice(1); | |
const newT = t.callExpression(t.identifier(callee.object.name), args[0].elements); | |
path.replaceWith(newT); | |
} | |
} | |
}, | |
}; | |
const deobfuscateEncryptedStringsVisitor = { | |
CallExpression(path) { | |
const node = path.node; | |
const toReplace = ["pzgbK9", "WbNnBx3", "_NCrEQ", "jqgmog", "HHFFUl", "CLlOtS", "MCcmnR", "Wk5pGK3", "X4Stmgr"]; | |
if (toReplace.includes(node.callee.name)) { | |
var bind = path.scope.getBinding(node.callee.name); | |
console.log(node.callee.name, bind.path.type); | |
if (["VariableDeclarator", "FunctionDeclaration"].includes(bind.path.type)) { | |
const decryptFuncCode = generate(bind.path.node).code; | |
const expressionCode = generate(node).code; | |
try { | |
const decryptFuncCtx = vm.createContext(); | |
vm.runInContext(readFileSync("./context.js", "utf8"), decryptFuncCtx) | |
vm.runInContext(decryptFuncCode, decryptFuncCtx); | |
maybeAddToScope(path, "MCcmnR", vm, decryptFuncCtx); | |
const value = vm.runInContext(expressionCode, decryptFuncCtx); | |
path.replaceWith(t.valueToNode(value)); | |
} catch { | |
} | |
} | |
} | |
}, | |
}; | |
const deobfuscateStringConcatVisitor = { | |
BinaryExpression(path) { | |
let { confident, value } = path.evaluate(); // Evaluate the binary expression | |
if (!confident) return; // Skip if not confident | |
if (typeof value == "string") { | |
path.replaceWith(t.stringLiteral(value)); // Substitute the simplified value | |
} | |
}, | |
}; | |
const bracketToDotVisitor = { | |
MemberExpression(path) { | |
const validIdentifierRegex = /^[a-zA-Z0-9_]+$/; | |
let { object, property, computed } = path.node; | |
if (!computed) return; // Verify computed property is false | |
if (!t.isStringLiteral(property)) return; // Verify property is a string literal | |
if (!validIdentifierRegex.test(property.value)) return; // Verify that the property being accessed is a valid identifier | |
// If conditions pass: | |
// Replace the node with a new one | |
path.replaceWith( | |
t.MemberExpression(object, t.identifier(property.value), false) | |
); | |
}, | |
}; | |
const replaceStringConstants = { | |
Identifier(path) { | |
const { node } = path; | |
const { name } = node; | |
// Check if the identifier is a constant variable | |
// if (node.name != "IOTEST") { | |
// return; | |
// } | |
// Get the value of the constant variable | |
const binding = path.scope.getBinding(name); | |
if (!binding || | |
binding.path.node.type != "VariableDeclarator" | |
) { | |
return; | |
} | |
let constVal = undefined; | |
if (binding.path.node.init && binding.path.node.init.type == "StringLiteral") { | |
constVal = binding.path.node.init.value; | |
} else if (binding.constantViolations.length == 1) { | |
if (binding.constantViolations[0].node.right) { | |
// console.log("x", binding.constantViolations) | |
// console.log("x", binding.constantViolations[0].node.right.value) | |
constVal = binding.constantViolations[0].node.right.value; | |
} | |
} | |
if (constVal === undefined) { | |
return; | |
} | |
const alloweds = ["LogicalExpression", "MemberExpression", "BinaryExpression", "UnaryExpression", "ObjectProperty"] | |
if (!alloweds.includes(path.parent.type)) { | |
return; | |
} | |
console.log("replacing", node.name, "with", constVal); | |
path.replaceWith(t.valueToNode(constVal)); | |
} | |
}; | |
const reparseAst = (ast) => { | |
let x = generate(ast, { comments: false }).code; | |
return parser.parse(x); | |
}; | |
function deobfuscate(source) { | |
var ast = parser.parse(source); | |
traverse(ast, constantFold); | |
traverse(ast, deobfuscateEncodedStringVisitor); | |
ast = reparseAst(ast); | |
traverse(ast, splitDeclarators); | |
ast = reparseAst(ast); | |
traverse(ast, callAndApplyToDirectCall ); | |
ast = reparseAst(ast); | |
traverse(ast, deobfuscateEncryptedStringsVisitor); | |
ast = reparseAst(ast); | |
traverse(ast, deobfuscateEncryptedStringsVisitor); | |
ast = reparseAst(ast); | |
traverse(ast, replaceStringConstants); | |
traverse(ast, deobfuscateStringConcatVisitor); | |
traverse(ast, bracketToDotVisitor); | |
let deobfCode = generate(ast, { comments: false }).code; | |
deobfCode = require("@babel/core").transformSync(deobfCode, { | |
plugins: ["babel-plugin-remove-unused-vars"] | |
}).code; | |
deobfCode = beautify(deobfCode, { | |
indent_size: 2, | |
space_in_empty_paren: true, | |
}); | |
writeCodeToFile(deobfCode); | |
} | |
function writeCodeToFile(code) { | |
let outputPath = "output.js"; | |
writeFile(outputPath, code, (err) => { | |
if (err) { | |
console.log("Error writing file", err); | |
} else { | |
console.log(`Wrote file to ${outputPath}`); | |
} | |
}); | |
} | |
deobfuscate(readFileSync("./b.js", "utf8")); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment