UNPKG

quamvoluptatem

Version:
608 lines (528 loc) 16.3 kB
import Transform from "../transform"; import { Node, IfStatement, ExpressionStatement, AssignmentExpression, Identifier, BinaryExpression, CallExpression, MemberExpression, Literal, UnaryExpression, NewExpression, FunctionDeclaration, ReturnStatement, VariableDeclaration, ObjectExpression, Property, ArrayExpression, FunctionExpression, ThisExpression, VariableDeclarator, Location, LogicalExpression, } from "../../util/gen"; import traverse, { getBlock, isBlock } from "../../traverse"; import { choice, getRandomInteger } from "../../util/random"; import { CrashTemplate1, CrashTemplate2, CrashTemplate3, } from "../../templates/crash"; import { getBlockBody, getVarContext, prepend } from "../../util/insert"; import Template from "../../templates/template"; import { ObfuscateOrder } from "../../order"; import Integrity from "./integrity"; import AntiDebug from "./antiDebug"; import { getIdentifierInfo } from "../../util/identifiers"; import { isLoop, isValidIdentifier } from "../../util/compare"; /** * Strings are formulated to work only during the allowed time */ class LockStrings extends Transform { strings: { [key: string]: string }; gen: any; fnName: string; objectExpression: Node; shift: number; constructor(o) { super(o); this.strings = Object.create(null); this.gen = this.getGenerator(); this.fnName = null; this.objectExpression = null; this.shift = getRandomInteger(1, 5); } match(object) { return ( object.type == "Program" || (object.type == "Literal" && typeof object.value === "string") ); } getKey() { function ensureNumber(y: Date | number | false) { if (!y) { return 0; } if (y instanceof Date) { return y.getTime(); } // @ts-ignore return parseInt(y); } var start = ensureNumber(this.options.lock.startDate); var end = ensureNumber(this.options.lock.endDate); var diff = end - start; var now = Date.now(); return { key: Math.floor(now / diff), diff: diff, }; } transform(object: Node, parents: Node[]) { if (!this.fnName) { this.fnName = this.getPlaceholder(); this.objectExpression = ObjectExpression([]); } if (object.type == "Program") { var keyArg = this.getPlaceholder(); var mapName = this.getPlaceholder(); return () => { if (this.objectExpression.properties.length) { var keyVar = this.getPlaceholder(); var { diff } = this.getKey(); prepend( object, VariableDeclaration([ VariableDeclarator( Identifier(keyVar), Template(`Math.floor(Date.now()/${diff})`).single().expression ), ]) ); var currentVar = this.getPlaceholder(); var outputVar = this.getPlaceholder(); var xVar = this.getPlaceholder(); prepend( object, FunctionDeclaration( this.fnName, [Identifier(keyArg)], [ VariableDeclaration( VariableDeclarator(mapName, this.objectExpression) ), VariableDeclaration( VariableDeclarator( currentVar, MemberExpression( Identifier(mapName), Identifier(keyArg), true ) ) ), VariableDeclaration(VariableDeclarator(outputVar, Literal(""))), ExpressionStatement( CallExpression( MemberExpression( Identifier(currentVar), Identifier("forEach"), false ), [ FunctionExpression( [Identifier(xVar)], [ ExpressionStatement( AssignmentExpression( "+=", Identifier(outputVar), CallExpression( MemberExpression( Identifier("String"), Identifier("fromCharCode"), false ), [ BinaryExpression( "^", BinaryExpression( ">>", Identifier(xVar), Literal(this.shift) ), Identifier(keyVar) ), ] ) ) ), ] ), ] ) ), ReturnStatement(Identifier(outputVar)), ] ) ); } }; } if (!object.value) { return; } if ( parents.find( (x) => x.type == "CallExpression" && x.callee.name == this.fnName ) ) { return; } var key = this.strings[object.value]; if (!key) { // New string found! key = this.gen.generate(); this.strings[key] = object.value; var xorKey = this.getKey().key; var array = ArrayExpression( object.value .split("") .map((x) => x.charCodeAt(0)) .map((x) => x ^ xorKey) .map((x) => x << this.shift) .map((x) => Literal(x)) ); this.objectExpression.properties.push( Property(Identifier(key), array, false) ); } if (parents[0].type == "Property") { parents[0].computed = true; } this.objectAssign( object, CallExpression(Identifier(this.fnName), [Literal(key)]) ); } } /** * Applies browser & date locks. */ export default class Lock extends Transform { globalVar: string; counterMeasuresNode: Location; constructor(o) { super(o, ObfuscateOrder.Lock); if (this.options.lock.startDate && this.options.lock.endDate) { this.before.push(new LockStrings(o)); } if (this.options.lock.integrity) { this.before.push(new Integrity(o, this)); } if (this.options.lock.antiDebug) { this.before.push(new AntiDebug(o)); } } apply(tree) { if ( typeof this.options.lock.countermeasures === "string" && isValidIdentifier(this.options.lock.countermeasures) ) { var defined = new Set<string>(); traverse(tree, (object, parents) => { if (object.type == "Identifier") { var info = getIdentifierInfo(object, parents); if (info.spec.isDefined) { defined.add(object.name); if (object.name === this.options.lock.countermeasures) { if (this.counterMeasuresNode) { throw new Error( "Countermeasures function was already defined, it must have a unique name from the rest of your code" ); } else { var definingContext = getVarContext( parents[0], parents.slice(1) ); if (definingContext != tree) { throw new Error( "Countermeasures function must be defined at the global level" ); } var chain: Location = [object, parents]; if (info.isFunctionDeclaration) { chain = [parents[0], parents.slice(1)]; } else if (info.isVariableDeclaration) { chain = [parents[1], parents.slice(2)]; } this.counterMeasuresNode = chain; } } } } }); if (!this.counterMeasuresNode) { throw new Error( "Countermeasures function named '" + this.options.lock.countermeasures + "' was not found. Names found: " + Array.from(defined).slice(0, 100).join(", ") ); } } super.apply(tree); } getCounterMeasuresCode(): Node[] { var opt = this.options.lock.countermeasures; if (opt === false) { return null; } // Call function if (typeof opt === "string") { // Since Lock occurs before variable renaming, we are using the pre-obfuscated function name return [CallExpression(Template(opt).single().expression, [])]; } var type = choice(["crash", "exit", "stutter"]); switch (type) { case "crash": var varName = this.getPlaceholder(); return choice([CrashTemplate1, CrashTemplate2, CrashTemplate3]).compile( { var: varName, } ); case "exit": if (this.options.target == "browser") { return Template("document.documentElement.innerHTML = '';").compile(); } return Template("process.exit()").compile(); case "stutter": return Template( "for ( var i=0; i < 1000; i++ ) { var x = Math.cos(i) }" ).compile(); } } /** * Converts Dates to numbers, then applies some randomness * @param object */ getTime(object: Date | number | false): number { if (!object) { return 0; } if (object instanceof Date) { return this.getTime(object.getTime()); } return object + getRandomInteger(-4000, 4000); } match(object: Node, parents: Node[]) { return isBlock(object); } transform(object: Node, parents: Node[]) { if (parents.find((x) => isLoop(x) && x.type != "SwitchStatement")) { return; } // no check in countermeasures code, otherwise it will infinitely call itself if ( this.counterMeasuresNode && (object == this.counterMeasuresNode[0] || parents.indexOf(this.counterMeasuresNode[0]) !== -1) ) { return; } var block = getBlock(object, parents); var choices = []; if (this.options.lock.startDate) { choices.push("startDate"); } if (this.options.lock.endDate) { choices.push("endDate"); } if (this.options.lock.domainLock && this.options.lock.domainLock.length) { choices.push("domainLock"); } if (this.options.lock.nativeFunctions) { choices.push("nativeFunction"); } if (this.options.lock.context) { choices.push("context"); } if (!choices.length) { return; } return () => { var type = choice(choices); var nodes = []; var dateNow: Node = CallExpression( MemberExpression(Identifier("Date"), Literal("now"), true), [] ); if (Math.random() > 0.5) { dateNow = CallExpression( MemberExpression( NewExpression(Identifier("Date"), []), Literal("getTime") ), [] ); } if (Math.random() > 0.5) { dateNow = CallExpression( MemberExpression( MemberExpression( MemberExpression(Identifier("Date"), Literal("prototype"), true), Literal("getTime"), true ), Literal("call"), true ), [NewExpression(Identifier("Date"), [])] ); } var test; var offset = 0; switch (type) { case "nativeFunction": var set = this.options.lock.nativeFunctions; if (set === true) { if (this.options.target == "node") { set = new Set(["Function", "String"]); } else { set = new Set(["Function", "String", "fetch"]); } } if (Array.isArray(set)) { set = new Set(set); } if (!set) { set = new Set(); } var fn = choice(Array.from(set)); if (fn) { test = Template( `(${fn}+"").indexOf("[native code]") == -1` ).single().expression; if (Math.random() > 0.5) { test = Template( `${fn}.toString().split("{ [native code] }").length <= 1` ).single().expression; } nodes.push( IfStatement(test, this.getCounterMeasuresCode() || [], null) ); } break; case "startDate": test = BinaryExpression( "<", dateNow, Literal(this.getTime(this.options.lock.startDate)) ); nodes.push( IfStatement(test, this.getCounterMeasuresCode() || [], null) ); break; case "endDate": test = BinaryExpression( ">", dateNow, Literal(this.getTime(this.options.lock.endDate)) ); nodes.push( IfStatement(test, this.getCounterMeasuresCode() || [], null) ); break; case "context": var prop = choice(this.options.lock.context); // Todo: Alternative to `this` if (!this.globalVar) { offset = 1; this.globalVar = this.getPlaceholder(); prepend( parents[parents.length - 1] || block, VariableDeclaration( VariableDeclarator( this.globalVar, LogicalExpression( "||", Identifier( this.options.globalVariables.keys().next().value ), ThisExpression() ) ) ) ); } test = UnaryExpression( "!", MemberExpression(Identifier(this.globalVar), Literal(prop), true) ); nodes.push( IfStatement(test, this.getCounterMeasuresCode() || [], null) ); break; case "domainLock": function removeSlashes(path: string) { var count = path.length - 1; var index = 0; while (path.charCodeAt(index) === 47 && ++index); while (path.charCodeAt(count) === 47 && --count); return path.slice(index, count + 1); } var locationHref = MemberExpression( Identifier("location"), Literal("href"), true ); var random = choice(this.options.lock.domainLock as any); if (random) { test = CallExpression( MemberExpression(locationHref, Literal("match"), true), [ { type: "Literal", regex: { pattern: random instanceof RegExp ? random.source : removeSlashes(random + ""), flags: random instanceof RegExp ? "" : "", }, }, ] ); test = UnaryExpression("!", test); if (Math.random() > 0.5) { test = LogicalExpression( "||", BinaryExpression( "==", UnaryExpression("typeof", Identifier("location")), Literal("undefined") ), test ); } nodes.push( IfStatement(test, this.getCounterMeasuresCode() || [], null) ); } break; } if (nodes.length) { var body = getBlockBody(block); var randomIndex = getRandomInteger(0, body.length) + offset; if (randomIndex >= body.length) { body.push(...nodes); } else { body.splice(randomIndex, 0, ...nodes); } } }; } }