@nodesecure/js-x-ray 8.2.0 → 9.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 (62) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +9 -208
  3. package/package.json +62 -63
  4. package/src/{AstAnalyser.js → AstAnalyser.ts} +283 -209
  5. package/src/{Deobfuscator.js → Deobfuscator.ts} +228 -195
  6. package/src/{EntryFilesAnalyser.js → EntryFilesAnalyser.ts} +206 -167
  7. package/src/JsSourceParser.ts +77 -0
  8. package/src/NodeCounter.ts +90 -0
  9. package/src/{ProbeRunner.js → ProbeRunner.ts} +167 -144
  10. package/src/SourceFile.ts +226 -0
  11. package/src/index.ts +5 -0
  12. package/src/obfuscators/freejsobfuscator.ts +17 -0
  13. package/src/obfuscators/{jjencode.js → jjencode.ts} +39 -27
  14. package/src/obfuscators/jsfuck.ts +19 -0
  15. package/src/obfuscators/{obfuscator-io.js → obfuscator-io.ts} +25 -13
  16. package/src/obfuscators/{trojan-source.js → trojan-source.ts} +3 -1
  17. package/src/probes/{isArrayExpression.js → isArrayExpression.ts} +12 -3
  18. package/src/probes/{isBinaryExpression.js → isBinaryExpression.ts} +74 -55
  19. package/src/probes/isESMExport.ts +50 -0
  20. package/src/probes/{isFetch.js → isFetch.ts} +28 -19
  21. package/src/probes/isImportDeclaration.ts +58 -0
  22. package/src/probes/{isLiteral.js → isLiteral.ts} +91 -70
  23. package/src/probes/isLiteralRegex.ts +42 -0
  24. package/src/probes/{isRegexObject.js → isRegexObject.ts} +71 -49
  25. package/src/probes/isRequire/RequireCallExpressionWalker.ts +142 -0
  26. package/src/probes/isRequire/{isRequire.js → isRequire.ts} +195 -148
  27. package/src/probes/isSerializeEnv.ts +65 -0
  28. package/src/probes/isSyncIO.ts +96 -0
  29. package/src/probes/isUnsafeCallee.ts +89 -0
  30. package/src/probes/isUnsafeCommand.ts +133 -0
  31. package/src/probes/isWeakCrypto.ts +69 -0
  32. package/src/types/estree.ts +35 -0
  33. package/src/utils/extractNode.ts +22 -0
  34. package/src/utils/index.ts +4 -0
  35. package/src/utils/{exportAssignmentHasRequireLeave.js → isOneLineExpressionExport.ts} +70 -40
  36. package/src/utils/notNullOrUndefined.ts +5 -0
  37. package/src/utils/toArrayLocation.ts +22 -0
  38. package/src/warnings.ts +146 -0
  39. package/index.d.ts +0 -46
  40. package/index.js +0 -4
  41. package/src/JsSourceParser.js +0 -57
  42. package/src/NodeCounter.js +0 -76
  43. package/src/SourceFile.js +0 -147
  44. package/src/obfuscators/freejsobfuscator.js +0 -9
  45. package/src/obfuscators/jsfuck.js +0 -11
  46. package/src/probes/isESMExport.js +0 -31
  47. package/src/probes/isImportDeclaration.js +0 -33
  48. package/src/probes/isLiteralRegex.js +0 -31
  49. package/src/probes/isRequire/RequireCallExpressionWalker.js +0 -93
  50. package/src/probes/isUnsafeCallee.js +0 -35
  51. package/src/probes/isWeakCrypto.js +0 -37
  52. package/src/utils/extractNode.js +0 -14
  53. package/src/utils/index.js +0 -8
  54. package/src/utils/isNode.js +0 -5
  55. package/src/utils/isOneLineExpressionExport.js +0 -24
  56. package/src/utils/isUnsafeCallee.js +0 -28
  57. package/src/utils/notNullOrUndefined.js +0 -3
  58. package/src/utils/rootLocation.js +0 -3
  59. package/src/utils/toArrayLocation.js +0 -11
  60. package/src/warnings.js +0 -76
  61. package/types/api.d.ts +0 -177
  62. package/types/warnings.d.ts +0 -36
