quamvoluptatem
Version:
JavaScript Obfuscation Tool.
608 lines (528 loc) • 16.3 kB
text/typescript
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);
}
}
};
}
}