distinctiomagnam
Version:
JavaScript Obfuscation Tool.
451 lines (383 loc) • 11.1 kB
text/typescript
import { ok } from "assert";
import traverse, { walk } from "../traverse";
import { Location, Node } from "./gen";
import { getVarContext, isVarContext, isFunction } from "./insert";
/**
* Ensures the chain (object and parents) are connected.
* @param object
* @param parents
*/
export function validateChain(object: Node, parents: Node[]) {
if (!Array.isArray(parents)) {
throw new Error("parents need to be an array");
}
if (!object) {
throw new Error("object must be a node (not null)");
}
if (parents.length > 0) {
if (object == parents[0]) {
throw new Error("parent overlap");
}
if (!Object.values(parents[0]).includes(object)) {
console.log("parents=", parents);
console.log("object=", object);
throw new Error("parents[0] is not connected to object");
}
}
}
/**
* Returns detailed information about the given Identifier node.
* @param object
* @param parents
*/
export function getIdentifierInfo(object: Node, parents: Node[]) {
if (object.type != "Identifier") {
console.log(object);
throw new Error("object is not an Identifier, its a type=" + object.type);
}
var parent = parents[0] || ({} as Node);
var isAccessor =
parent.type == "MemberExpression" &&
parent.object != object &&
parent.property === object &&
!parent.computed;
var propIndex = parents.findIndex(
(x) => x.type == "Property" || x.type == "MethodDefinition"
);
var isPropertyKey =
propIndex != -1 &&
parents[propIndex].key == (parents[propIndex - 1] || object) &&
!parents[propIndex].computed;
var objectPatternIndex = parents.findIndex((x) => x.type == "ObjectPattern");
if (
objectPatternIndex !== -1 &&
parents[objectPatternIndex].properties == parents[objectPatternIndex - 1]
) {
if (objectPatternIndex - propIndex == 2) {
if (parents[propIndex].value === (parents[propIndex - 1] || object)) {
isPropertyKey = false;
}
}
}
var varIndex = parents.findIndex((x) => x.type == "VariableDeclarator");
var isVariableDeclaration =
varIndex != -1 &&
parents[varIndex].id == (parents[varIndex - 1] || object) &&
parents.find((x) => x.type == "VariableDeclaration");
var forIndex = parents.findIndex((x) => x.type == "ForStatement");
var isForInitializer =
forIndex != -1 &&
parents[forIndex].init == (parents[forIndex - 1] || object);
var functionIndex = parents.findIndex((x) => isFunction(x));
var isFunctionDeclaration =
functionIndex != -1 &&
parents[functionIndex].type == "FunctionDeclaration" &&
parents[functionIndex].id == object;
var isAFunctionParameter = isFunctionParameter(object, parents);
var isClauseParameter = false;
// Special case for Catch clauses
var clauseIndex = parents.findIndex((x) => x.type == "CatchClause");
if (clauseIndex != -1) {
if (parents[clauseIndex].param == (parents[clauseIndex - 1] || object)) {
isClauseParameter = true;
}
}
var isImportSpecifier =
(parent.type == "ImportDefaultSpecifier" ||
parent.type == "ImportSpecifier") &&
parent.local == object;
var isFunctionCall = parent.callee == object; // NewExpression and CallExpression
var assignmentIndex = parents.findIndex(
(p) => p.type === "AssignmentExpression"
);
var isAssignmentLeft =
assignmentIndex !== -1 &&
parents[assignmentIndex].left === (parents[assignmentIndex - 1] || object);
var isAssignmentValue =
assignmentIndex !== -1 &&
parents[assignmentIndex].right === (parents[assignmentIndex - 1] || object);
var isUpdateExpression = parent.type == "UpdateExpression";
var isClassDeclaration =
(parent.type == "ClassDeclaration" || parent.type == "ClassExpression") &&
parent.id == object;
var isMethodDefinition =
parent.type == "MethodDefinition" &&
parent.key == object &&
!parent.computed;
var isMetaProperty = parent.type == "MetaProperty";
var isLabel = parent.type == "LabeledStatement" && parent.label == object;
// Fix 1: Labels are properly identified
if (parent.type == "BreakStatement" || parent.type == "ContinueStatement") {
if (parent.label == object) {
isLabel = true;
}
}
var isDeleteExpression = false;
var deleteIndex = parents.findIndex(
(x) => x.type == "UnaryExpression" && x.operator == "delete"
);
if (deleteIndex != -1) {
isDeleteExpression = true;
}
var isReferenced =
!isAccessor &&
!isPropertyKey &&
!isMetaProperty &&
!isLabel &&
!object.name.startsWith("0") &&
!object.name.startsWith("'");
return {
/**
* MemberExpression: `parent.identifier`
*/
isAccessor,
/**
* Property: `{identifier: ...}`
*/
isPropertyKey,
/**
* `var identifier = ...`
*/
isVariableDeclaration,
/**
* `function identifier(){...}`
*/
isFunctionDeclaration,
/**
* `function a(identifier){...}`
*/
isFunctionParameter: isAFunctionParameter,
/**
* ```js
* try ... catch ( identifier ) {
* ...
* }
* ```
*/
isClauseParameter,
/**
* CallExpression: `identifier()`
*/
isFunctionCall,
/**
* AssignmentExpression: `identifier = ...`
*/
isAssignmentLeft,
/**
* AssignmentExpression (right): `x = identifier`
*/
isAssignmentValue,
/**
* UpdateExpression: `identifier++`
*/
isUpdateExpression,
/**
* ClassDeclaration `class identifier {...}`
*/
isClassDeclaration,
/**
* Method Definition inside a class body
* ```js
* class Rectangle {
* identifier(){...}
*
* get identifier(){...}
* }
* ```
*/
isMethodDefinition,
/**
* `new.target` or `yield.input`
*/
isMetaProperty,
/**
* LabelStatement: `identifier: for ( var i...)`
*/
isLabel,
/**
* ```js
* for (var i=0; ...) {
* ...
* }
* ```
*/
isForInitializer,
/**
* ```js
* import identifier from "...";
* import {key as identifier} from "...";
* ```
*/
isImportSpecifier,
/**
* ```js
* delete identifier[identifier]
* ```
*/
isDeleteExpression: isDeleteExpression,
spec: {
/**
* - `export function identifier()...`
* - `export var identifier = ...`
*/
isExported:
(isVariableDeclaration &&
parents[3] &&
parents[3].type == "ExportNamedDeclaration") ||
(isFunctionDeclaration &&
parents[1] &&
parents[1].type == "ExportNamedDeclaration"),
/**
* Is the Identifier defined, i.e a variable declaration, function declaration, parameter, or class definition
*/
isDefined:
isVariableDeclaration ||
isFunctionDeclaration ||
isAFunctionParameter ||
isClassDeclaration ||
isClauseParameter ||
isMethodDefinition ||
isImportSpecifier,
/**
* Is the Identifier modified, either by an `AssignmentExpression` or `UpdateExpression`
*/
isModified: isAssignmentLeft || isUpdateExpression || isDeleteExpression,
/**
* Is the Identifier referenced as a variable.
*
* - true: `if ( identifier ) {...}`
* - false `if ( obj.identifier ) {...}`
* - false `identifier: for ( var ...)`
* - false `var {identifier: ...}`
* - false `break identifier;`
*/
isReferenced: isReferenced,
},
};
}
export function getDefiningIdentifier(object: Node, parents: Node[]): Location {
ok(object.type == "Identifier", "must be identifier");
ok(typeof object.name === "string");
ok(
parents[parents.length - 1].type == "Program",
"root node must be type Program. Found '" +
parents[parents.length - 1].type +
"'"
);
var seen = new Set<Node>();
var i = 0;
for (var parent of parents) {
var l;
var bestScore = Infinity;
walk(parent, parents.slice(i + 1), (o, p) => {
// if (p.find((x) => seen.has(x))) {
// return "EXIT";
// }
if (o.type == "Identifier" && o.name === object.name && o !== object) {
var info = getIdentifierInfo(o, p);
if (info.spec.isDefined) {
var contexts = p.filter((x) => isVarContext(x));
var definingContext = info.isFunctionDeclaration
? getVarContext(p[0], p.slice(1))
: getVarContext(o, p);
if (parents.includes(definingContext)) {
var index = contexts.indexOf(definingContext);
if (index < bestScore) {
l = [o, p];
bestScore = index;
}
}
}
}
});
if (l) {
// console.log(l[0].name, "->", l[0], bestScore);
return l;
}
seen.add(parent);
i++;
}
}
export function isFunctionParameter(o: Node, p: Node[], c?: Node) {
ok(o);
ok(p);
validateChain(o, p);
if (o.type !== "Identifier") {
return false;
}
var object = p.find((x) => isFunction(x) && x.params);
if (!object) {
return false;
}
c = c || getVarContext(o, p);
if (c === object) {
var pIndex = p.indexOf(object.params);
if (pIndex == -1) {
return false;
}
var param = p[pIndex - 1] || o;
var paramIndex = object.params.indexOf(param);
ok(paramIndex !== -1);
var sliced = p.slice(0, pIndex);
var isReferenced = true;
var i = 0;
for (var node of sliced) {
var down = sliced[i - 1] || o;
ok(down);
if (node.type) {
if (node.type == "AssignmentPattern" && node.right === down) {
isReferenced = false;
break;
}
if (
node.type == "Property" &&
node.key === down &&
sliced[i + 2] &&
sliced[i + 2].type == "ObjectPattern"
) {
isReferenced = false;
break;
}
}
i++;
}
if (isReferenced) {
return true;
}
}
return false;
}
export function getFunctionParameters(
object: Node,
parents: Node[]
): [{ type: "Identifier"; name: string }, Node[]][] {
ok(isFunction(object));
ok(object.params);
var locations = [];
walk(object.params, [object, ...parents], (o, p) => {
if (o.type == "Identifier") {
if (isFunctionParameter(o, p, object)) {
locations.push([o, p]);
}
}
});
return locations;
}
export function containsLexicallyBoundVariables(object: Node, parents: Node[]) {
var contains = false;
walk(object, parents, (o, p) => {
if (o.type == "VariableDeclaration") {
if (o.kind === "let" || o.kind === "const") {
// Control Flow Flattening changes the lexical block, therefore this is not possible
// Maybe a transformation to remove let
contains = true;
return "EXIT";
}
}
if (o.type == "ClassDeclaration") {
contains = true;
return "EXIT";
}
});
return contains;
}