UNPKG

js-confuser

Version:

JavaScript Obfuscation Tool.

421 lines (356 loc) 12.7 kB
import { NodePath } from "@babel/traverse"; import { PluginArg, PluginObject } from "../plugin"; import { Order } from "../../order"; import { chance, choice } from "../../utils/random-utils"; import Template from "../../templates/template"; import * as t from "@babel/types"; import { CustomLock } from "../../options"; import { getFunctionName, getParentFunctionOrProgram, isDefiningIdentifier, isVariableIdentifier, prependProgram, } from "../../utils/ast-utils"; import { INTEGRITY, NodeIntegrity } from "./integrity"; import { HashTemplate } from "../../templates/integrityTemplate"; import { MULTI_TRANSFORM, NodeSymbol, PREDICTABLE, SKIP, UNSAFE, } from "../../constants"; import { IndexOfTemplate, NativeFunctionTemplate, StrictModeTemplate, } from "../../templates/tamperProtectionTemplates"; export default ({ Plugin }: PluginArg): PluginObject => { const me = Plugin(Order.Lock, { changeData: { locksInserted: 0, }, }); if (me.options.lock.startDate instanceof Date) { // Ensure date is in the past if (me.options.lock.startDate.getTime() > Date.now()) { me.warn("lock.startDate is detected to be in the future"); } me.options.lock.customLocks.push({ code: [ ` if(Date.now()<${me.options.lock.startDate.getTime()}) { {countermeasures} } `, ` if((new Date()).getTime()<${me.options.lock.startDate.getTime()}) { {countermeasures} } `, ], percentagePerBlock: 0.5, }); } if (me.options.lock.endDate instanceof Date) { // Ensure date is in the future if (me.options.lock.endDate.getTime() < Date.now()) { me.warn("lock.endDate is detected to be in the past"); } me.options.lock.customLocks.push({ code: [ ` if(Date.now()>${me.options.lock.endDate.getTime()}) { {countermeasures} } `, ` if((new Date()).getTime()>${me.options.lock.endDate.getTime()}) { {countermeasures} } `, ], percentagePerBlock: 0.5, }); } if (me.options.lock.domainLock) { var domainArray = Array.isArray(me.options.lock.domainLock) ? me.options.lock.domainLock : [me.options.lock.domainLock]; for (const regexString of domainArray) { me.options.lock.customLocks.push({ code: new Template(` if(!new RegExp({regexString}).test(window.location.href)) { {countermeasures} } `).setDefaultVariables({ regexString: () => t.stringLiteral(regexString.toString()), }), percentagePerBlock: 0.5, }); } } if (me.options.lock.selfDefending) { me.options.lock.customLocks.push({ code: ` ( function(){ // Breaks any code formatter var namedFunction = function(){ const test = function(){ const regExp=new RegExp('\\n'); return regExp['test'](namedFunction) }; if(test()) { {countermeasures} } } return namedFunction(); } )(); `, percentagePerBlock: 0.5, }); } if (me.options.lock.antiDebug) { me.options.lock.customLocks.push({ code: ` debugger; `, percentagePerBlock: 0.5, }); } const timesMap = new WeakMap<CustomLock, number>(); let countermeasuresNode: NodePath<t.Identifier>; let invokeCountermeasuresFnName: string; if (me.options.lock.countermeasures) { invokeCountermeasuresFnName = me.getPlaceholder("invokeCountermeasures"); me.globalState.internals.invokeCountermeasuresFnName = invokeCountermeasuresFnName; } var createCountermeasuresCode = () => { if (invokeCountermeasuresFnName) { return new Template(`${invokeCountermeasuresFnName}()`).compile(); } if (me.options.lock.countermeasures === false) { return []; } return new Template(`while(true){}`).compile(); }; me.globalState.lock.createCountermeasuresCode = createCountermeasuresCode; function applyLockToBlock(path: NodePath<t.Block>, customLock: CustomLock) { let times = timesMap.get(customLock); if (typeof times === "undefined") { times = 0; } let maxCount = customLock.maxCount || 100; // 100 is default max count let minCount = customLock.minCount || 1; // 1 is default min count if (maxCount >= 0 && times > maxCount) { // Limit creation, allowing -1 to disable the limit entirely return; } // The Program always gets a lock // Else based on the percentage // Try to reach the minimum count if ( !path.isProgram() && !chance(customLock.percentagePerBlock * 100) && times >= minCount ) { return; } // Increment the times timesMap.set(customLock, times + 1); const lockCode = Array.isArray(customLock.code) ? choice(customLock.code) : customLock.code; const template = typeof lockCode === "string" ? new Template(lockCode) : lockCode; const lockNodes = template.compile({ countermeasures: () => createCountermeasuresCode(), }); var p = path.unshiftContainer("body", lockNodes); p.forEach((p) => p.skip()); me.changeData.locksInserted++; } return { visitor: { BindingIdentifier(path) { if (path.node.name !== me.options.lock.countermeasures) { return; } // Exclude labels if (!isVariableIdentifier(path)) return; if (!isDefiningIdentifier(path)) { // Reassignments are not allowed me.error("Countermeasures function cannot be reassigned"); } if (countermeasuresNode) { // Disallow multiple countermeasures functions me.error( "Countermeasures function was already defined, it must have a unique name from the rest of your code" ); } if ( path.scope.getBinding(path.node.name).scope !== path.scope.getProgramParent() ) { me.error( "Countermeasures function must be defined at the global level" ); } countermeasuresNode = path; }, Block: { exit(path) { var customLock = choice(me.options.lock.customLocks); if (customLock) { applyLockToBlock(path, customLock); } }, }, Program: { exit(path) { // Insert nativeFunctionCheck if (me.options.lock.tamperProtection) { // Disallow strict mode // Tamper Protection uses non-strict mode features: // - eval() with local scope assignments const directives = path.get("directives"); for (var directive of directives) { if (directive.node.value.value === "use strict") { me.error( "Tamper Protection cannot be applied to code in strict mode. Disable strict mode by removing the 'use strict' directive, or disable Tamper Protection." ); } } var nativeFunctionName = me.getPlaceholder() + "_nativeFunctionCheck"; me.obfuscator.globalState.internals.nativeFunctionName = nativeFunctionName; // Ensure program is not in strict mode // Tamper Protection forces non-strict mode prependProgram( path, StrictModeTemplate.compile({ nativeFunctionName, countermeasures: createCountermeasuresCode(), }) ); const nativeFunctionDeclaration = NativeFunctionTemplate.single({ nativeFunctionName, countermeasures: createCountermeasuresCode(), IndexOfTemplate: IndexOfTemplate, }); // Checks function's toString() value for [native code] signature prependProgram(path, nativeFunctionDeclaration); } // Insert invokeCountermeasures function if (invokeCountermeasuresFnName) { if (!countermeasuresNode) { me.error( "Countermeasures function named '" + me.options.lock.countermeasures + "' was not found." ); } var hasInvoked = me.getPlaceholder("hasInvoked"); var statements = new Template(` var ${hasInvoked} = false; function ${invokeCountermeasuresFnName}(){ if(${hasInvoked}) return; ${hasInvoked} = true; ${me.options.lock.countermeasures}(); } `) .addSymbols(MULTI_TRANSFORM) .compile(); prependProgram(path, statements).forEach((p) => p.skip()); } if (me.options.lock.integrity) { const hashFnName = me.getPlaceholder() + "_hash"; const imulFnName = me.getPlaceholder() + "_imul"; const { sensitivityRegex } = me.globalState.lock.integrity; me.globalState.internals.integrityHashName = hashFnName; const hashCode = HashTemplate.compile({ imul: imulFnName, name: hashFnName, hashingUtilFnName: me.getPlaceholder(), sensitivityRegex: () => t.newExpression(t.identifier("RegExp"), [ t.stringLiteral(sensitivityRegex.source), t.stringLiteral(sensitivityRegex.flags), ]), }); prependProgram(path, hashCode); } }, }, // Integrity first pass // Functions are prepared for Integrity by simply extracting the function body // The extracted function is hashed in the 'integrity' plugin FunctionDeclaration: { exit(funcDecPath) { if (!me.options.lock.integrity) return; // Mark functions for integrity // Don't apply to async or generator functions if (funcDecPath.node.async || funcDecPath.node.generator) return; if (funcDecPath.find((p) => !!(p.node as NodeSymbol)[SKIP])) return; var program = getParentFunctionOrProgram(funcDecPath); // Only top-level functions if (!program.isProgram()) return; // Check user's custom implementation const functionName = getFunctionName(funcDecPath); // Don't apply to the countermeasures function (Intended) if ( me.options.lock.countermeasures && functionName === me.options.lock.countermeasures ) return; // Don't apply to invokeCountermeasures function (Intended) if (me.obfuscator.isInternalVariable(functionName)) return; if ( !me.computeProbabilityMap(me.options.lock.integrity, functionName) ) return; var newFnName = me.getPlaceholder(); var newFunctionDeclaration = t.functionDeclaration( t.identifier(newFnName), funcDecPath.node.params, funcDecPath.node.body ); // Clone semantic symbols like (UNSAFE, PREDICTABLE, MULTI_TRANSFORM, etc) const source = funcDecPath.node; Object.getOwnPropertySymbols(source).forEach((symbol) => { newFunctionDeclaration[symbol] = source[symbol]; }); (newFunctionDeclaration as NodeSymbol)[SKIP] = true; var [newFnPath] = program.unshiftContainer( "body", newFunctionDeclaration ); // Function simply calls the new function // In the case Integrity cannot transform the function, the original behavior is preserved funcDecPath.node.body = t.blockStatement( new Template(` return ${newFnName}(...arguments); `).compile(), funcDecPath.node.body.directives ); // Parameters no longer needed, using 'arguments' instead funcDecPath.node.params = []; // Mark the function as unsafe - use of 'arguments' is unsafe (funcDecPath.node as NodeSymbol)[UNSAFE] = true; // Params changed - function is no longer predictable (funcDecPath.node as NodeSymbol)[PREDICTABLE] = false; // Mark the function for integrity (funcDecPath.node as NodeIntegrity)[INTEGRITY] = { fnPath: newFnPath, fnName: newFnName, }; }, }, }, }; };