distinctiomagnam
Version:
JavaScript Obfuscation Tool.
270 lines (232 loc) • 7.56 kB
text/typescript
import { ok } from "assert";
import { ObfuscateOrder } from "../../order";
import { ComputeProbabilityMap } from "../../probability";
import { getBlock, isBlock, walk } from "../../traverse";
import {
AssignmentExpression,
ExpressionStatement,
Identifier,
Location,
VariableDeclarator,
} from "../../util/gen";
import {
containsLexicallyBoundVariables,
getFunctionParameters,
getIdentifierInfo,
} from "../../util/identifiers";
import {
getDefiningContext,
getReferencingContexts,
getVarContext,
isForInitialize,
isFunction,
isVarContext,
} from "../../util/insert";
import Transform from "../transform";
/**
* Statement-based variable recycling.
*
* ```js
* // Input
* function percentage(decimal) {
* var multiplied = x * 100;
* var floored = Math.floor(multiplied);
* var output = floored + "%"
* return output;
* }
*
* // Output
* function percentage(decimal) {
* var multiplied = x * 100;
* var floored = Math.floor(multiplied);
* multiplied = floored + "%";
* return multiplied;
* }
* ```
*/
export default class NameRecycling extends Transform {
constructor(o) {
super(o, ObfuscateOrder.NameRecycling);
}
match(object, parents) {
return isBlock(object);
}
transform(object, parents) {
return () => {
if (containsLexicallyBoundVariables(object, parents)) {
return;
}
var context = getVarContext(object, parents);
var stmts = [...object.body];
ok(Array.isArray(stmts));
var definedMap = new Map<number, Set<string>>();
var referencedMap = new Map<number, Set<string>>();
var nodeMap = new Map<number, Map<string, Location[]>>();
var lastReferenceMap = new Map<string, number>();
var defined = new Set<string>();
var illegal = new Set<string>();
var fn = isFunction(parents[0]) ? parents[0] : null;
if (fn) {
definedMap.set(
-1,
new Set(
getFunctionParameters(fn, parents.slice(1)).map((x) => x[0].name)
)
);
}
stmts.forEach((stmt, i) => {
var definedHere = new Set<string>();
var referencedHere = new Set<string>();
var nodesHere = new Map<string, Location[]>();
walk(stmt, [object.body, object, ...parents], (o, p) => {
if (o.type == "Identifier") {
return () => {
var info = getIdentifierInfo(o, p);
if (!info.spec.isReferenced) {
return;
}
lastReferenceMap.set(o.name, i);
var comparingContext = info.spec.isDefined
? getDefiningContext(o, p)
: getReferencingContexts(o, p).find((x) => isVarContext(x));
if (comparingContext !== context) {
illegal.add(o.name);
this.log(o.name, "is different context");
} else {
if (!nodesHere.has(o.name)) {
nodesHere.set(o.name, [[o, p]]);
} else {
nodesHere.get(o.name).push([o, p]);
}
if (info.spec.isDefined) {
if (defined.has(o.name) || getBlock(o, p) !== object) {
illegal.add(o.name);
}
defined.add(o.name);
definedHere.add(o.name);
} else {
referencedHere.add(o.name);
}
}
};
}
});
// console.log(i, definedHere);
definedMap.set(i, definedHere);
referencedMap.set(i, referencedHere);
nodeMap.set(i, nodesHere);
});
this.log(illegal);
illegal.forEach((name) => {
nodeMap.forEach((value) => {
value.delete(name);
});
});
var available = new Set<string>();
var newNames = Object.create(null);
stmts.forEach((stmt, i) => {
var nodes = nodeMap.get(i);
nodes.forEach((locations, name) => {
var newName = newNames[name];
if (!newName) {
var canChange = false;
if (
object.type == "Program" &&
!ComputeProbabilityMap(this.options.renameGlobals, (x) => x, name)
) {
return;
}
if (defined.has(name) && definedMap.get(i).has(name)) {
canChange = true;
}
if (!canChange) {
return;
}
if (available.size) {
newName = available.keys().next().value;
available.delete(newName);
ok(name !== newName);
newNames[name] = newName;
defined.delete(name);
this.log(name, "->", newName);
}
}
});
nodes.forEach((locations, name) => {
var newName = newNames[name];
if (newName) {
locations.forEach(([object, parents]) => {
object.name = newName;
var declaratorIndex = parents.findIndex(
(p) => p.type == "VariableDeclarator"
);
if (
declaratorIndex !== -1 &&
parents[declaratorIndex].id ===
(parents[declaratorIndex - 1] || object)
) {
var value =
parents[declaratorIndex].init || Identifier("undefined");
var expr = AssignmentExpression(
"=",
parents[declaratorIndex].id,
value
);
if (parents[declaratorIndex + 1].length === 1) {
if (
isForInitialize(
parents[declaratorIndex + 2],
parents.slice(3)
)
) {
this.replace(parents[declaratorIndex + 2], expr);
} else {
this.replace(
parents[declaratorIndex + 2],
ExpressionStatement(expr)
);
}
} else {
this.replace(
parents[declaratorIndex],
VariableDeclarator(this.getPlaceholder(), expr)
);
}
} else {
if (parents[0].type == "FunctionDeclaration") {
this.replace(
parents[0],
ExpressionStatement(
AssignmentExpression("=", Identifier(newName), {
...parents[0],
type: "FunctionExpression",
id: null,
})
)
);
} else if (parents[0].type == "ClassDeclaration") {
this.replace(
parents[0],
ExpressionStatement(
AssignmentExpression("=", Identifier(newName), {
...parents[0],
type: "ClassExpression",
})
)
);
}
}
});
}
if (defined.has(name)) {
var lastRef = lastReferenceMap.get(name);
var isDecommissioned = lastRef === i;
if (isDecommissioned) {
available.add(name);
}
}
});
});
};
}
}