@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.
- package/LICENSE +21 -21
- package/README.md +9 -208
- package/package.json +62 -63
- package/src/{AstAnalyser.js → AstAnalyser.ts} +283 -209
- package/src/{Deobfuscator.js → Deobfuscator.ts} +228 -195
- package/src/{EntryFilesAnalyser.js → EntryFilesAnalyser.ts} +206 -167
- package/src/JsSourceParser.ts +77 -0
- package/src/NodeCounter.ts +90 -0
- package/src/{ProbeRunner.js → ProbeRunner.ts} +167 -144
- package/src/SourceFile.ts +226 -0
- package/src/index.ts +5 -0
- package/src/obfuscators/freejsobfuscator.ts +17 -0
- package/src/obfuscators/{jjencode.js → jjencode.ts} +39 -27
- package/src/obfuscators/jsfuck.ts +19 -0
- package/src/obfuscators/{obfuscator-io.js → obfuscator-io.ts} +25 -13
- package/src/obfuscators/{trojan-source.js → trojan-source.ts} +3 -1
- package/src/probes/{isArrayExpression.js → isArrayExpression.ts} +12 -3
- package/src/probes/{isBinaryExpression.js → isBinaryExpression.ts} +74 -55
- package/src/probes/isESMExport.ts +50 -0
- package/src/probes/{isFetch.js → isFetch.ts} +28 -19
- package/src/probes/isImportDeclaration.ts +58 -0
- package/src/probes/{isLiteral.js → isLiteral.ts} +91 -70
- package/src/probes/isLiteralRegex.ts +42 -0
- package/src/probes/{isRegexObject.js → isRegexObject.ts} +71 -49
- package/src/probes/isRequire/RequireCallExpressionWalker.ts +142 -0
- package/src/probes/isRequire/{isRequire.js → isRequire.ts} +195 -148
- package/src/probes/isSerializeEnv.ts +65 -0
- package/src/probes/isSyncIO.ts +96 -0
- package/src/probes/isUnsafeCallee.ts +89 -0
- package/src/probes/isUnsafeCommand.ts +133 -0
- package/src/probes/isWeakCrypto.ts +69 -0
- package/src/types/estree.ts +35 -0
- package/src/utils/extractNode.ts +22 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/{exportAssignmentHasRequireLeave.js → isOneLineExpressionExport.ts} +70 -40
- package/src/utils/notNullOrUndefined.ts +5 -0
- package/src/utils/toArrayLocation.ts +22 -0
- package/src/warnings.ts +146 -0
- package/index.d.ts +0 -46
- package/index.js +0 -4
- package/src/JsSourceParser.js +0 -57
- package/src/NodeCounter.js +0 -76
- package/src/SourceFile.js +0 -147
- package/src/obfuscators/freejsobfuscator.js +0 -9
- package/src/obfuscators/jsfuck.js +0 -11
- package/src/probes/isESMExport.js +0 -31
- package/src/probes/isImportDeclaration.js +0 -33
- package/src/probes/isLiteralRegex.js +0 -31
- package/src/probes/isRequire/RequireCallExpressionWalker.js +0 -93
- package/src/probes/isUnsafeCallee.js +0 -35
- package/src/probes/isWeakCrypto.js +0 -37
- package/src/utils/extractNode.js +0 -14
- package/src/utils/index.js +0 -8
- package/src/utils/isNode.js +0 -5
- package/src/utils/isOneLineExpressionExport.js +0 -24
- package/src/utils/isUnsafeCallee.js +0 -28
- package/src/utils/notNullOrUndefined.js +0 -3
- package/src/utils/rootLocation.js +0 -3
- package/src/utils/toArrayLocation.js +0 -11
- package/src/warnings.js +0 -76
- package/types/api.d.ts +0 -177
- 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
|
-
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import * as
|
|
12
|
-
import * as
|
|
13
|
-
import * as
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
this
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
+
}
|