simple-javascript-obf 0.1.1

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 (43) hide show
  1. package/.github/workflows/node.js.yml +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +92 -0
  4. package/THIRD_PARTY_NOTICES.md +82 -0
  5. package/bin/js-obf +228 -0
  6. package/bin/obf.sh +257 -0
  7. package/package.json +26 -0
  8. package/src/index.js +106 -0
  9. package/src/options.js +128 -0
  10. package/src/pipeline.js +56 -0
  11. package/src/plugins/antiHook.js +123 -0
  12. package/src/plugins/controlFlowFlatten.js +203 -0
  13. package/src/plugins/deadCode.js +82 -0
  14. package/src/plugins/encodeMembers.js +44 -0
  15. package/src/plugins/entry.js +31 -0
  16. package/src/plugins/rename.js +100 -0
  17. package/src/plugins/stringEncode.js +494 -0
  18. package/src/plugins/vm/ast-utils.js +58 -0
  19. package/src/plugins/vm/compiler.js +113 -0
  20. package/src/plugins/vm/constants.js +72 -0
  21. package/src/plugins/vm/emit.js +916 -0
  22. package/src/plugins/vm/encoding.js +252 -0
  23. package/src/plugins/vm/index.js +366 -0
  24. package/src/plugins/vm/mapping.js +24 -0
  25. package/src/plugins/vm/normalize.js +692 -0
  26. package/src/plugins/vm/runtime.js +1145 -0
  27. package/src/plugins/vm.js +1 -0
  28. package/src/utils/names.js +55 -0
  29. package/src/utils/reserved.js +57 -0
  30. package/src/utils/rng.js +55 -0
  31. package/src/utils/stream.js +97 -0
  32. package/src/utils/string.js +13 -0
  33. package/test/bench-runner.js +78 -0
  34. package/test/benchmark-source.js +35 -0
  35. package/test/benchmark-vm.js +160 -0
  36. package/test/dist/bench.obf.js +1 -0
  37. package/test/dist/bench.original.js +35 -0
  38. package/test/dist/bench.vm.js +1 -0
  39. package/test/dist/sample-input.obf.js +1 -0
  40. package/test/dist/sample-input.vm.js +1 -0
  41. package/test/generate-obf.js +38 -0
  42. package/test/obf-smoke.js +129 -0
  43. package/test/sample-input.js +23 -0
