@nodesecure/js-x-ray 6.3.0 → 7.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.
Files changed (52) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +106 -18
  3. package/index.d.ts +16 -3
  4. package/index.js +37 -134
  5. package/package.json +9 -9
  6. package/src/AstAnalyser.js +137 -0
  7. package/src/Deobfuscator.js +192 -0
  8. package/src/EntryFilesAnalyser.js +99 -0
  9. package/src/JsSourceParser.js +57 -0
  10. package/src/NodeCounter.js +76 -0
  11. package/src/ProbeRunner.js +140 -0
  12. package/src/{Analysis.js → SourceFile.js} +55 -68
  13. package/src/obfuscators/freejsobfuscator.js +9 -9
  14. package/src/obfuscators/jjencode.js +7 -7
  15. package/src/obfuscators/jsfuck.js +6 -6
  16. package/src/obfuscators/obfuscator-io.js +7 -7
  17. package/src/obfuscators/trojan-source.js +28 -28
  18. package/src/probes/isArrayExpression.js +32 -30
  19. package/src/probes/isBinaryExpression.js +5 -3
  20. package/src/probes/isImportDeclaration.js +8 -5
  21. package/src/probes/isLiteral.js +17 -9
  22. package/src/probes/isLiteralRegex.js +5 -3
  23. package/src/probes/isRegexObject.js +5 -3
  24. package/src/probes/isRequire/RequireCallExpressionWalker.js +93 -0
  25. package/src/probes/isRequire/isRequire.js +142 -0
  26. package/src/probes/isUnsafeCallee.js +15 -5
  27. package/src/probes/isWeakCrypto.js +5 -3
  28. package/src/utils/exportAssignmentHasRequireLeave.js +40 -0
  29. package/src/utils/extractNode.js +14 -0
  30. package/src/utils/index.js +8 -0
  31. package/src/utils/isNode.js +5 -0
  32. package/src/utils/isOneLineExpressionExport.js +18 -0
  33. package/src/utils/isUnsafeCallee.js +28 -0
  34. package/src/utils/notNullOrUndefined.js +3 -0
  35. package/src/utils/rootLocation.js +3 -0
  36. package/src/utils/toArrayLocation.js +11 -0
  37. package/src/warnings.js +1 -1
  38. package/types/api.d.ts +79 -18
  39. package/src/ASTDeps.js +0 -63
  40. package/src/obfuscators/index.js +0 -69
  41. package/src/probes/index.js +0 -70
  42. package/src/probes/isAssignmentExpression.js +0 -29
  43. package/src/probes/isClassDeclaration.js +0 -25
  44. package/src/probes/isFunction.js +0 -38
  45. package/src/probes/isMemberExpression.js +0 -16
  46. package/src/probes/isMethodDefinition.js +0 -25
  47. package/src/probes/isObjectExpression.js +0 -29
  48. package/src/probes/isRequire.js +0 -164
  49. package/src/probes/isUnaryExpression.js +0 -26
  50. package/src/probes/isVariableDeclaration.js +0 -30
  51. package/src/utils.js +0 -48
  52. package/types/astdeps.d.ts +0 -34
