UNPKG

jsobx

Version:

a simple js obfuscator

269 lines (223 loc) 7.72 kB
const acorn = require('acorn'); const walk = require('acorn-walk'); const codegen = require('escodegen'); const through = require('through2'); const fs = require('fs'); const path = require('path'); const domProps = JSON.parse(fs.readFileSync(path.resolve(__dirname, 'domprops.json'))).props; function shuffle(array) { for (let i = array.length; i; i--) { let j = Math.floor(Math.random() * i); let x = array[i - 1]; array[i - 1] = array[j]; array[j] = x; } } function numberToBase52(num) { if (num === 0) return 'a'; let result = ''; do { const index = num % 52; const char = String.fromCharCode(index < 26 ? index + 97 : index - 26 + 65); result = char + result; num = Math.floor(num / 52); } while (num > 0); return result; } const defaultOptions = { groupDomProperties: true, groupDomMethodsNoArgs: true, groupDomMethods: true, groupStrings: true, groupRegexes: true, shuffle: true, mangle: false, wrap: true, prefix: '_', splitString: true, splitStringRegex: /(<\/\S>|\S+(?==)|=[^>\s]+|\n|\s{4})/ }; function process(code, options) { options = Object.assign({}, defaultOptions, options || {}); const collectedData = new Map(); let counter = 0; const collectData = (node) => { let key; if (node.type === 'BinaryExpression') { key = JSON.stringify(node); } else { key = node.regex ? node.raw : node.value; } if (collectedData.has(key)) { return collectedData.get(key).name; } const name = options.prefix + (options.mangle ? numberToBase52(counter++) : counter++); collectedData.set(key, { name, node }); return name; }; const createIdentifier = (name) => { return { type: 'Identifier', name: name }; }; const createBinaryExpression = (vars) => { if (vars.length === 1) { return createIdentifier(vars[0]); } return { type: 'BinaryExpression', operator: '+', left: createBinaryExpression(vars.slice(0, -1)), right: { type: 'Identifier', name: vars[vars.length - 1] } }; } const comments = []; const tokens = []; const ast = acorn.parse(code, { ecmaVersion: 2020, allowReturnOutsideFunction: true, locations: true, ranges: true, onComment: comments, onToken: tokens }); const attachedAst = codegen.attachComments(ast, comments, tokens); walk.ancestor(ast, { Literal(node, ancestors) { const parent = ancestors[ancestors.length - 2]; // console.log('-- Literal --'); // console.log(node); // console.log('parent', parent); let transform = false; let transformString = false; if (options.groupStrings && typeof node.value === 'string' && node.value !== '' && node.value !== 'use strict' && !(parent.type === 'ImportDeclaration' || parent.type === 'CallExpression' && parent.callee.name === 'require' || parent.type === 'MemberExpression' && parent.property === node ) ) { transform = true; transformString = true; } if (options.groupRegexes && node.regex) { transform = true; } const getReplacement = () => { if (transformString && options.splitString) { const parts = node.value.split(options.splitStringRegex); const names = []; parts.forEach((value) => { if (value === '') return; names.push(collectData({ type: 'Literal', value: value })) }); if (names.length > 1) { return createIdentifier(collectData(createBinaryExpression(names))); } } return createIdentifier(collectData(node)); }; if (transform) { const obj = parent.arguments || parent.elements || parent; const keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { if (obj[keys[i]] === node) { obj[keys[i]] = getReplacement(); break; } } } }, MemberExpression(node, ancestors) { const parent = ancestors[ancestors.length - 2]; // console.log('-- MemberExpression --'); // console.log(node); // console.log('parent', parent); let transform = false; if (options.groupDomMethodsNoArgs && parent.type === 'CallExpression' && !parent.arguments.length ) { transform = true; } if (options.groupDomMethods && parent.type === 'CallExpression' && parent.callee.type === 'MemberExpression' && parent.callee.object === node.object ) { transform = true; } if (options.groupDomProperties && (parent.type !== 'CallExpression' || parent.arguments && parent.arguments.indexOf(node) > -1 ) ) { transform = true; } if (transform) { if (node.property.type === 'Literal' && node.computed && domProps.indexOf(node.property.value) > -1 ) { node.computed = true; node.property = createIdentifier(collectData(node.property)); } else if (node.property.type === 'Identifier' && !node.computed && domProps.indexOf(node.property.name) > -1 ) { node.computed = true; node.property = createIdentifier(collectData({ type: 'Literal', value: node.property.name })); } } } }); if (collectedData.size > 0) { const array = Array.from(collectedData.entries()); if (options.shuffle && !options.splitString) { shuffle(array); } const declaration = { type: 'VariableDeclaration', kind: 'var', declarations: array.map(([, meta]) => ({ type: 'VariableDeclarator', id: { type: 'Identifier', name: meta.name }, init: meta.node })) }; ast.body.unshift(declaration); } let output = codegen.generate(attachedAst, { comment: true }); if (options.wrap) { output = `(function(){\n${output}\n})();` } return output; } function jsobx(options) { return through.obj(function(file, encoding, callback) { var content = String(file.contents); content = process(content, options); file.contents = Buffer.from(content); return callback(null, file); }); } jsobx.process = process; module.exports = jsobx;