distinctiomagnam
Version:
JavaScript Obfuscation Tool.
340 lines (294 loc) • 9.69 kB
text/typescript
import Template from "../../templates/template";
import Transform from "../transform";
import { ObfuscateOrder } from "../../order";
import {
Node,
Location,
CallExpression,
Identifier,
Literal,
FunctionDeclaration,
ReturnStatement,
MemberExpression,
SwitchStatement,
SwitchCase,
LogicalExpression,
VariableDeclarator,
FunctionExpression,
ExpressionStatement,
SequenceExpression,
AssignmentExpression,
VariableDeclaration,
BreakStatement,
} from "../../util/gen";
import { append, prepend } from "../../util/insert";
import { getIdentifierInfo } from "../../util/identifiers";
import { getRandomInteger } from "../../util/random";
import { reservedIdentifiers, reservedKeywords } from "../../constants";
import { ComputeProbabilityMap } from "../../probability";
class GlobalAnalysis extends Transform {
notGlobals: Set<string>;
globals: { [name: string]: Location[] };
constructor(o) {
super(o);
this.globals = Object.create(null);
this.notGlobals = new Set();
}
match(object: Node, parents: Node[]) {
return object.type == "Identifier" && !reservedKeywords.has(object.name);
}
transform(object: Node, parents: Node[]) {
// no touching `import()` or `import x from ...`
var importIndex = parents.findIndex(
(x) => x.type == "ImportExpression" || x.type == "ImportDeclaration"
);
if (importIndex !== -1) {
if (
parents[importIndex].source === (parents[importIndex - 1] || object)
) {
return;
}
}
var info = getIdentifierInfo(object, parents);
if (!info.spec.isReferenced) {
return;
}
// Add to globals
if (!this.notGlobals.has(object.name)) {
if (!this.globals[object.name]) {
this.globals[object.name] = [];
}
this.globals[object.name].push([object, parents]);
}
if (info.spec.isDefined || info.spec.isModified) {
delete this.globals[object.name];
this.notGlobals.add(object.name);
}
var assignmentIndex = parents.findIndex(
(x) => x.type == "AssignmentExpression"
);
var updateIndex = parents.findIndex((x) => x.type == "UpdateExpression");
if (
(assignmentIndex != -1 &&
parents[assignmentIndex].left ===
(parents[assignmentIndex - 1] || object)) ||
updateIndex != -1
) {
var memberIndex = parents.findIndex((x) => x.type == "MemberExpression");
if (
memberIndex == -1 ||
memberIndex > (assignmentIndex == -1 ? assignmentIndex : updateIndex)
) {
delete this.globals[object.name];
this.notGlobals.add(object.name);
}
}
}
}
/**
* Global Concealing hides global variables being accessed.
*
* - Any variable that is not defined is considered "global"
*/
export default class GlobalConcealing extends Transform {
globalAnalysis: GlobalAnalysis;
constructor(o) {
super(o, ObfuscateOrder.GlobalConcealing);
this.globalAnalysis = new GlobalAnalysis(o);
this.before.push(this.globalAnalysis);
}
match(object: Node, parents: Node[]) {
return object.type == "Program";
}
transform(object: Node, parents: Node[]) {
return () => {
var globals: { [name: string]: Location[] } = this.globalAnalysis.globals;
this.globalAnalysis.notGlobals.forEach((del) => {
delete globals[del];
});
delete globals["require"];
reservedIdentifiers.forEach((x) => {
delete globals[x];
});
Object.keys(globals).forEach((x) => {
if (this.globalAnalysis.globals[x].length < 1) {
delete globals[x];
} else if (
!ComputeProbabilityMap(this.options.globalConcealing, (x) => x, x)
) {
delete globals[x];
}
});
// this.log(Object.keys(globals).join(', '))
if (Object.keys(globals).length > 0) {
var used = new Set();
// 1. Make getter function
// holds "window" or "global"
var globalVar = this.getPlaceholder();
// holds outermost "this"
var thisVar = this.getPlaceholder();
// "window" or "global" in node
var global =
this.options.globalVariables.values().next().value || "window";
var getGlobalVariableFnName = this.getPlaceholder();
var getThisVariableFnName = this.getPlaceholder();
// Returns global variable or fall backs to `this`
var getGlobalVariableFn = Template(`
var ${getGlobalVariableFnName} = function(){
try {
return ${global};
} catch (e){
return ${getThisVariableFnName}["call"](this);
}
}`).single();
var getThisVariableFn = Template(`
var ${getThisVariableFnName} = function(){
try {
return this;
} catch (e){
return null;
}
}`).single();
// 2. Replace old accessors
var globalFn = this.getPlaceholder();
var newNames = Object.create(null);
Object.keys(globals).forEach((name) => {
var locations: Location[] = globals[name];
var state;
do {
state = getRandomInteger(-1000, 1000 + used.size);
} while (used.has(state));
used.add(state);
newNames[name] = state;
locations.forEach(([node, parents]) => {
if (!parents.find((x) => x.$dispatcherSkip)) {
// Do not replace
if (parents[0]) {
if (
parents[0].type == "ClassDeclaration" ||
parents[0].type == "ClassExpression" ||
parents[0].type == "FunctionExpression" ||
parents[0].type == "FunctionDeclaration"
) {
if (parents[0].id === node) {
return;
}
}
}
this.replace(
node,
CallExpression(Identifier(globalFn), [Literal(state)])
);
}
});
});
// Adds all global variables to the switch statement
this.options.globalVariables.forEach((name) => {
if (!newNames[name]) {
var state;
do {
state = getRandomInteger(
-1000,
1000 + used.size + this.options.globalVariables.size * 100
);
} while (used.has(state));
used.add(state);
newNames[name] = state;
}
});
var indexParamName = this.getPlaceholder();
var returnName = this.getPlaceholder();
var functionDeclaration = FunctionDeclaration(
globalFn,
[Identifier(indexParamName)],
[
VariableDeclaration(VariableDeclarator(returnName)),
SwitchStatement(
Identifier(indexParamName),
Object.keys(newNames).map((name) => {
var code = newNames[name];
var body: Node[] = [
ReturnStatement(
LogicalExpression(
"||",
MemberExpression(
Identifier(globalVar),
Literal(name),
true
),
MemberExpression(Identifier(thisVar), Literal(name), true)
)
),
];
if (Math.random() > 0.5 && name) {
body = [
ExpressionStatement(
AssignmentExpression(
"=",
Identifier(returnName),
LogicalExpression(
"||",
Literal(name),
MemberExpression(
Identifier(thisVar),
Literal(name),
true
)
)
)
),
BreakStatement(),
];
}
return SwitchCase(Literal(code), body);
})
),
ReturnStatement(
LogicalExpression(
"||",
MemberExpression(
Identifier(globalVar),
Identifier(returnName),
true
),
MemberExpression(
Identifier(thisVar),
Identifier(returnName),
true
)
)
),
]
);
var tempVar = this.getPlaceholder();
var variableDeclaration = Template(`
var ${globalVar}, ${thisVar};
`).single();
variableDeclaration.declarations.push(
VariableDeclarator(
tempVar,
CallExpression(
MemberExpression(
FunctionExpression(
[],
[
getGlobalVariableFn,
getThisVariableFn,
Template(
`return ${thisVar} = ${getThisVariableFnName}["call"](this, ${globalFn}), ${globalVar} = ${getGlobalVariableFnName}["call"](this)`
).single(),
]
),
Literal("call"),
true
),
[]
)
)
);
prepend(object, variableDeclaration);
append(object, functionDeclaration);
}
};
}
}