distinctiomagnam
Version:
JavaScript Obfuscation Tool.
421 lines (364 loc) • 12 kB
text/typescript
import { ok } from "assert";
import { stringify } from "querystring";
import { reservedIdentifiers } from "../constants";
import { ObfuscateOrder } from "../order";
import { ComputeProbabilityMap } from "../probability";
import Template from "../templates/template";
import { walk } from "../traverse";
import {
AssignmentExpression,
BinaryExpression,
ExpressionStatement,
Identifier,
IfStatement,
Literal,
Location,
MemberExpression,
Node,
RestElement,
ReturnStatement,
SequenceExpression,
} from "../util/gen";
import { getIdentifierInfo } from "../util/identifiers";
import {
getDefiningContext,
getReferencingContexts,
getVarContext,
isForInitialize,
isFunction,
isVarContext,
prepend,
} from "../util/insert";
import { choice, getRandomInteger, getRandomString } from "../util/random";
import Transform from "./transform";
export default class Stack extends Transform {
made: number;
constructor(o) {
super(o, ObfuscateOrder.Stack);
this.made = 0;
}
match(object: Node, parents: Node[]) {
return (
isFunction(object) &&
!object.params.find((x) => x.type !== "Identifier") &&
object.body.type === "BlockStatement" &&
!parents.find((x) => x.$dispatcherSkip) &&
!object.$requiresEval
);
}
transform(object: Node, parents: Node[]) {
return () => {
// Uncaught SyntaxError: Getter must not have any formal parameters.
// Uncaught SyntaxError: Setter must have exactly one formal parameter
var propIndex = parents.findIndex((x) => x.type == "Property");
if (propIndex !== -1) {
if (parents[propIndex].value === (parents[propIndex - 1] || object)) {
if (parents[propIndex].kind !== "init" || parents[propIndex].method) {
return;
}
}
}
var defined = new Set<string>();
var referenced = new Set<string>();
var illegal = new Set<string>();
var subscripts = new Map<string, string>();
var deadValues = Object.create(null);
function setSubscript(string, index) {
subscripts.set(string, index + "");
}
object.params.forEach((param) => {
ok(param.name);
defined.add(param.name);
setSubscript(param.name, subscripts.size);
});
var startingSize = subscripts.size;
walk(object.body, [object, ...parents], (o, p) => {
if (o.type == "Identifier") {
var info = getIdentifierInfo(o, p);
if (!info.spec.isReferenced) {
return;
}
var c = info.spec.isDefined
? getDefiningContext(o, p)
: getReferencingContexts(o, p).find((x) => isVarContext(x));
if (c !== object) {
this.log(o.name + " is illegal due to different context");
illegal.add(o.name);
}
if (
info.isClauseParameter ||
info.isFunctionParameter ||
isForInitialize(o, p)
) {
this.log(
o.name + " is illegal due to clause parameter/function parameter"
);
illegal.add(o.name);
}
if (o.hidden) {
illegal.add(o.name);
}
if (info.spec.isDefined) {
if (defined.has(o.name)) {
illegal.add(o.name);
}
if (info.isFunctionDeclaration) {
if (p[0] !== object.body.body[0]) {
illegal.add(o.name);
}
}
setSubscript(o.name, subscripts.size);
defined.add(o.name);
var varIndex = p.findIndex((x) => x.type == "VariableDeclaration");
if (
varIndex !== -1 &&
(varIndex !== 2 || p[varIndex].declarations.length > 1)
) {
illegal.add(o.name);
}
} else if (info.spec.isReferenced) {
referenced.add(o.name);
}
}
});
illegal.forEach((name) => {
defined.delete(name);
referenced.delete(name);
subscripts.delete(name);
});
referenced.forEach((name) => {
if (!defined.has(name)) {
subscripts.delete(name);
}
});
if (object.params.find((x) => illegal.has(x.name))) {
return;
}
if (!subscripts.size) {
return;
}
function numberLiteral(number, depth = 0) {
ok(number === number);
if (
typeof number !== "number" ||
!Object.keys(deadValues).length ||
depth > 5 ||
Math.random() > (depth == 0 ? 0.9 : 0.8 / (depth * 2))
) {
return Literal(number);
}
var opposingIndex = choice(Object.keys(deadValues));
if (typeof opposingIndex === "undefined") {
return Literal(number);
}
var actualValue = deadValues[opposingIndex];
ok(typeof actualValue === "number");
return BinaryExpression(
"-",
MemberExpression(
Identifier(stackName),
numberLiteral(
isNaN(parseFloat(opposingIndex))
? opposingIndex
: parseFloat(opposingIndex),
depth + 1
),
true
),
numberLiteral(actualValue - number, depth + 1)
);
}
function getMemberExpression(index) {
ok(typeof index === "string", typeof index);
return MemberExpression(
Identifier(stackName),
numberLiteral(isNaN(parseFloat(index)) ? index : parseFloat(index)),
true
);
}
var stackName = this.getPlaceholder();
var made = 1;
const scan = (o, p) => {
if (o.type == "Identifier") {
var index = subscripts.get(o.name);
if (typeof index !== "undefined") {
var info = getIdentifierInfo(o, p);
if (!info.spec.isReferenced) {
return;
}
var member = getMemberExpression(index);
if (info.spec.isDefined) {
if (info.isVariableDeclaration) {
walk(p[2], p.slice(3), (oo, pp) => {
if (oo != o) {
return scan(oo, pp);
}
});
this.replace(
p[2],
ExpressionStatement(
AssignmentExpression(
"=",
member,
p[0].init || Identifier("undefined")
)
)
);
return;
} else if (info.isFunctionDeclaration) {
walk(p[0], p.slice(1), (oo, pp) => {
if (oo != o) {
return scan(oo, pp);
}
});
this.replace(
p[0],
ExpressionStatement(
AssignmentExpression("=", member, {
...p[0],
type: "FunctionExpression",
id: null,
expression: false,
})
)
);
return;
} else if (info.isClassDeclaration) {
walk(p[0], p.slice(1), (oo, pp) => {
if (oo != o) {
return scan(oo, pp);
}
});
this.replace(
p[0],
ExpressionStatement(
AssignmentExpression("=", member, {
...p[0],
type: "ClassExpression",
})
)
);
return;
}
}
if (info.spec.isReferenced) {
this.replace(o, member);
}
}
}
if (
o.type == "Literal" &&
typeof o.value === "number" &&
Math.floor(o.value) === o.value &&
Math.abs(o.value) < 100_000 &&
Math.random() < 4 / made &&
p.find((x) => isFunction(x)) === object
) {
made++;
return () => {
this.replaceIdentifierOrLiteral(o, numberLiteral(o.value, 0), p);
};
}
};
var rotateNodes: { [index: number]: Node } = Object.create(null);
object.body.body.forEach((stmt, index) => {
var isFirst = index == 0;
if (isFirst || Math.random() < 0.9 / index) {
var exprs = [];
var changes = getRandomInteger(isFirst ? 2 : 1, isFirst ? 3 : 2);
for (var i = 0; i < changes; i++) {
var expr;
var type = choice(["set", "deadValue"]);
var valueSet = new Set([
...Array.from(subscripts.values()),
...Object.keys(deadValues),
]);
var newIndex;
var i = 0;
do {
newIndex =
getRandomInteger(0, 250 + subscripts.size + i * 1000) + "";
i++;
} while (valueSet.has(newIndex));
switch (type) {
case "set":
var randomName = choice(Array.from(subscripts.keys()));
var currentIndex = subscripts.get(randomName);
expr = AssignmentExpression(
"=",
getMemberExpression(newIndex),
getMemberExpression(currentIndex)
);
ok(
typeof deadValues[newIndex] === "undefined",
deadValues[newIndex]
);
setSubscript(randomName, newIndex);
break;
case "deadValue":
var rand = getRandomInteger(-250, 250);
// modify an already existing dead value index
if (Math.random() > 0.5) {
var alreadyExisting = choice(Object.keys(deadValues));
if (typeof alreadyExisting === "string") {
newIndex = alreadyExisting;
}
}
expr = AssignmentExpression(
"=",
getMemberExpression(newIndex),
numberLiteral(rand)
);
ok(!subscripts.has(newIndex));
deadValues[newIndex] = rand;
break;
}
exprs.push(expr);
}
rotateNodes[index] = ExpressionStatement(SequenceExpression(exprs));
}
walk(
stmt,
[object.body.body, object.body, object, ...parents],
(o, p) => {
return scan(o, p);
}
);
if (stmt.type == "ReturnStatement") {
var opposing = choice(Object.keys(deadValues));
if (typeof opposing === "string") {
this.replace(
stmt,
IfStatement(
BinaryExpression(
">",
getMemberExpression(opposing),
numberLiteral(
deadValues[opposing] + getRandomInteger(40, 140)
)
),
[
ReturnStatement(
getMemberExpression(getRandomInteger(-250, 250) + "")
),
],
[ReturnStatement(stmt.argument)]
)
);
}
}
});
// Add in the rotation nodes
Object.keys(rotateNodes).forEach((index, i) => {
object.body.body.splice(parseInt(index) + i, 0, rotateNodes[index]);
});
// Set the params for this function to be the stack array
object.params = [RestElement(Identifier(stackName))];
// Ensure the array is correct length
prepend(
object.body,
Template(`${stackName}.length = ${startingSize}`).single()
);
};
}
}