@@ -1,195 +1,228 @@
1
- // Import Third-party Dependencies
2
- import { getVariableDeclarationIdentifiers } from "@nodesecure/estree-ast-utils";
3
- import { Utils, Patterns } from "@nodesecure/sec-literal";
4
- import { match } from "ts-pattern";
5
-
6
- // Import Internal Dependencies
7
- import { NodeCounter } from "./NodeCounter.js";
8
- import { extractNode } from "./utils/index.js";
9
-
10
- import * as jjencode from "./obfuscators/jjencode.js";
11
- import * as jsfuck from "./obfuscators/jsfuck.js";
12
- import * as freejsobfuscator from "./obfuscators/freejsobfuscator.js";
13
- import * as obfuscatorio from "./obfuscators/obfuscator-io.js";
14
-
15
- // CONSTANTS
16
- const kIdentifierNodeExtractor = extractNode("Identifier");
17
- const kDictionaryStrParts = [
18
- "abcdefghijklmnopqrstuvwxyz",
19
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
20
- "0123456789"
21
- ];
22
- const kMinimumIdsCount = 5;
23
-
24
- export class Deobfuscator {
25
- deepBinaryExpression = 0;
26
- encodedArrayValue = 0;
27
- hasDictionaryString = false;
28
- hasPrefixedIdentifiers = false;
29
-
30
- /** @type {Set<string>} */
31
- morseLiterals = new Set();
32
-
33
- /** @type {number[]} */
34
- literalScores = [];
35
-
36
- /** @type {({ name: string; type: string; })[]} */
37
- identifiers = [];
38
-
39
- #counters = [
40
- new NodeCounter("VariableDeclaration[kind]"),
41
- new NodeCounter("AssignmentExpression", {
42
- match: (node, nc) => this.#extractCounterIdentifiers(nc, node.left)
43
- }),
44
- new NodeCounter("FunctionDeclaration", {
45
- match: (node, nc) => this.#extractCounterIdentifiers(nc, node.id)
46
- }),
47
- new NodeCounter("MemberExpression[computed]"),
48
- new NodeCounter("Property", {
49
- filter: (node) => node.key.type === "Identifier",
50
- match: (node, nc) => this.#extractCounterIdentifiers(nc, node.key)
51
- }),
52
- new NodeCounter("UnaryExpression", {
53
- name: "DoubleUnaryExpression",
54
- filter: ({ argument }) => argument.type === "UnaryExpression" && argument.argument.type === "ArrayExpression"
55
- }),
56
- new NodeCounter("VariableDeclarator", {
57
- match: (node, nc) => this.#extractCounterIdentifiers(nc, node.id)
58
- })
59
- ];
60
-
61
- /**
62
- * @param {!NodeCounter} nc
63
- * @param {*} node
64
- */
65
- #extractCounterIdentifiers(nc, node) {
66
- if (node === null) {
67
- return;
68
- }
69
- const { type } = nc;
70
-
71
- switch (type) {
72
- case "VariableDeclarator":
73
- case "AssignmentExpression": {
74
- for (const { name } of getVariableDeclarationIdentifiers(node)) {
75
- this.identifiers.push({ name, type });
76
- }
77
- break;
78
- }
79
- case "Property":
80
- case "FunctionDeclaration":
81
- this.identifiers.push({ name: node.name, type });
82
- break;
83
- }
84
- }
85
-
86
- analyzeString(str) {
87
- const score = Utils.stringSuspicionScore(str);
88
- if (score !== 0) {
89
- this.literalScores.push(score);
90
- }
91
-
92
- if (!this.hasDictionaryString) {
93
- const isDictionaryStr = kDictionaryStrParts.every((word) => str.includes(word));
94
- if (isDictionaryStr) {
95
- this.hasDictionaryString = true;
96
- }
97
- }
98
-
99
- // Searching for morse string like "--.- --.--"
100
- if (Utils.isMorse(str)) {
101
- this.morseLiterals.add(str);
102
- }
103
- }
104
-
105
- walk(node) {
106
- const { type } = node;
107
-
108
- const isFunctionParams = node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
109
- const nodesToExtract = match(type)
110
- .with("ClassDeclaration", () => [node.id, node.superClass])
111
- .with("FunctionDeclaration", () => node.params)
112
- .with("FunctionExpression", () => node.params)
113
- .with("MethodDefinition", () => [node.key])
114
- .otherwise(() => []);
115
-
116
- kIdentifierNodeExtractor(
117
- ({ name }) => this.identifiers.push({ name, type: isFunctionParams ? "FunctionParams" : type }),
118
- nodesToExtract
119
- );
120
-
121
- this.#counters.forEach((counter) => counter.walk(node));
122
- }
123
-
124
- aggregateCounters() {
125
- const defaultValue = {
126
- Identifiers: this.identifiers.length
127
- };
128
-
129
- return this.#counters.reduce((result, counter) => {
130
- result[counter.name] = counter.lookup ?
131
- counter.properties :
132
- counter.count;
133
-
134
- return result;
135
- }, defaultValue);
136
- }
137
-
138
- #calcAvgPrefixedIdentifiers(
139
- counters,
140
- prefix
141
- ) {
142
- const valuesArr = Object
143
- .values(prefix)
144
- .slice()
145
- .sort((left, right) => left - right);
146
- if (valuesArr.length === 0) {
147
- return 0;
148
- }
149
-
150
- const nbOfPrefixedIds = valuesArr.length === 1 ?
151
- valuesArr.pop() :
152
- (valuesArr.pop() + valuesArr.pop());
153
- const maxIds = counters.Identifiers - counters.Property;
154
-
155
- return ((nbOfPrefixedIds / maxIds) * 100);
156
- }
157
-
158
- assertObfuscation() {
159
- const counters = this.aggregateCounters();
160
-
161
- if (jsfuck.verify(counters)) {
162
- return "jsfuck";
163
- }
164
- if (jjencode.verify(this.identifiers, counters)) {
165
- return "jjencode";
166
- }
167
- if (this.morseLiterals.size >= 36) {
168
- return "morse";
169
- }
170
-
171
- const { prefix } = Patterns.commonHexadecimalPrefix(
172
- this.identifiers.flatMap(
173
- ({ name }) => (typeof name === "string" ? [name] : [])
174
- )
175
- );
176
- const uPrefixNames = new Set(Object.keys(prefix));
177
-
178
- if (this.identifiers.length > kMinimumIdsCount && uPrefixNames.size > 0) {
179
- this.hasPrefixedIdentifiers = this.#calcAvgPrefixedIdentifiers(counters, prefix) > 80;
180
- }
181
-
182
- if (uPrefixNames.size === 1 && freejsobfuscator.verify(this.identifiers, prefix)) {
183
- return "freejsobfuscator";
184
- }
185
- if (obfuscatorio.verify(this, counters)) {
186
- return "obfuscator.io";
187
- }
188
- // if ((identifierLength > (kMinimumIdsCount * 3) && this.hasPrefixedIdentifiers)
189
- // && (oneTimeOccurence <= 3 || this.encodedArrayValue > 0)) {
190
- // return "unknown";
191
- // }
192
-
193
- return null;
194
- }
195
- }
1
+ // Import Third-party Dependencies
2
+ import { getVariableDeclarationIdentifiers } from "@nodesecure/estree-ast-utils";
3
+ import { Utils, Patterns } from "@nodesecure/sec-literal";
4
+ import { match } from "ts-pattern";
5
+ import type { ESTree } from "meriyah";
6
+
7
+ // Import Internal Dependencies
8
+ import { NodeCounter } from "./NodeCounter.js";
9
+ import { extractNode } from "./utils/index.js";
10
+
11
+ import * as jjencode from "./obfuscators/jjencode.js";
12
+ import * as jsfuck from "./obfuscators/jsfuck.js";
13
+ import * as freejsobfuscator from "./obfuscators/freejsobfuscator.js";
14
+ import * as obfuscatorio from "./obfuscators/obfuscator-io.js";
15
+
16
+ // CONSTANTS
17
+ const kIdentifierNodeExtractor = extractNode<ESTree.Identifier>("Identifier");
18
+ const kDictionaryStrParts = [
19
+ "abcdefghijklmnopqrstuvwxyz",
20
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
21
+ "0123456789"
22
+ ];
23
+ const kMinimumIdsCount = 5;
24
+
25
+ export type ObfuscatedEngine =
26
+ | "jsfuck"
27
+ | "jjencode"
28
+ | "morse"
29
+ | "freejsobfuscator"
30
+ | "obfuscator.io"
31
+ | "unknown";
32
+
33
+ export interface ObfuscatedIdentifier {
34
+ name: string;
35
+ type: string;
36
+ }
37
+
38
+ export interface ObfuscatedCounters {
39
+ Identifiers: number;
40
+ VariableDeclaration?: {
41
+ const?: number;
42
+ let?: number;
43
+ var?: number;
44
+ };
45
+ VariableDeclarator?: number;
46
+ AssignmentExpression?: number;
47
+ FunctionDeclaration?: number;
48
+ MemberExpression?: Record<string, number>;
49
+ Property?: number;
50
+ UnaryExpression?: number;
51
+ DoubleUnaryExpression?: number;
52
+ }
53
+
54
+ export class Deobfuscator {
55
+ deepBinaryExpression = 0;
56
+ encodedArrayValue = 0;
57
+ hasDictionaryString = false;
58
+ hasPrefixedIdentifiers = false;
59
+
60
+ morseLiterals = new Set<string>();
61
+ literalScores: number[] = [];
62
+
63
+ identifiers: ObfuscatedIdentifier[] = [];
64
+
65
+ #counters = [
66
+ new NodeCounter<ESTree.VariableDeclaration>("VariableDeclaration[kind]"),
67
+ new NodeCounter<ESTree.AssignmentExpression>("AssignmentExpression", {
68
+ match: (node, nc) => this.#extractCounterIdentifiers(nc, node.left)
69
+ }),
70
+ new NodeCounter<ESTree.FunctionDeclaration>("FunctionDeclaration", {
71
+ match: (node, nc) => this.#extractCounterIdentifiers(nc, node.id)
72
+ }),
73
+ new NodeCounter<ESTree.MemberExpression>("MemberExpression[computed]"),
74
+ new NodeCounter<ESTree.Property>("Property", {
75
+ filter: (node) => node.key.type === "Identifier",
76
+ match: (node, nc) => this.#extractCounterIdentifiers(nc, node.key)
77
+ }),
78
+ new NodeCounter<ESTree.UnaryExpression>("UnaryExpression", {
79
+ name: "DoubleUnaryExpression",
80
+ filter: ({ argument }) => argument.type === "UnaryExpression" && argument.argument.type === "ArrayExpression"
81
+ }),
82
+ new NodeCounter<ESTree.VariableDeclarator>("VariableDeclarator", {
83
+ match: (node, nc) => this.#extractCounterIdentifiers(nc, node.id)
84
+ })
85
+ ];
86
+
87
+ #extractCounterIdentifiers(
88
+ nc: NodeCounter<any>,
89
+ node: ESTree.Node | null
90
+ ) {
91
+ if (node === null) {
92
+ return;
93
+ }
94
+ const { type } = nc;
95
+
96
+ switch (type) {
97
+ case "VariableDeclarator":
98
+ case "AssignmentExpression": {
99
+ for (const { name } of getVariableDeclarationIdentifiers(node)) {
100
+ this.identifiers.push({ name, type });
101
+ }
102
+ break;
103
+ }
104
+ case "Property":
105
+ case "FunctionDeclaration":
106
+ if (node.type === "Identifier") {
107
+ this.identifiers.push({ name: node.name, type });
108
+ }
109
+ break;
110
+ }
111
+ }
112
+
113
+ analyzeString(
114
+ str: string
115
+ ): void {
116
+ const score = Utils.stringSuspicionScore(str);
117
+ if (score !== 0) {
118
+ this.literalScores.push(score);
119
+ }
120
+
121
+ if (!this.hasDictionaryString) {
122
+ const isDictionaryStr = kDictionaryStrParts.every((word) => str.includes(word));
123
+ if (isDictionaryStr) {
124
+ this.hasDictionaryString = true;
125
+ }
126
+ }
127
+
128
+ // Searching for morse string like "--.- --.--"
129
+ if (Utils.isMorse(str)) {
130
+ this.morseLiterals.add(str);
131
+ }
132
+ }
133
+
134
+ walk(
135
+ node: ESTree.Node
136
+ ): void {
137
+ const nodesToExtract = match(node)
138
+ .with({ type: "ClassDeclaration" }, (node) => [node.id, node.superClass])
139
+ .with({ type: "FunctionDeclaration" }, (node) => node.params)
140
+ .with({ type: "FunctionExpression" }, (node) => node.params)
141
+ .with({ type: "MethodDefinition" }, (node) => [node.key])
142
+ .otherwise(() => []);
143
+
144
+ const isFunctionParams =
145
+ node.type === "FunctionDeclaration" ||
146
+ node.type === "FunctionExpression";
147
+
148
+ kIdentifierNodeExtractor(
149
+ ({ name }) => this.identifiers.push({
150
+ name,
151
+ type: isFunctionParams ? "FunctionParams" : node.type
152
+ }),
153
+ nodesToExtract
154
+ );
155
+
156
+ this.#counters.forEach((counter) => counter.walk(node));
157
+ }
158
+
159
+ aggregateCounters(): ObfuscatedCounters {
160
+ return this.#counters.reduce((result, counter) => {
161
+ result[counter.name] = counter.lookup ?
162
+ counter.properties :
163
+ counter.count;
164
+
165
+ return result;
166
+ }, {
167
+ Identifiers: this.identifiers.length
168
+ });
169
+ }
170
+
171
+ #calcAvgPrefixedIdentifiers(
172
+ counters: ObfuscatedCounters,
173
+ prefix: Record<string, number>
174
+ ): number {
175
+ const valuesArr = Object
176
+ .values(prefix)
177
+ .slice()
178
+ .sort((left, right) => left - right);
179
+ if (valuesArr.length === 0) {
180
+ return 0;
181
+ }
182
+
183
+ const nbOfPrefixedIds = valuesArr.length === 1 ?
184
+ valuesArr.pop()! :
185
+ (valuesArr.pop()! + valuesArr.pop()!);
186
+ const maxIds = counters.Identifiers - (counters.Property ?? 0);
187
+
188
+ return ((nbOfPrefixedIds / maxIds) * 100);
189
+ }
190
+
191
+ assertObfuscation(): ObfuscatedEngine | null {
192
+ const counters = this.aggregateCounters();
193
+
194
+ if (jsfuck.verify(counters)) {
195
+ return "jsfuck";
196
+ }
197
+ if (jjencode.verify(this.identifiers, counters)) {
198
+ return "jjencode";
199
+ }
200
+ if (this.morseLiterals.size >= 36) {
201
+ return "morse";
202
+ }
203
+
204
+ const { prefix } = Patterns.commonHexadecimalPrefix(
205
+ this.identifiers.flatMap(
206
+ ({ name }) => (typeof name === "string" ? [name] : [])
207
+ )
208
+ );
209
+ const uPrefixNames = new Set(Object.keys(prefix));
210
+
211
+ if (this.identifiers.length > kMinimumIdsCount && uPrefixNames.size > 0) {
212
+ this.hasPrefixedIdentifiers = this.#calcAvgPrefixedIdentifiers(counters, prefix) > 80;
213
+ }
214
+
215
+ if (uPrefixNames.size === 1 && freejsobfuscator.verify(this.identifiers, prefix)) {
216
+ return "freejsobfuscator";
217
+ }
218
+ if (obfuscatorio.verify(this, counters)) {
219
+ return "obfuscator.io";
220
+ }
221
+ // if ((identifierLength > (kMinimumIdsCount * 3) && this.hasPrefixedIdentifiers)
222
+ // && (oneTimeOccurence <= 3 || this.encodedArrayValue > 0)) {
223
+ // return "unknown";
224
+ // }
225
+
226
+ return null;
227
+ }
228
+ }