@@ -1,29 +0,0 @@
1
- // Import Third-party Dependencies
2
- import { getVariableDeclarationIdentifiers } from "@nodesecure/estree-ast-utils";
3
-
4
- /**
5
- * @description Search for AssignmentExpression (Not to be confused with AssignmentPattern).
6
- *
7
- * @see https://github.com/estree/estree/blob/master/es5.md#assignmentexpression
8
- * @example
9
- * (foo = 5)
10
- */
11
- function validateNode(node) {
12
- return [
13
- node.type === "AssignmentExpression"
14
- ];
15
- }
16
-
17
- function main(node, options) {
18
- const { analysis } = options;
19
-
20
- analysis.idtypes.assignExpr++;
21
- for (const { name } of getVariableDeclarationIdentifiers(node.left)) {
22
- analysis.identifiersName.push({ name, type: "assignExpr" });
23
- }
24
- }
25
-
26
- export default {
27
- name: "isAssignmentExpression",
28
- validateNode, main, breakOnMatch: false
29
- };
@@ -1,25 +0,0 @@
1
- // Import Internal Dependencies
2
- import { extractNode } from "../utils.js";
3
-
4
- // CONSTANTS
5
- const kIdExtractor = extractNode("Identifier");
6
-
7
- function validateNode(node) {
8
- return [
9
- node.type === "ClassDeclaration"
10
- ];
11
- }
12
-
13
- function main(node, options) {
14
- const { analysis } = options;
15
-
16
- kIdExtractor(
17
- ({ name }) => analysis.identifiersName.push({ name, type: "class" }),
18
- [node.id, node.superClass]
19
- );
20
- }
21
-
22
- export default {
23
- name: "isClassDeclaration",
24
- validateNode, main, breakOnMatch: false
25
- };
@@ -1,38 +0,0 @@
1
- // Import Internal Dependencies
2
- import { extractNode } from "../utils.js";
3
-
4
- // CONSTANTS
5
- const kIdExtractor = extractNode("Identifier");
6
-
7
- /**
8
- * @description Search for FunctionDeclaration AST Node.
9
- *
10
- * @see https://github.com/estree/estree/blob/master/es5.md#functiondeclaration
11
- * @example
12
- * function foo() {}
13
- */
14
- function validateNode(node) {
15
- return [
16
- node.type === "FunctionDeclaration" || node.type === "FunctionExpression"
17
- ];
18
- }
19
-
20
- function main(node, options) {
21
- const { analysis } = options;
22
-
23
- kIdExtractor(
24
- ({ name }) => analysis.identifiersName.push({ name, type: "params" }),
25
- node.params
26
- );
27
-
28
- if (node.id === null || node.id.type !== "Identifier") {
29
- return;
30
- }
31
- analysis.idtypes.functionDeclaration++;
32
- analysis.identifiersName.push({ name: node.id.name, type: "functionDeclaration" });
33
- }
34
-
35
- export default {
36
- name: "isFunctionDeclaration",
37
- validateNode, main, breakOnMatch: false
38
- };
@@ -1,16 +0,0 @@
1
- function validateNode(node) {
2
- return [
3
- node.type === "MemberExpression"
4
- ];
5
- }
6
-
7
- function main(node, options) {
8
- const { analysis } = options;
9
-
10
- analysis.counter[node.computed ? "computedMemberExpr" : "memberExpr"]++;
11
- }
12
-
13
- export default {
14
- name: "isMemberExpression",
15
- validateNode, main, breakOnMatch: true, breakGroup: "import"
16
- };
@@ -1,25 +0,0 @@
1
- // Import Internal Dependencies
2
- import { extractNode } from "../utils.js";
3
-
4
- // CONSTANTS
5
- const kIdExtractor = extractNode("Identifier");
6
-
7
- function validateNode(node) {
8
- return [
9
- node.type === "MethodDefinition"
10
- ];
11
- }
12
-
13
- function main(node, options) {
14
- const { analysis } = options;
15
-
16
- kIdExtractor(
17
- ({ name }) => analysis.identifiersName.push({ name, type: "method" }),
18
- [node.key]
19
- );
20
- }
21
-
22
- export default {
23
- name: "isMethodDefinition",
24
- validateNode, main, breakOnMatch: false
25
- };
@@ -1,29 +0,0 @@
1
- /**
2
- * @description Search for ObjectExpression AST Node (commonly known as Object).
3
- * @see https://github.com/estree/estree/blob/master/es5.md#objectexpression
4
- * @example
5
- * { foo: "bar" }
6
- */
7
- function validateNode(node) {
8
- return [
9
- node.type === "ObjectExpression"
10
- ];
11
- }
12
-
13
- function main(node, options) {
14
- const { analysis } = options;
15
-
16
- for (const property of node.properties) {
17
- if (property.type !== "Property" || property.key.type !== "Identifier") {
18
- continue;
19
- }
20
-
21
- analysis.idtypes.property++;
22
- analysis.identifiersName.push({ name: property.key.name, type: "property" });
23
- }
24
- }
25
-
26
- export default {
27
- name: "isObjectExpression",
28
- validateNode, main, breakOnMatch: false
29
- };
@@ -1,164 +0,0 @@
1
- /* eslint-disable consistent-return */
2
-
3
- // Import Third-party Dependencies
4
- import { Hex } from "@nodesecure/sec-literal";
5
- import { walk } from "estree-walker";
6
- import {
7
- concatBinaryExpression,
8
- arrayExpressionToString,
9
- getMemberExpressionIdentifier,
10
- getCallExpressionIdentifier,
11
- getCallExpressionArguments
12
- } from "@nodesecure/estree-ast-utils";
13
-
14
- function validateNode(node, { tracer }) {
15
- const id = getCallExpressionIdentifier(node);
16
- if (id === null) {
17
- return [false];
18
- }
19
-
20
- const data = tracer.getDataFromIdentifier(id);
21
-
22
- return [
23
- data !== null && data.name === "require",
24
- data?.identifierOrMemberExpr ?? void 0
25
- ];
26
- }
27
-
28
- function main(node, options) {
29
- const { analysis } = options;
30
- const { tracer } = analysis;
31
-
32
- if (node.arguments.length === 0) {
33
- return;
34
- }
35
- const arg = node.arguments.at(0);
36
-
37
- switch (arg.type) {
38
- // const foo = "http"; require(foo);
39
- case "Identifier":
40
- if (analysis.tracer.literalIdentifiers.has(arg.name)) {
41
- analysis.dependencies.add(
42
- analysis.tracer.literalIdentifiers.get(arg.name),
43
- node.loc
44
- );
45
- }
46
- else {
47
- analysis.addWarning("unsafe-import", null, node.loc);
48
- }
49
- break;
50
-
51
- // require("http")
52
- case "Literal":
53
- analysis.dependencies.add(arg.value, node.loc);
54
- break;
55
-
56
- // require(["ht", "tp"])
57
- case "ArrayExpression": {
58
- const value = [...arrayExpressionToString(arg, { tracer })]
59
- .join("")
60
- .trim();
61
-
62
- if (value === "") {
63
- analysis.addWarning("unsafe-import", null, node.loc);
64
- }
65
- else {
66
- analysis.dependencies.add(value, node.loc);
67
- }
68
- break;
69
- }
70
-
71
- // require("ht" + "tp");
72
- case "BinaryExpression": {
73
- if (arg.operator !== "+") {
74
- analysis.addWarning("unsafe-import", null, node.loc);
75
- break;
76
- }
77
-
78
- try {
79
- const iter = concatBinaryExpression(arg, {
80
- tracer, stopOnUnsupportedNode: true
81
- });
82
-
83
- analysis.dependencies.add([...iter].join(""), node.loc);
84
- }
85
- catch {
86
- analysis.addWarning("unsafe-import", null, node.loc);
87
- }
88
- break;
89
- }
90
-
91
- // require(Buffer.from("...", "hex").toString());
92
- case "CallExpression": {
93
- walkRequireCallExpression(arg, tracer)
94
- .forEach((depName) => analysis.dependencies.add(depName, node.loc, true));
95
-
96
- analysis.addWarning("unsafe-import", null, node.loc);
97
-
98
- // We skip walking the tree to avoid anymore warnings...
99
- return Symbol.for("skipWalk");
100
- }
101
-
102
- default:
103
- analysis.addWarning("unsafe-import", null, node.loc);
104
- }
105
- }
106
-
107
- function walkRequireCallExpression(nodeToWalk, tracer) {
108
- const dependencies = new Set();
109
-
110
- walk(nodeToWalk, {
111
- enter(node) {
112
- if (node.type !== "CallExpression" || node.arguments.length === 0) {
113
- return;
114
- }
115
-
116
- const rootArgument = node.arguments.at(0);
117
- if (rootArgument.type === "Literal" && Hex.isHex(rootArgument.value)) {
118
- dependencies.add(Buffer.from(rootArgument.value, "hex").toString());
119
-
120
- return this.skip();
121
- }
122
-
123
- const fullName = node.callee.type === "MemberExpression" ?
124
- [...getMemberExpressionIdentifier(node.callee)].join(".") :
125
- node.callee.name;
126
- const tracedFullName = tracer.getDataFromIdentifier(fullName)?.identifierOrMemberExpr ?? fullName;
127
-
128
- switch (tracedFullName) {
129
- case "atob": {
130
- const nodeArguments = getCallExpressionArguments(node, { tracer });
131
- if (nodeArguments !== null) {
132
- dependencies.add(
133
- Buffer.from(nodeArguments.at(0), "base64").toString()
134
- );
135
- }
136
-
137
- break;
138
- }
139
- case "Buffer.from": {
140
- const [element] = node.arguments;
141
-
142
- if (element.type === "ArrayExpression") {
143
- const depName = [...arrayExpressionToString(element)].join("").trim();
144
- dependencies.add(depName);
145
- }
146
- break;
147
- }
148
- case "require.resolve": {
149
- if (rootArgument.type === "Literal") {
150
- dependencies.add(rootArgument.value);
151
- }
152
- break;
153
- }
154
- }
155
- }
156
- });
157
-
158
- return [...dependencies];
159
- }
160
-
161
- export default {
162
- name: "isRequire",
163
- validateNode, main, breakOnMatch: true, breakGroup: "import"
164
- };
@@ -1,26 +0,0 @@
1
- /**
2
- * @description Search for UnaryExpression AST Node
3
- * @see https://github.com/estree/estree/blob/master/es5.md#unaryexpression
4
- * @example
5
- * -2
6
- */
7
- function validateNode(node) {
8
- return [
9
- node.type === "UnaryExpression"
10
- ];
11
- }
12
-
13
- function main(node, options) {
14
- const { analysis } = options;
15
-
16
- // Example: !![]
17
- // See: https://docs.google.com/document/d/11ZrfW0bDQ-kd7Gr_Ixqyk8p3TGvxckmhFH3Z8dFoPhY/edit#
18
- if (node.argument.type === "UnaryExpression" && node.argument.argument.type === "ArrayExpression") {
19
- analysis.counter.doubleUnaryArray++;
20
- }
21
- }
22
-
23
- export default {
24
- name: "isUnaryExpression",
25
- validateNode, main, breakOnMatch: false
26
- };
@@ -1,30 +0,0 @@
1
- // Import Third-party Dependencies
2
- import {
3
- getVariableDeclarationIdentifiers
4
- } from "@nodesecure/estree-ast-utils";
5
-
6
- // In case we are matching a Variable declaration, we have to save the identifier
7
- // This allow the AST Analysis to retrieve required dependency when the stmt is mixed with variables.
8
- function validateNode(node) {
9
- return [
10
- node.type === "VariableDeclaration"
11
- ];
12
- }
13
-
14
- function main(mainNode, options) {
15
- const { analysis } = options;
16
-
17
- analysis.varkinds[mainNode.kind]++;
18
-
19
- for (const node of mainNode.declarations) {
20
- analysis.idtypes.variableDeclarator++;
21
- for (const { name } of getVariableDeclarationIdentifiers(node.id)) {
22
- analysis.identifiersName.push({ name, type: "variableDeclarator" });
23
- }
24
- }
25
- }
26
-
27
- export default {
28
- name: "isVariableDeclaration",
29
- validateNode, main, breakOnMatch: false
30
- };
package/src/utils.js DELETED
@@ -1,48 +0,0 @@
1
- // Import Third-party Dependencies
2
- import {
3
- getCallExpressionIdentifier
4
- } from "@nodesecure/estree-ast-utils";
5
-
6
- export function notNullOrUndefined(value) {
7
- return value !== null && value !== void 0;
8
- }
9
-
10
- export function isUnsafeCallee(node) {
11
- const identifier = getCallExpressionIdentifier(node);
12
-
13
- // For Function we are looking for this: `Function("...")();`
14
- // A double CallExpression
15
- return [
16
- identifier === "eval" || (identifier === "Function" && node.callee.type === "CallExpression"),
17
- identifier
18
- ];
19
- }
20
-
21
- export function rootLocation() {
22
- return { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } };
23
- }
24
-
25
- export function toArrayLocation(location = rootLocation()) {
26
- const { start, end = start } = location;
27
-
28
- return [
29
- [start.line || 0, start.column || 0],
30
- [end.line || 0, end.column || 0]
31
- ];
32
- }
33
-
34
- export function extractNode(expectedType) {
35
- return (callback, nodes) => {
36
- const finalNodes = Array.isArray(nodes) ? nodes : [nodes];
37
-
38
- for (const node of finalNodes) {
39
- if (notNullOrUndefined(node) && node.type === expectedType) {
40
- callback(node);
41
- }
42
- }
43
- };
44
- }
45
-
46
- export function removeHTMLComment(str) {
47
- return str.replace(/<!--[\s\S]*?(?:-->)/g, "");
48
- }
@@ -1,34 +0,0 @@
1
- export {
2
- ASTDeps,
3
- SourceLocation,
4
- Dependency
5
- }
6
-
7
- interface SourceLocation {
8
- start: {
9
- line: number;
10
- column: number;
11
- };
12
- end: {
13
- line: number;
14
- column: number;
15
- }
16
- }
17
-
18
- interface Dependency {
19
- unsafe: boolean;
20
- inTry: boolean;
21
- location?: SourceLocation;
22
- }
23
-
24
- declare class ASTDeps {
25
- constructor();
26
- removeByName(name: string): void;
27
- add(depName: string): void;
28
- getDependenciesInTryStatement(): IterableIterator<string>;
29
- [Symbol.iterator]: IterableIterator<string>;
30
-
31
- public isInTryStmt: boolean;
32
- public dependencies: Record<string, Dependency>;
33
- public readonly size: number;
34
- }