package/bin/obf.sh ADDED
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ prompt() {
5
+ local text="$1"
6
+ local value=""
7
+ read -r -p "$text" value
8
+ printf "%s" "$value"
9
+ }
10
+
11
+ prompt_yes_no() {
12
+ local text="$1"
13
+ local value=""
14
+ read -r -p "$text" value
15
+ case "${value}" in
16
+ y|Y|yes|YES) return 0 ;;
17
+ *) return 1 ;;
18
+ esac
19
+ }
20
+
21
+ now_ms() {
22
+ date +%s%3N
23
+ }
24
+
25
+ format_duration() {
26
+ local ms="$1"
27
+ awk -v ms="${ms}" 'BEGIN { printf "%.3f", ms / 1000 }'
28
+ }
29
+
30
+ root_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
31
+
32
+ input_path=$(prompt "Enter a JS file or directory path: ")
33
+ if [[ -z "${input_path}" ]]; then
34
+ echo "No input path provided." >&2
35
+ exit 1
36
+ fi
37
+
38
+ input_kind=""
39
+ if [[ -f "${input_path}" ]]; then
40
+ input_kind="file"
41
+ elif [[ -d "${input_path}" ]]; then
42
+ input_kind="dir"
43
+ else
44
+ echo "Input path not found: ${input_path}" >&2
45
+ exit 1
46
+ fi
47
+
48
+ abs_input=$(readlink -f "${input_path}")
49
+ if [[ "${input_kind}" == "dir" ]]; then
50
+ input_dir="${abs_input}"
51
+ else
52
+ input_dir=$(dirname "${abs_input}")
53
+ fi
54
+
55
+ echo "Input directory: ${input_dir}"
56
+ if [[ "${input_kind}" == "file" ]]; then
57
+ output_dir=$(prompt "Output directory (default: ${input_dir}, use . for current dir): ")
58
+ if [[ -z "${output_dir}" ]]; then
59
+ output_dir="${input_dir}"
60
+ fi
61
+ output_dir=$(readlink -f "${output_dir}")
62
+ mkdir -p "${output_dir}"
63
+ else
64
+ output_dir="${input_dir}"
65
+ if ! prompt_yes_no "This will overwrite all JS files in the directory. Continue? [y/N]: "; then
66
+ echo "Cancelled." >&2
67
+ exit 1
68
+ fi
69
+ fi
70
+ echo "Output directory: ${output_dir}"
71
+
72
+ max_jobs=1
73
+ if [[ "${input_kind}" == "dir" ]]; then
74
+ max_jobs=$(prompt "Concurrency (default 1): ")
75
+ if [[ -z "${max_jobs}" ]]; then
76
+ max_jobs=1
77
+ fi
78
+ if ! [[ "${max_jobs}" =~ ^[0-9]+$ ]] || [[ "${max_jobs}" -lt 1 ]]; then
79
+ echo "Concurrency must be a positive integer." >&2
80
+ exit 1
81
+ fi
82
+ fi
83
+
84
+ preset=$(prompt "Preset strength (high/balanced/low, default high): ")
85
+ if [[ -z "${preset}" ]]; then
86
+ preset="high"
87
+ fi
88
+
89
+ enable_rename=true
90
+ enable_strings=true
91
+ enable_cff=true
92
+ enable_dead=true
93
+ enable_vm=false
94
+ enable_anti_hook=false
95
+ enable_anti_hook_lock=false
96
+
97
+ if ! prompt_yes_no "Enable variable renaming? [y/N]: "; then
98
+ enable_rename=false
99
+ fi
100
+ if ! prompt_yes_no "Enable string encryption? [y/N]: "; then
101
+ enable_strings=false
102
+ fi
103
+ if ! prompt_yes_no "Enable control-flow flattening? [y/N]: "; then
104
+ enable_cff=false
105
+ fi
106
+ if ! prompt_yes_no "Enable dead-code injection? [y/N]: "; then
107
+ enable_dead=false
108
+ fi
109
+ if prompt_yes_no "Enable VM virtualization? [y/N]: "; then
110
+ enable_vm=true
111
+ fi
112
+ if prompt_yes_no "Enable anti-hook runtime guard? [y/N]: "; then
113
+ enable_anti_hook=true
114
+ if prompt_yes_no "Freeze built-in prototype chains (anti-hook-lock)? [y/N]: "; then
115
+ enable_anti_hook_lock=true
116
+ fi
117
+ fi
118
+
119
+ vm_include=""
120
+ if ${enable_vm}; then
121
+ vm_include=$(prompt "Only virtualize function names (comma-separated, optional): ")
122
+ fi
123
+
124
+ seed=$(prompt "PRNG seed (optional): ")
125
+ enable_sourcemap=false
126
+ enable_compact=false
127
+ if prompt_yes_no "Generate source map? [y/N]: "; then
128
+ enable_sourcemap=true
129
+ fi
130
+ if prompt_yes_no "Use compact output? [y/N]: "; then
131
+ enable_compact=true
132
+ fi
133
+
134
+ common_args=(--preset "${preset}")
135
+
136
+ if ! ${enable_rename}; then
137
+ common_args+=(--no-rename)
138
+ fi
139
+ if ! ${enable_strings}; then
140
+ common_args+=(--no-strings)
141
+ fi
142
+ if ! ${enable_cff}; then
143
+ common_args+=(--no-cff)
144
+ fi
145
+ if ! ${enable_dead}; then
146
+ common_args+=(--no-dead)
147
+ fi
148
+ if ${enable_vm}; then
149
+ common_args+=(--vm)
150
+ if [[ -n "${vm_include}" ]]; then
151
+ common_args+=(--vm-include "${vm_include}")
152
+ fi
153
+ fi
154
+ if ${enable_anti_hook_lock}; then
155
+ common_args+=(--anti-hook-lock)
156
+ elif ${enable_anti_hook}; then
157
+ common_args+=(--anti-hook)
158
+ fi
159
+ if [[ -n "${seed}" ]]; then
160
+ common_args+=(--seed "${seed}")
161
+ fi
162
+ if ${enable_sourcemap}; then
163
+ common_args+=(--sourcemap)
164
+ fi
165
+ if ${enable_compact}; then
166
+ common_args+=(--compact)
167
+ fi
168
+
169
+ if [[ "${input_kind}" == "file" ]]; then
170
+ base_name=$(basename "${abs_input}")
171
+ base_no_ext="${base_name%.*}"
172
+ output_path="${output_dir}/${base_no_ext}.obf.js"
173
+ cmd=(node "${root_dir}/bin/js-obf" "${abs_input}" -o "${output_path}" "${common_args[@]}")
174
+ total_start_ms=$(now_ms)
175
+ start_ms=$(now_ms)
176
+ echo "Run: ${cmd[*]}"
177
+ "${cmd[@]}"
178
+ end_ms=$(now_ms)
179
+ duration_ms=$((end_ms - start_ms))
180
+ duration_sec=$(format_duration "${duration_ms}")
181
+ echo "Done: ${output_path} (${duration_sec}s)"
182
+ total_end_ms=$(now_ms)
183
+ total_ms=$((total_end_ms - total_start_ms))
184
+ total_sec=$(format_duration "${total_ms}")
185
+ echo "Total time: ${total_sec}s"
186
+ else
187
+ prune_paths=(
188
+ -type d -name node_modules
189
+ -o -type d -name node_packge
190
+ -o -type d -name node_package
191
+ )
192
+ if [[ "${output_dir}" != "${input_dir}" && "${output_dir}" == "${input_dir}"/* ]]; then
193
+ prune_paths+=(-o -path "${output_dir}")
194
+ fi
195
+
196
+ files=()
197
+ while IFS= read -r -d '' file; do
198
+ files+=("${file}")
199
+ done < <(
200
+ find "${input_dir}" \( "${prune_paths[@]}" \) -prune -o \
201
+ -type f -name "*.js" ! -name "*.obf.js" -print0
202
+ )
203
+
204
+ if [[ ${#files[@]} -eq 0 ]]; then
205
+ echo "No JS files found to obfuscate." >&2
206
+ exit 1
207
+ fi
208
+
209
+ total_start_ms=$(now_ms)
210
+ echo "Found ${#files[@]} JS files. Starting obfuscation..."
211
+ failed=false
212
+ run_file() {
213
+ local file="$1"
214
+ local rel_path="${file#${input_dir}/}"
215
+ local output_path="${output_dir}/${rel_path}"
216
+ mkdir -p "$(dirname "${output_path}")"
217
+ local cmd=(node "${root_dir}/bin/js-obf" "${file}" -o "${output_path}" "${common_args[@]}")
218
+ local start_ms
219
+ local end_ms
220
+ start_ms=$(now_ms)
221
+ echo "Run: ${cmd[*]}"
222
+ "${cmd[@]}"
223
+ end_ms=$(now_ms)
224
+ local duration_ms=$((end_ms - start_ms))
225
+ local duration_sec
226
+ duration_sec=$(format_duration "${duration_ms}")
227
+ echo "Done: ${output_path} (${duration_sec}s)"
228
+ }
229
+
230
+ if [[ "${max_jobs}" -le 1 ]]; then
231
+ for file in "${files[@]}"; do
232
+ run_file "${file}"
233
+ done
234
+ else
235
+ for file in "${files[@]}"; do
236
+ run_file "${file}" &
237
+ while (( $(jobs -pr | wc -l) >= max_jobs )); do
238
+ if ! wait -n; then
239
+ failed=true
240
+ fi
241
+ done
242
+ done
243
+ while (( $(jobs -pr | wc -l) > 0 )); do
244
+ if ! wait -n; then
245
+ failed=true
246
+ fi
247
+ done
248
+ if ${failed}; then
249
+ echo "Some files failed to obfuscate. Check the logs." >&2
250
+ exit 1
251
+ fi
252
+ fi
253
+ total_end_ms=$(now_ms)
254
+ total_ms=$((total_end_ms - total_start_ms))
255
+ total_sec=$(format_duration "${total_ms}")
256
+ echo "All done: ${#files[@]} files in ${total_sec}s"
257
+ fi
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "simple-javascript-obf",
3
+ "version": "0.1.1",
4
+ "description": "Modular JavaScript obfuscator with optional VM and CFF",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "js-obf": "bin/js-obf"
8
+ },
9
+ "scripts": {
10
+ "obf": "node bin/js-obf"
11
+ },
12
+ "keywords": [
13
+ "javascript",
14
+ "obfuscator",
15
+ "vm",
16
+ "control-flow-flattening"
17
+ ],
18
+ "license": "MIT",
19
+ "dependencies": {
20
+ "@babel/generator": "^7.26.0",
21
+ "@babel/parser": "^7.26.0",
22
+ "@babel/traverse": "^7.26.0",
23
+ "@babel/types": "^7.26.0",
24
+ "terser": "^5.44.1"
25
+ }
26
+ }
package/src/index.js ADDED
@@ -0,0 +1,106 @@
1
+ const parser = require("@babel/parser");
2
+ const generate = require("@babel/generator").default;
3
+ const traverse = require("@babel/traverse").default;
4
+ const t = require("@babel/types");
5
+ const { minify } = require("terser");
6
+ const { buildPipeline } = require("./pipeline");
7
+ const { normalizeOptions } = require("./options");
8
+
9
+ function parseSource(code, filename) {
10
+ return parser.parse(code, {
11
+ sourceType: "unambiguous",
12
+ sourceFilename: filename,
13
+ allowReturnOutsideFunction: true,
14
+ plugins: [
15
+ "jsx",
16
+ "typescript",
17
+ "classProperties",
18
+ "classPrivateProperties",
19
+ "classPrivateMethods",
20
+ "decorators-legacy",
21
+ "dynamicImport",
22
+ "objectRestSpread",
23
+ "optionalChaining",
24
+ "nullishCoalescingOperator",
25
+ "numericSeparator",
26
+ "logicalAssignment",
27
+ "topLevelAwait",
28
+ "exportDefaultFrom",
29
+ "exportNamespaceFrom",
30
+ "asyncGenerators",
31
+ "bigInt",
32
+ "optionalCatchBinding",
33
+ "privateIn",
34
+ "importAssertions"
35
+ ],
36
+ });
37
+ }
38
+
39
+ async function obfuscate(source, userOptions = {}) {
40
+ const options = normalizeOptions(userOptions);
41
+ const ast = parseSource(source, options.filename);
42
+
43
+ const pipeline = buildPipeline({
44
+ t,
45
+ traverse,
46
+ options,
47
+ });
48
+
49
+ for (const plugin of pipeline) {
50
+ plugin(ast);
51
+ }
52
+
53
+ const output = generate(
54
+ ast,
55
+ {
56
+ compact: options.compact,
57
+ sourceMaps: options.sourceMap,
58
+ sourceFileName: options.filename,
59
+ retainLines: false,
60
+ comments: false,
61
+ },
62
+ source
63
+ );
64
+
65
+ const ecma = Number.isFinite(options.ecma) ? options.ecma : 2015;
66
+ const minifyOptions = {
67
+ ecma,
68
+ compress: {
69
+ defaults: true,
70
+ ecma,
71
+ },
72
+ mangle: true,
73
+ format: { comments: false, ecma },
74
+ };
75
+ if (options.sourceMap) {
76
+ minifyOptions.sourceMap = { content: output.map, asObject: true };
77
+ }
78
+
79
+ const minified = await minify(output.code, minifyOptions);
80
+ if (minified && minified.error) {
81
+ throw minified.error;
82
+ }
83
+
84
+ let map = null;
85
+ if (options.sourceMap) {
86
+ if (typeof minified.map === "string") {
87
+ try {
88
+ map = JSON.parse(minified.map);
89
+ } catch {
90
+ map = null;
91
+ }
92
+ } else {
93
+ map = minified.map || null;
94
+ }
95
+ }
96
+
97
+ return {
98
+ code: minified.code || "",
99
+ map,
100
+ };
101
+ }
102
+
103
+ module.exports = {
104
+ obfuscate,
105
+ parseSource,
106
+ };
package/src/options.js ADDED
@@ -0,0 +1,128 @@
1
+ const { DEFAULT_RESERVED } = require("./utils/reserved");
2
+
3
+ const DEFAULT_ECMA = 2015;
4
+
5
+ function normalizeEcma(value) {
6
+ if (value === undefined || value === null || value === "") {
7
+ return DEFAULT_ECMA;
8
+ }
9
+ if (typeof value === "number" && Number.isFinite(value)) {
10
+ return value;
11
+ }
12
+ const text = String(value).trim().toLowerCase();
13
+ if (!text) {
14
+ return DEFAULT_ECMA;
15
+ }
16
+ const normalized = text.startsWith("es") ? text.slice(2) : text;
17
+ const parsed = Number(normalized);
18
+ if (Number.isFinite(parsed)) {
19
+ return parsed;
20
+ }
21
+ return DEFAULT_ECMA;
22
+ }
23
+
24
+ const PRESETS = {
25
+ high: {
26
+ rename: true,
27
+ strings: true,
28
+ cff: true,
29
+ dead: true,
30
+ vm: false,
31
+ },
32
+ balanced: {
33
+ rename: true,
34
+ strings: true,
35
+ cff: true,
36
+ dead: false,
37
+ vm: false,
38
+ },
39
+ low: {
40
+ rename: true,
41
+ strings: false,
42
+ cff: false,
43
+ dead: false,
44
+ vm: false,
45
+ },
46
+ };
47
+
48
+ function normalizeOptions(userOptions = {}) {
49
+ const presetName = userOptions.preset || "high";
50
+ const preset = PRESETS[presetName] || PRESETS.high;
51
+ const stringsUserOptions = userOptions.stringsOptions || {};
52
+ const antiHookUserOptions = userOptions.antiHook;
53
+ const ecma = normalizeEcma(userOptions.ecma);
54
+
55
+ const options = {
56
+ preset: presetName,
57
+ rename: userOptions.rename ?? preset.rename,
58
+ strings: userOptions.strings ?? preset.strings,
59
+ cff: userOptions.cff ?? preset.cff,
60
+ dead: userOptions.dead ?? preset.dead,
61
+ vm: userOptions.vm || { enabled: preset.vm },
62
+ seed: userOptions.seed || "js-obf",
63
+ filename: userOptions.filename || "input.js",
64
+ sourceMap: Boolean(userOptions.sourceMap),
65
+ compact: Boolean(userOptions.compact),
66
+ ecma,
67
+ stringsOptions: {
68
+ minLength: stringsUserOptions.minLength ?? 3,
69
+ maxCount: stringsUserOptions.maxCount ?? 5000,
70
+ encodeConsole: stringsUserOptions.encodeConsole !== false,
71
+ },
72
+ renameOptions: {
73
+ reserved: userOptions.reserved || DEFAULT_RESERVED,
74
+ renameGlobals: userOptions.renameGlobals ?? false,
75
+ },
76
+ deadCodeOptions: {
77
+ probability: 0.15,
78
+ },
79
+ cffOptions: {
80
+ minStatements: 3,
81
+ },
82
+ antiHook: {
83
+ enabled: false,
84
+ lock: false,
85
+ },
86
+ };
87
+
88
+ if (typeof antiHookUserOptions === "boolean") {
89
+ options.antiHook.enabled = antiHookUserOptions;
90
+ } else if (antiHookUserOptions && typeof antiHookUserOptions === "object") {
91
+ options.antiHook.enabled = antiHookUserOptions.enabled !== false;
92
+ options.antiHook.lock = Boolean(antiHookUserOptions.lock);
93
+ }
94
+
95
+ if (typeof options.vm === "boolean") {
96
+ options.vm = { enabled: options.vm };
97
+ }
98
+ const vmOptions = options.vm || {};
99
+ let fakeOpcodes = vmOptions.fakeOpcodes;
100
+ if (fakeOpcodes === undefined || fakeOpcodes === true) {
101
+ fakeOpcodes = 0.15;
102
+ } else if (fakeOpcodes === false) {
103
+ fakeOpcodes = 0;
104
+ } else {
105
+ fakeOpcodes = Number(fakeOpcodes);
106
+ if (Number.isNaN(fakeOpcodes)) {
107
+ fakeOpcodes = 0.15;
108
+ }
109
+ }
110
+ fakeOpcodes = Math.max(0, Math.min(1, fakeOpcodes));
111
+ options.vm = {
112
+ enabled: Boolean(vmOptions.enabled),
113
+ include: vmOptions.include || [],
114
+ all: Boolean(vmOptions.all),
115
+ opcodeShuffle: vmOptions.opcodeShuffle !== false,
116
+ fakeOpcodes,
117
+ bytecodeEncrypt: vmOptions.bytecodeEncrypt !== false,
118
+ constsEncrypt: vmOptions.constsEncrypt !== false,
119
+ downlevel: Boolean(vmOptions.downlevel),
120
+ };
121
+
122
+ return options;
123
+ }
124
+
125
+ module.exports = {
126
+ normalizeOptions,
127
+ PRESETS,
128
+ };
@@ -0,0 +1,56 @@
1
+ const { RNG } = require("./utils/rng");
2
+ const { NameGenerator } = require("./utils/names");
3
+ const entryPlugin = require("./plugins/entry");
4
+ const renamePlugin = require("./plugins/rename");
5
+ const stringPlugin = require("./plugins/stringEncode");
6
+ const memberEncodePlugin = require("./plugins/encodeMembers");
7
+ const cffPlugin = require("./plugins/controlFlowFlatten");
8
+ const deadPlugin = require("./plugins/deadCode");
9
+ const antiHookPlugin = require("./plugins/antiHook");
10
+ const vmPlugin = require("./plugins/vm");
11
+
12
+ function buildPipeline({ t, traverse, options }) {
13
+ const rng = new RNG(options.seed);
14
+ const nameGen = new NameGenerator({
15
+ reserved: options.renameOptions.reserved,
16
+ rng,
17
+ });
18
+
19
+ const ctx = {
20
+ t,
21
+ traverse,
22
+ options,
23
+ rng,
24
+ nameGen,
25
+ state: {},
26
+ };
27
+
28
+ const plugins = [];
29
+
30
+ plugins.push((ast) => entryPlugin(ast, ctx));
31
+ if (options.vm.enabled) {
32
+ plugins.push((ast) => vmPlugin(ast, ctx));
33
+ }
34
+ if (options.cff) {
35
+ plugins.push((ast) => cffPlugin(ast, ctx));
36
+ }
37
+ if (options.strings) {
38
+ plugins.push((ast) => memberEncodePlugin(ast, ctx));
39
+ plugins.push((ast) => stringPlugin(ast, ctx));
40
+ }
41
+ if (options.dead) {
42
+ plugins.push((ast) => deadPlugin(ast, ctx));
43
+ }
44
+ if (options.antiHook && options.antiHook.enabled) {
45
+ plugins.push((ast) => antiHookPlugin(ast, ctx));
46
+ }
47
+ if (options.rename) {
48
+ plugins.push((ast) => renamePlugin(ast, ctx));
49
+ }
50
+
51
+ return plugins;
52
+ }
53
+
54
+ module.exports = {
55
+ buildPipeline,
56
+ };
@@ -0,0 +1,123 @@
1
+ const parser = require("@babel/parser");
2
+
3
+ function insertAtTop(programPath, nodes) {
4
+ const body = programPath.node.body;
5
+ let index = 0;
6
+ while (index < body.length) {
7
+ const stmt = body[index];
8
+ if (stmt.type === "ExpressionStatement" && stmt.directive) {
9
+ index += 1;
10
+ continue;
11
+ }
12
+ break;
13
+ }
14
+ body.splice(index, 0, ...nodes);
15
+ }
16
+
17
+ function buildRuntime({ lock }) {
18
+ const code = `
19
+ (function () {
20
+ const g = typeof globalThis !== "undefined"
21
+ ? globalThis
22
+ : typeof window !== "undefined"
23
+ ? window
24
+ : typeof global !== "undefined"
25
+ ? global
26
+ : this;
27
+ const nativeMark = "[native code]";
28
+ const fnToString =
29
+ g.Function && g.Function.prototype && g.Function.prototype.toString;
30
+ const isNative = (fn) => {
31
+ if (typeof fn !== "function" || !fnToString) {
32
+ return false;
33
+ }
34
+ try {
35
+ return fnToString.call(fn).indexOf(nativeMark) !== -1;
36
+ } catch (err) {
37
+ return false;
38
+ }
39
+ };
40
+ const checkAll = () => {
41
+ const required = [
42
+ g.Function,
43
+ g.eval,
44
+ g.Object && g.Object.defineProperty,
45
+ g.Object && g.Object.getOwnPropertyDescriptor,
46
+ g.Function && g.Function.prototype && g.Function.prototype.toString,
47
+ ];
48
+ for (let i = 0; i < required.length; i += 1) {
49
+ const fn = required[i];
50
+ if (!isNative(fn)) {
51
+ return false;
52
+ }
53
+ }
54
+ const optional = [
55
+ g.Object && g.Object.getPrototypeOf,
56
+ g.Object && g.Object.keys,
57
+ g.Object && g.Object.freeze,
58
+ g.JSON && g.JSON.parse,
59
+ g.JSON && g.JSON.stringify,
60
+ g.Math && g.Math.random,
61
+ g.Date,
62
+ g.Date && g.Date.now,
63
+ g.Reflect && g.Reflect.apply,
64
+ g.Reflect && g.Reflect.construct,
65
+ ];
66
+ for (let i = 0; i < optional.length; i += 1) {
67
+ const fn = optional[i];
68
+ if (typeof fn !== "function") {
69
+ continue;
70
+ }
71
+ if (!isNative(fn)) {
72
+ return false;
73
+ }
74
+ }
75
+ return true;
76
+ };
77
+ if (!checkAll()) {
78
+ throw new Error("Hook detected");
79
+ }
80
+ if (${lock ? "true" : "false"}) {
81
+ const freeze = g.Object && g.Object.freeze;
82
+ if (typeof freeze === "function") {
83
+ const lockProto = (obj) => {
84
+ try {
85
+ if (obj) {
86
+ freeze(obj);
87
+ }
88
+ } catch (err) {}
89
+ };
90
+ lockProto(g.Function && g.Function.prototype);
91
+ lockProto(g.Object && g.Object.prototype);
92
+ lockProto(g.Array && g.Array.prototype);
93
+ lockProto(g.String && g.String.prototype);
94
+ lockProto(g.Number && g.Number.prototype);
95
+ lockProto(g.Boolean && g.Boolean.prototype);
96
+ lockProto(g.Date && g.Date.prototype);
97
+ lockProto(g.RegExp && g.RegExp.prototype);
98
+ lockProto(g.Error && g.Error.prototype);
99
+ }
100
+ }
101
+ })();
102
+ `;
103
+ return parser.parse(code, { sourceType: "script" }).program.body;
104
+ }
105
+
106
+ function antiHook(ast, ctx) {
107
+ if (!ctx.options.antiHook || !ctx.options.antiHook.enabled) {
108
+ return;
109
+ }
110
+ let programPathRef = null;
111
+ ctx.traverse(ast, {
112
+ Program(path) {
113
+ programPathRef = path;
114
+ },
115
+ });
116
+ if (!programPathRef) {
117
+ return;
118
+ }
119
+ const runtime = buildRuntime({ lock: ctx.options.antiHook.lock });
120
+ insertAtTop(programPathRef, runtime);
121
+ }
122
+
123
+ module.exports = antiHook;