UNPKG

humanifyjs

Version:

> Deobfuscate Javascript code using LLMs ("AI")

87 lines (86 loc) 3.76 kB
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; }