humanifyjs
Version:
> Deobfuscate Javascript code using LLMs ("AI")
87 lines (86 loc) • 3.76 kB
JavaScript
import { parseAsync, transformFromAstAsync } from "@babel/core";
import * as babelTraverse from "@babel/traverse";
import { toIdentifier } from "@babel/types";
const traverse = (typeof babelTraverse.default === "function"
? babelTraverse.default
: babelTraverse.default.default); // eslint-disable-line @typescript-eslint/no-explicit-any -- This hack is because pkgroll fucks up the import somehow
const CONTEXT_WINDOW_SIZE = 200;
export async function visitAllIdentifiers(code, visitor, onProgress) {
const ast = await parseAsync(code);
const renames = new Set();
const visited = new Set();
if (!ast) {
throw new Error("Failed to parse code");
}
const scopes = await findScopes(ast);
const numRenamesExpected = scopes.length;
for (const smallestScope of scopes) {
if (hasVisited(smallestScope, visited))
continue;
const smallestScopeNode = smallestScope.node;
if (smallestScopeNode.type !== "Identifier") {
throw new Error("No identifiers found");
}
const surroundingCode = await scopeToString(smallestScope);
const renamed = await visitor(smallestScopeNode.name, surroundingCode);
let safeRenamed = toIdentifier(renamed);
while (renames.has(safeRenamed)) {
safeRenamed = `_${safeRenamed}`;
}
renames.add(safeRenamed);
smallestScope.scope.rename(smallestScopeNode.name, safeRenamed);
markVisited(smallestScope, smallestScopeNode.name, visited);
onProgress === null || onProgress === void 0 ? void 0 : onProgress(visited.size / numRenamesExpected);
}
onProgress === null || onProgress === void 0 ? void 0 : onProgress(1);
const stringified = await transformFromAstAsync(ast);
if ((stringified === null || stringified === void 0 ? void 0 : stringified.code) == null) {
throw new Error("Failed to stringify code");
}
return stringified.code;
}
function findScopes(ast) {
const scopes = [];
traverse(ast, {
BindingIdentifier(path) {
const bindingBlock = closestSurroundingContextPath(path).scope.block;
const pathSize = bindingBlock.end - bindingBlock.start;
scopes.push([path, pathSize]);
}
});
scopes.sort((a, b) => b[1] - a[1]);
return scopes.map(([nodePath]) => nodePath);
}
function hasVisited(path, visited) {
return visited.has(path.node.name);
}
function markVisited(path, newName, visited) {
visited.add(newName);
}
async function scopeToString(path) {
var _a, _b;
const surroundingPath = closestSurroundingContextPath(path);
const code = `${surroundingPath}`; // Implements a hidden `.toString()`
if (code.length < CONTEXT_WINDOW_SIZE) {
return code;
}
if (surroundingPath.isProgram()) {
const start = (_a = path.node.start) !== null && _a !== void 0 ? _a : 0;
const end = (_b = path.node.end) !== null && _b !== void 0 ? _b : code.length;
if (end < CONTEXT_WINDOW_SIZE / 2) {
return code.slice(0, CONTEXT_WINDOW_SIZE);
}
if (start > code.length - CONTEXT_WINDOW_SIZE / 2) {
return code.slice(-CONTEXT_WINDOW_SIZE);
}
return code.slice(start - CONTEXT_WINDOW_SIZE / 2, end + CONTEXT_WINDOW_SIZE / 2);
}
else {
return code.slice(0, CONTEXT_WINDOW_SIZE);
}
}
function closestSurroundingContextPath(path) {
var _a;
const programOrBindingNode = (_a = path.findParent((p) => p.isProgram() || path.node.name in p.getOuterBindingIdentifiers())) === null || _a === void 0 ? void 0 : _a.scope.path;
return programOrBindingNode !== null && programOrBindingNode !== void 0 ? programOrBindingNode : path.scope.path;
}