@dacely/toildefender 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/NOTICE.md +16 -0
- package/README.md +380 -0
- package/cli.js +168 -0
- package/defendjs.js +7 -0
- package/docs/all-modes-output.demo.js +673 -0
- package/estest.js +49 -0
- package/esutils.js +107 -0
- package/logger.js +28 -0
- package/obfuscator.js +534 -0
- package/package.json +108 -0
- package/processors/deadCode.js +62 -0
- package/processors/flattener.js +808 -0
- package/processors/health.js +55 -0
- package/processors/identifiers.js +256 -0
- package/processors/literalObfuscator.js +40 -0
- package/processors/literals.js +233 -0
- package/processors/methods.js +332 -0
- package/processors/modules.js +231 -0
- package/processors/normalizer.js +490 -0
- package/processors/numericVm.js +950 -0
- package/processors/postprocessing.js +69 -0
- package/processors/preprocessing.js +248 -0
- package/processors/scopes.js +177 -0
- package/processors/uglifier.js +26 -0
- package/processors/variables.js +185 -0
- package/toildefender.js +7 -0
- package/traverser.js +115 -0
- package/utils.js +135 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var assert = require("assert");
|
|
4
|
+
|
|
5
|
+
var _ = require("lodash");
|
|
6
|
+
|
|
7
|
+
var estest = require("../estest");
|
|
8
|
+
var traverser = require("../traverser");
|
|
9
|
+
var utils = require("../utils");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Merges nested bind calls like
|
|
13
|
+
* veilmark$bind(veilmark$bind(main, 1234), 5678)
|
|
14
|
+
* to
|
|
15
|
+
* veilmark$bind(main, 1234, 5678)
|
|
16
|
+
* @param {Node} node
|
|
17
|
+
* @returns {Node}
|
|
18
|
+
*/
|
|
19
|
+
function mergeNestedBinds(node) {
|
|
20
|
+
assert.ok(estest.isNode(node));
|
|
21
|
+
|
|
22
|
+
if (isBindCall(node)) {
|
|
23
|
+
return mergeNestedBinds(node.arguments[0]).concat(node.arguments.slice(1));
|
|
24
|
+
} else {
|
|
25
|
+
return [ node ];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Checks whether node is a call to veilmark$bind.
|
|
31
|
+
* @param {Node} node
|
|
32
|
+
* @returns {boolean}
|
|
33
|
+
*/
|
|
34
|
+
function isBindCall(node) {
|
|
35
|
+
assert.ok(estest.isNode(node));
|
|
36
|
+
|
|
37
|
+
return node.type == "CallExpression"
|
|
38
|
+
&& node.callee.type == "Identifier"
|
|
39
|
+
&& node.callee.name == "veilmark$bind";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = class Postprocessing {
|
|
43
|
+
|
|
44
|
+
constructor (logger) {
|
|
45
|
+
this.logger = logger;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Does postprocessing.
|
|
50
|
+
* @param {Node} ast Root node
|
|
51
|
+
* @return {Node} Root node
|
|
52
|
+
*/
|
|
53
|
+
do (ast) {
|
|
54
|
+
assert.ok(estest.isNode(ast));
|
|
55
|
+
|
|
56
|
+
return traverser.traverse(ast, [], (node, stack) => {
|
|
57
|
+
if (isBindCall(node)) {
|
|
58
|
+
node.arguments = mergeNestedBinds(node);
|
|
59
|
+
} else if (node.type == "BlockStatement" || node.type == "Program") {
|
|
60
|
+
node.body = node.body.filter(x => estest.isNode(x) && x.type != "EmptyStatement");
|
|
61
|
+
} else if (node.type == "SwitchCase") {
|
|
62
|
+
node.consequent = node.consequent.filter(x => estest.isNode(x) && x.type != "EmptyStatement");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return node;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
};
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _ = require("lodash");
|
|
4
|
+
|
|
5
|
+
var { Parser: Parser } = require("expr-eval");
|
|
6
|
+
|
|
7
|
+
const DEFAULT_PREPROCESSOR_VARIABLES = {
|
|
8
|
+
"true": 1,
|
|
9
|
+
"false": 0
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Generates code from an array of text nodes.
|
|
14
|
+
* @param {TextNode[]} nodes
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
function codeFromNodeArray(nodes) {
|
|
18
|
+
let lines = [];
|
|
19
|
+
for (let node of nodes) {
|
|
20
|
+
lines[node.line] = node.text;
|
|
21
|
+
}
|
|
22
|
+
return lines.join("\n");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Removes shebang from beginning of code.
|
|
27
|
+
* @param {string} code
|
|
28
|
+
* @returns {string}
|
|
29
|
+
*/
|
|
30
|
+
function removeShebangs(code) {
|
|
31
|
+
if (_.startsWith(code, "#!")) {
|
|
32
|
+
code = code.split(/\r?\n/).slice(1).join("\n");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return code;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class ArrayUtils {
|
|
39
|
+
/**
|
|
40
|
+
* Replaces all occurences of an object in an array with an other object in place.
|
|
41
|
+
* @param {Array} arr
|
|
42
|
+
* @param {Object} oldElem
|
|
43
|
+
* @param {Object} newElem
|
|
44
|
+
*/
|
|
45
|
+
static replace(arr, oldElem, newElem) {
|
|
46
|
+
for (let i = 0; i < arr.length; ++i) {
|
|
47
|
+
if (arr[i] == oldElem) {
|
|
48
|
+
arr[i] = newElem;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class Node {
|
|
55
|
+
constructor() {
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Evaluates tree into an array of TextNodes.
|
|
60
|
+
* @param {Object.<string, string>} defines Preprocessor variables
|
|
61
|
+
* @returns {TextNode[]}
|
|
62
|
+
*/
|
|
63
|
+
eval(defines) {
|
|
64
|
+
throw new Error("Node.eval() can not be called directly");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
class BlockNode extends Node {
|
|
69
|
+
constructor() {
|
|
70
|
+
super();
|
|
71
|
+
this.children = [];
|
|
72
|
+
}
|
|
73
|
+
eval(defines) {
|
|
74
|
+
return _.flatten(this.children.map(x => x.eval(defines)));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
class TextNode extends Node {
|
|
79
|
+
constructor(text) {
|
|
80
|
+
super();
|
|
81
|
+
this.text = text;
|
|
82
|
+
}
|
|
83
|
+
eval(defines) {
|
|
84
|
+
return [this];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
class DefineNode extends Node {
|
|
89
|
+
constructor(left, right) {
|
|
90
|
+
super();
|
|
91
|
+
this.left = left;
|
|
92
|
+
this.right = right;
|
|
93
|
+
}
|
|
94
|
+
eval(defines) {
|
|
95
|
+
defines[this.left] = this.right;
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
class ErrorNode extends Node {
|
|
101
|
+
constructor(message) {
|
|
102
|
+
super();
|
|
103
|
+
this.message = message;
|
|
104
|
+
}
|
|
105
|
+
eval(defines) {
|
|
106
|
+
throw new Error(this.message);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
class IfBlockNode extends BlockNode {
|
|
111
|
+
constructor(condition) {
|
|
112
|
+
super();
|
|
113
|
+
this.condition = condition;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Evaluates condition.
|
|
117
|
+
* @param {Object.<string, string>} defines Preprocessor variables
|
|
118
|
+
* @returns {boolean}
|
|
119
|
+
*/
|
|
120
|
+
evalCond(defines) {
|
|
121
|
+
let condition = this.condition;
|
|
122
|
+
condition = condition.replace(/!defined\(([\w\d]+)\)/, (match, p1) => !defines.hasOwnProperty(p1) ? "true" : "false");
|
|
123
|
+
condition = condition.replace(/defined\(([\w\d]+)\)/, (match, p1) => defines.hasOwnProperty(p1) ? "true" : "false");
|
|
124
|
+
return Parser.evaluate(condition, defines);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Evaluates node with given condition result.
|
|
128
|
+
* @param {Object.<string, string>} defines Preprocessor variables
|
|
129
|
+
* @returns {boolean}
|
|
130
|
+
*/
|
|
131
|
+
evalWith(defines, result) {
|
|
132
|
+
if (result) {
|
|
133
|
+
return super.eval(defines);
|
|
134
|
+
} else {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
eval(defines) {
|
|
139
|
+
return this.evalWith(defines, this.evalCond(defines));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
class ElseBlockNode extends BlockNode {
|
|
144
|
+
constructor(ifNode) {
|
|
145
|
+
super();
|
|
146
|
+
this.ifNode = ifNode;
|
|
147
|
+
}
|
|
148
|
+
eval(defines) {
|
|
149
|
+
if (this.ifNode.evalCond(defines)) {
|
|
150
|
+
return this.ifNode.evalWith(defines, true);
|
|
151
|
+
} else {
|
|
152
|
+
return super.eval(defines);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = class Preprocessing {
|
|
158
|
+
|
|
159
|
+
constructor (logger) {
|
|
160
|
+
this.logger = logger;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Processes preprocessor directives.
|
|
165
|
+
* @param {string} code
|
|
166
|
+
* @param {Object.<string, string>} preprocessorVariables
|
|
167
|
+
* @returns {string} Processed code
|
|
168
|
+
*/
|
|
169
|
+
processDirectives (code, preprocessorVariables) {
|
|
170
|
+
let lines = code.split(/\r?\n/), stack = [new BlockNode()];
|
|
171
|
+
|
|
172
|
+
let defines = {};
|
|
173
|
+
_.merge(defines, DEFAULT_PREPROCESSOR_VARIABLES);
|
|
174
|
+
_.merge(defines, preprocessorVariables);
|
|
175
|
+
|
|
176
|
+
for (let i = 0; i < lines.length; ++i) {
|
|
177
|
+
let line = lines[i];
|
|
178
|
+
let [, directive, parameters] = /^\s*\/\/\s*#(\w+)\s*(.+)?$/.exec(line) || [];
|
|
179
|
+
switch (directive) {
|
|
180
|
+
case undefined: {
|
|
181
|
+
let elem = new TextNode(line);
|
|
182
|
+
elem.line = i;
|
|
183
|
+
_.last(stack).children.push(elem);
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case "define": {
|
|
187
|
+
let [, left, right] = /^\s*([\w\d]+)\s*(?:=\s*([\w\d]+))?\s*$/.exec(parameters);
|
|
188
|
+
let elem = new DefineNode(left, right);
|
|
189
|
+
elem.line = i;
|
|
190
|
+
_.last(stack).children.push(elem);
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case "error": {
|
|
194
|
+
let elem = new ErrorNode(parameters);
|
|
195
|
+
elem.line = i;
|
|
196
|
+
_.last(stack).children.push(elem);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case "if":
|
|
200
|
+
case "ifdef":
|
|
201
|
+
case "ifndef": {
|
|
202
|
+
let elem =
|
|
203
|
+
directive == "if" ? new IfBlockNode(parameters) :
|
|
204
|
+
directive == "ifdef" ? new IfBlockNode(`defined(${parameters})`) :
|
|
205
|
+
directive == "ifndef" ? new IfBlockNode(`!defined(${parameters})`) :
|
|
206
|
+
null;
|
|
207
|
+
elem.line = i;
|
|
208
|
+
_.last(stack).children.push(elem);
|
|
209
|
+
stack.push(elem);
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
case "else": {
|
|
213
|
+
let elem = new ElseBlockNode(stack.pop());
|
|
214
|
+
elem.line = i;
|
|
215
|
+
ArrayUtils.replace(_.last(stack).children, elem.ifNode, elem);
|
|
216
|
+
stack.push(elem);
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
case "endif": {
|
|
220
|
+
let elem = stack.pop();
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
default: {
|
|
224
|
+
this.logger.warn(`Unknown preprocessor directive #${directive}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (stack.length > 1) {
|
|
230
|
+
this.logger.warn("stack.length != 1 (preprocessor directive closing tag missing?)");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return codeFromNodeArray(stack[0].eval(defines));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Does preprocessing.
|
|
238
|
+
* @param {string} code
|
|
239
|
+
* @param {Object.<string, string>} preprocessorVariables
|
|
240
|
+
* @returns {string}
|
|
241
|
+
*/
|
|
242
|
+
process (code, preprocessorVariables) {
|
|
243
|
+
code = this.processDirectives(code, preprocessorVariables);
|
|
244
|
+
code = removeShebangs(code);
|
|
245
|
+
return code;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var assert = require("assert");
|
|
4
|
+
|
|
5
|
+
var _ = require("lodash");
|
|
6
|
+
|
|
7
|
+
var estest = require("../estest");
|
|
8
|
+
var ESUtils = require("../esutils");
|
|
9
|
+
var traverser = require("../traverser");
|
|
10
|
+
var utils = require("../utils");
|
|
11
|
+
|
|
12
|
+
module.exports = class Scopes {
|
|
13
|
+
|
|
14
|
+
constructor (logger) {
|
|
15
|
+
this.logger = logger;
|
|
16
|
+
this.esutils = new ESUtils(logger);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Moves all variables to scope arrays like
|
|
21
|
+
* var a = 1, b = 2;
|
|
22
|
+
* to
|
|
23
|
+
* var $$scope$abc = [];
|
|
24
|
+
* $$scope$abc[0] = 1;
|
|
25
|
+
* $$scope$abc[1] = 2;
|
|
26
|
+
* @param {Node} ast Root node
|
|
27
|
+
* @param {ScopeManager} scopeManager Scope manager
|
|
28
|
+
*/
|
|
29
|
+
createScopeObjects (ast, scopeManager) {
|
|
30
|
+
assert.ok(estest.isNode(ast));
|
|
31
|
+
|
|
32
|
+
this.esutils.setParentsRecursive(ast);
|
|
33
|
+
var scopes = scopeManager.acquireAll(ast);
|
|
34
|
+
var rngAlpha = new utils.UniqueRandomAlpha(3);
|
|
35
|
+
scopeManager.scopes.forEach(scope => {
|
|
36
|
+
var scopeVarName = `$$scope$${rngAlpha.get()}`;
|
|
37
|
+
|
|
38
|
+
var counter = 0;
|
|
39
|
+
var scopeDecl = {
|
|
40
|
+
type: "VariableDeclaration",
|
|
41
|
+
kind: "var",
|
|
42
|
+
declarations: [
|
|
43
|
+
{
|
|
44
|
+
type: "VariableDeclarator",
|
|
45
|
+
id: { type: "Identifier", name: scopeVarName },
|
|
46
|
+
init: { type: "ArrayExpression", elements: [] }
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
veilmark$scopeObject: true
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
this.esutils.insertIntoScope(scope, scopeDecl);
|
|
53
|
+
|
|
54
|
+
scope.variables.forEach(variable => {
|
|
55
|
+
var index = counter++;
|
|
56
|
+
|
|
57
|
+
variable.defs.forEach(def => {
|
|
58
|
+
if (def.type == "Variable") {
|
|
59
|
+
assert(def.parent.type == "VariableDeclaration");
|
|
60
|
+
def.parent.declarations = def.parent.declarations.filter(x => x != def.node);
|
|
61
|
+
var replacement = [];
|
|
62
|
+
if (def.node.init) {
|
|
63
|
+
replacement.push({
|
|
64
|
+
type: "ExpressionStatement",
|
|
65
|
+
expression: {
|
|
66
|
+
type: "AssignmentExpression",
|
|
67
|
+
operator: "=",
|
|
68
|
+
left: {
|
|
69
|
+
type: "MemberExpression",
|
|
70
|
+
object: { type: "Identifier", name: scopeVarName },
|
|
71
|
+
property: { type: "Literal", value: index },
|
|
72
|
+
computed: true,
|
|
73
|
+
veilmark$scopeObjectReference: true
|
|
74
|
+
},
|
|
75
|
+
right: def.node.init
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (def.parent.declarations.length > 0) {
|
|
80
|
+
replacement.push(def.parent);
|
|
81
|
+
}
|
|
82
|
+
if (replacement.length == 0) {
|
|
83
|
+
this.esutils.replaceNode(scope.block, def.parent, { type: "EmptyStatement" });
|
|
84
|
+
} else if (replacement.length == 1) {
|
|
85
|
+
this.esutils.replaceNode(scope.block, def.parent, replacement[0] );
|
|
86
|
+
} else {
|
|
87
|
+
this.esutils.replaceNode(scope.block, def.parent, { type: "BlockStatement", body: replacement });
|
|
88
|
+
}
|
|
89
|
+
variable.references.forEach(reference => {
|
|
90
|
+
// References can not be replaced via replaceNodeEx for whatever reason
|
|
91
|
+
this.esutils.replaceNode(scope.block, reference.identifier, {
|
|
92
|
+
type: "MemberExpression",
|
|
93
|
+
object: { type: "Identifier", name: scopeVarName },
|
|
94
|
+
property: { type: "Literal", value: index },
|
|
95
|
+
computed: true,
|
|
96
|
+
veilmark$scopeObjectReference: true
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
} else if (def.type == "CatchClause") {
|
|
100
|
+
Object.defineProperty(scope.block, "veilmark$exception", {
|
|
101
|
+
value: {
|
|
102
|
+
type: "MemberExpression",
|
|
103
|
+
object: { type: "Identifier", name: scopeVarName },
|
|
104
|
+
property: { type: "Literal", value: index },
|
|
105
|
+
computed: true,
|
|
106
|
+
veilmark$scopeObjectReference: true
|
|
107
|
+
},
|
|
108
|
+
configurable: true
|
|
109
|
+
});
|
|
110
|
+
this.esutils.insertIntoScope(scope, {
|
|
111
|
+
type: "ExpressionStatement",
|
|
112
|
+
expression: {
|
|
113
|
+
type: "AssignmentExpression",
|
|
114
|
+
operator: "=",
|
|
115
|
+
left: {
|
|
116
|
+
type: "MemberExpression",
|
|
117
|
+
object: { type: "Identifier", name: scopeVarName },
|
|
118
|
+
property: { type: "Literal", value: index },
|
|
119
|
+
computed: true,
|
|
120
|
+
veilmark$scopeObjectReference: true
|
|
121
|
+
},
|
|
122
|
+
right: def.name
|
|
123
|
+
}
|
|
124
|
+
}, 1);
|
|
125
|
+
variable.references.forEach(reference => {
|
|
126
|
+
this.esutils.replaceNode(scope.block, reference.identifier, {
|
|
127
|
+
type: "MemberExpression",
|
|
128
|
+
object: { type: "Identifier", name: scopeVarName },
|
|
129
|
+
property: { type: "Literal", value: index },
|
|
130
|
+
computed: true,
|
|
131
|
+
veilmark$scopeObjectReference: true
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
} else if (def.type == "FunctionName") {
|
|
135
|
+
variable.references.forEach(reference => {
|
|
136
|
+
this.esutils.replaceNode(scope.block, reference.identifier, {
|
|
137
|
+
type: "CallExpression",
|
|
138
|
+
callee: { type: "Identifier", name: "veilmark$bind" },
|
|
139
|
+
arguments: [
|
|
140
|
+
reference.identifier,
|
|
141
|
+
{ type: "Identifier", name: scopeVarName }
|
|
142
|
+
]
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
traverser.traverse(scope.block, [], (node, stack) => {
|
|
150
|
+
if (scope.block == node) {
|
|
151
|
+
return node;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (node.type.indexOf("Function") == 0) {
|
|
155
|
+
node.params.unshift({
|
|
156
|
+
type: "Identifier",
|
|
157
|
+
name: scopeVarName
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (node.type == "FunctionExpression") {
|
|
162
|
+
return {
|
|
163
|
+
type: "CallExpression",
|
|
164
|
+
callee: { type: "Identifier", name: "veilmark$bind" },
|
|
165
|
+
arguments: [
|
|
166
|
+
node,
|
|
167
|
+
{ type: "Identifier", name: scopeVarName }
|
|
168
|
+
]
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return node;
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var assert = require("assert");
|
|
4
|
+
|
|
5
|
+
var esshorten = require("esshorten");
|
|
6
|
+
|
|
7
|
+
var estest = require("../estest");
|
|
8
|
+
|
|
9
|
+
module.exports = class Uglifier {
|
|
10
|
+
|
|
11
|
+
constructor (logger) {
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Uglifies tree.
|
|
17
|
+
* @param {Node} ast Root node
|
|
18
|
+
* @returns {Node} Root node
|
|
19
|
+
*/
|
|
20
|
+
uglify (ast) {
|
|
21
|
+
assert.ok(estest.isNode(ast));
|
|
22
|
+
|
|
23
|
+
return esshorten.mangle(ast);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var assert = require("assert");
|
|
4
|
+
|
|
5
|
+
var _ = require("lodash");
|
|
6
|
+
|
|
7
|
+
var estest = require("../estest");
|
|
8
|
+
var ESUtils = require("../esutils");
|
|
9
|
+
var traverser = require("../traverser");
|
|
10
|
+
var utils = require("../utils");
|
|
11
|
+
|
|
12
|
+
module.exports = class Variables {
|
|
13
|
+
|
|
14
|
+
constructor (logger) {
|
|
15
|
+
this.logger = logger;
|
|
16
|
+
this.esutils = new ESUtils(logger);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Removes the id property from FunctionExpressions.
|
|
21
|
+
* They trip up functionDeclarationToExpression and escope (?),
|
|
22
|
+
* causing scopes.js to incorrectly rename some references.
|
|
23
|
+
* @param {Node} ast Root node
|
|
24
|
+
* @returns {Node} Root node
|
|
25
|
+
*/
|
|
26
|
+
removeFunctionExpressionIds (ast) {
|
|
27
|
+
return traverser.traverse(ast, [], (node, stack) => {
|
|
28
|
+
if (node.type == "FunctionExpression" && node.id) {
|
|
29
|
+
node.id = null;
|
|
30
|
+
}
|
|
31
|
+
return node;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Converts function declarations like
|
|
37
|
+
* function test() { ... }
|
|
38
|
+
* to function expression variables like
|
|
39
|
+
* var test = function() { ... };
|
|
40
|
+
* @param {Node} ast Root node
|
|
41
|
+
* @param {ScopeManager} scopeManager Scope manager
|
|
42
|
+
*/
|
|
43
|
+
functionDeclarationToExpression (ast, scopeManager) {
|
|
44
|
+
assert.ok(estest.isNode(ast));
|
|
45
|
+
|
|
46
|
+
this.esutils.setParentsRecursive(ast);
|
|
47
|
+
|
|
48
|
+
scopeManager.scopes.forEach(scope => {
|
|
49
|
+
scope.variables.forEach(variable => {
|
|
50
|
+
variable.defs.forEach(def => {
|
|
51
|
+
if (def.type == "FunctionName") {
|
|
52
|
+
assert(estest.isFunction(def.node));
|
|
53
|
+
/**
|
|
54
|
+
* Here you have to ensure that def.node is statement.
|
|
55
|
+
* Expressions like { foo: function() { ... }} are parsed
|
|
56
|
+
* as a FunctionExpression with an id, which are then
|
|
57
|
+
* mistakingly replaced with EmptyStatements.
|
|
58
|
+
*/
|
|
59
|
+
if (estest.isStatement(def.node)) {
|
|
60
|
+
this.esutils.replaceNode(ast, def.node, { type: "EmptyStatement" });
|
|
61
|
+
this.esutils.insertIntoScope(scope, {
|
|
62
|
+
type: "VariableDeclaration",
|
|
63
|
+
kind: "var",
|
|
64
|
+
declarations: [
|
|
65
|
+
{
|
|
66
|
+
type: "VariableDeclarator",
|
|
67
|
+
id: def.node.id,
|
|
68
|
+
init: {
|
|
69
|
+
type: "FunctionExpression",
|
|
70
|
+
params: def.node.params,
|
|
71
|
+
body: def.node.body
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Renames identifiers with unique names, e.g.
|
|
85
|
+
* var a, b = 5;
|
|
86
|
+
* to
|
|
87
|
+
* var $$var$123$a, $$var$123$b = 5;
|
|
88
|
+
* @param {Node} ast Root node
|
|
89
|
+
* @param {ScopeManager} scopeManager Scope manager
|
|
90
|
+
*/
|
|
91
|
+
obfuscateIdentifiers (ast, scopeManager) {
|
|
92
|
+
scopeManager.scopes.forEach(scope => {
|
|
93
|
+
if (scope.isStatic()) {
|
|
94
|
+
scope.variables.sort((a, b) => {
|
|
95
|
+
if (a.tainted) {
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
if (b.tainted) {
|
|
99
|
+
return -1;
|
|
100
|
+
}
|
|
101
|
+
return (b.identifiers.length + b.references.length) - (a.identifiers.length + a.references.length);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
for (let variable of scope.variables) {
|
|
105
|
+
var name = "$$var$" + utils.hash(variable) + "$" + variable.name;
|
|
106
|
+
|
|
107
|
+
if (variable.tainted) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (variable.identifiers.length === 0) {
|
|
112
|
+
// do not change names since this is a special name
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (let def of variable.identifiers) {
|
|
117
|
+
// change definition's name
|
|
118
|
+
def.name = name;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (let ref of variable.references) {
|
|
122
|
+
// change reference's name
|
|
123
|
+
ref.identifier.name = name;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Replaces direct parameter references like
|
|
132
|
+
* function (a) {
|
|
133
|
+
* return a;
|
|
134
|
+
* }
|
|
135
|
+
* to copys like
|
|
136
|
+
* function (a) {
|
|
137
|
+
* var $$arg$abc = a;
|
|
138
|
+
* return $$arg$abc;
|
|
139
|
+
* }
|
|
140
|
+
* @param {Node} ast Root node
|
|
141
|
+
* @param {ScopeManager} scopeManager Scope manager
|
|
142
|
+
*/
|
|
143
|
+
redefineParameters (ast, scopeManager) {
|
|
144
|
+
function getArgumentIndex(method, identifier) {
|
|
145
|
+
assert(method.type == "FunctionDeclaration" || method.type == "FunctionExpression");
|
|
146
|
+
assert(identifier.type == "Identifier");
|
|
147
|
+
for (var i = 0; i < method.params.length; ++i) {
|
|
148
|
+
if (method.params[i].name == identifier.name) {
|
|
149
|
+
return i;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return -1;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
var rng = new utils.UniqueRandomAlpha(3);
|
|
156
|
+
|
|
157
|
+
scopeManager.scopes.forEach(scope => {
|
|
158
|
+
scope.variables.forEach(variable => {
|
|
159
|
+
variable.defs.forEach(def => {
|
|
160
|
+
if (def.type == "Parameter") {
|
|
161
|
+
assert(def.name.type == "Identifier");
|
|
162
|
+
var name = "$$arg$" + rng.get();
|
|
163
|
+
|
|
164
|
+
this.esutils.insertIntoScope(scope, {
|
|
165
|
+
type: "VariableDeclaration",
|
|
166
|
+
kind: "var",
|
|
167
|
+
declarations: [
|
|
168
|
+
{
|
|
169
|
+
type: "VariableDeclarator",
|
|
170
|
+
id: { type: "Identifier", name: name },
|
|
171
|
+
init: def.name
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
variable.references.forEach(reference => {
|
|
177
|
+
reference.identifier.name = name;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
};
|