distinctiomagnam
Version:
JavaScript Obfuscation Tool.
295 lines (252 loc) • 7.51 kB
text/typescript
import { reservedIdentifiers } from "../constants";
import { ObfuscateOrder } from "../order";
import traverse, { walk } from "../traverse";
import {
FunctionDeclaration,
Identifier,
ReturnStatement,
VariableDeclaration,
VariableDeclarator,
CallExpression,
MemberExpression,
ThisExpression,
ArrayExpression,
ExpressionStatement,
AssignmentExpression,
Node,
BlockStatement,
ArrayPattern,
} from "../util/gen";
import { getIdentifierInfo } from "../util/identifiers";
import {
getBlockBody,
getVarContext,
isFunction,
prepend,
clone,
} from "../util/insert";
import Transform from "./transform";
/**
* Brings every function to the global level.
*
* Functions take parameters, input, have a return value and return modified changes to the scoped variables.
*
* ```js
* function topLevel(ref1, ref2, refN, param1, param2, paramN){
* return [ref1, ref2, refN, returnValue];
* }
* ```
*/
export default class Flatten extends Transform {
definedNames: Map<Node, Set<string>>;
constructor(o) {
super(o, ObfuscateOrder.Flatten);
this.definedNames = new Map();
}
apply(tree) {
traverse(tree, (o, p) => {
if (
o.type == "Identifier" &&
!reservedIdentifiers.has(o.name) &&
!this.options.globalVariables.has(o.name)
) {
var info = getIdentifierInfo(o, p);
if (info.spec.isReferenced) {
if (info.spec.isDefined) {
var c = getVarContext(o, p);
if (c) {
if (!this.definedNames.has(c)) {
this.definedNames.set(c, new Set([o.name]));
} else {
this.definedNames.get(c).add(o.name);
}
}
}
}
}
});
super.apply(tree);
}
match(object: Node, parents: Node[]) {
return (
object.type == "FunctionDeclaration" &&
object.body.type == "BlockStatement" &&
!object.generator &&
!object.async &&
!object.params.find((x) => x.type !== "Identifier")
);
}
transform(object: Node, parents: Node[]) {
return () => {
//
if (
parents.find(
(x) =>
x.type == "ClassExpression" ||
x.type == "ClassDeclaration" ||
x.type == "MethodDefinition"
)
) {
return;
}
var defined = new Set<string>();
var references = new Set<string>();
var modified = new Set<string>();
var illegal = new Set<string>();
var isIllegal = false;
var definedAbove = new Set<string>(this.options.globalVariables);
parents.forEach((x) => {
var set = this.definedNames.get(x);
if (set) {
set.forEach((name) => definedAbove.add(name));
}
});
walk(object, parents, (o, p) => {
if (object.id && o === object.id) {
return;
}
if (
o.type == "Identifier" &&
!this.options.globalVariables.has(o.name) &&
!reservedIdentifiers.has(o.name)
) {
var info = getIdentifierInfo(o, p);
if (!info.spec.isReferenced) {
return;
}
if (o.hidden) {
illegal.add(o.name);
}
if (info.spec.isDefined) {
defined.add(o.name);
} else if (info.spec.isModified) {
modified.add(o.name);
} else {
references.add(o.name);
}
}
if (o.type == "TryStatement") {
isIllegal = true;
return "EXIT";
}
if (o.type == "Identifier") {
if (o.name == "arguments") {
isIllegal = true;
return "EXIT";
}
}
if (o.type == "ThisExpression") {
isIllegal = true;
return "EXIT";
}
if (o.type == "Super") {
isIllegal = true;
return "EXIT";
}
if (o.type == "MetaProperty") {
isIllegal = true;
return "EXIT";
}
if (o.type == "VariableDeclaration" && o.kind !== "var") {
isIllegal = true;
return "EXIT";
}
});
if (isIllegal) {
return;
}
if (illegal.size) {
return;
}
illegal.forEach((name) => {
defined.delete(name);
});
defined.forEach((name) => {
references.delete(name);
modified.delete(name);
});
// console.log(object.id.name, illegal, references);
var input = Array.from(new Set([...modified, ...references]));
if (Array.from(input).find((x) => !definedAbove.has(x))) {
return;
}
var output = Array.from(modified);
var newName =
"flatten" +
this.getPlaceholder() +
"_" +
((object.id && object.id.name) || "fn");
getBlockBody(object.body).push(ReturnStatement());
walk(object.body, [object, ...parents], (o, p) => {
return () => {
if (o.type == "ReturnStatement" && getVarContext(o, p) === object) {
var elements = output.map(Identifier);
if (
o.argument &&
!(
o.argument.type == "Identifier" &&
o.argument.name == "undefined"
)
) {
elements.unshift(clone(o.argument));
}
o.argument = ArrayExpression(elements);
}
};
});
var newBody = getBlockBody(object.body);
if (input.length) {
newBody.unshift(
VariableDeclaration(
VariableDeclarator(
ArrayPattern(input.map(Identifier)),
ThisExpression()
)
)
);
}
var newFunctionDeclaration = FunctionDeclaration(
newName,
clone(object.params),
newBody
);
newFunctionDeclaration.async = !!object.async;
newFunctionDeclaration.generator = !!object.generator;
prepend(parents[parents.length - 1], newFunctionDeclaration);
var newParamNodes = object.params.map(() =>
Identifier(this.getPlaceholder())
);
// var result = newFn.call([...refs], ...arguments)
var call = VariableDeclaration(
VariableDeclarator(
"result",
CallExpression(
MemberExpression(Identifier(newName), Identifier("call"), false),
[ArrayExpression(input.map(Identifier)), ...newParamNodes]
)
)
);
// result.pop()
var pop = CallExpression(
MemberExpression(Identifier("result"), Identifier("pop"), false),
[]
);
// var result = newFn.call([...refs], ...arguments)
// modified1 = result.pop();
// modified2 = result.pop();
// ...modifiedN = result.pop();...
//
// return result.pop()
object.body = BlockStatement([
call,
...[...output].reverse().map((name) => {
return ExpressionStatement(
AssignmentExpression("=", Identifier(name), clone(pop))
);
}),
ReturnStatement(clone(pop)),
]);
object.params = newParamNodes;
};
}
}