Repository navigation
Expand file tree
/
Copy pathAddDeadCode.py
More file actions
117 lines (96 loc) · 4.72 KB
/
Copy pathAddDeadCode.py
File metadata and controls
117 lines (96 loc) · 4.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
"""Stage 6: Inject never-executing 'code caves' guarded by always-false conditions."""
import argparse
import os
import random
import string
# Used when the deadcode directory is missing or empty, so the stage still works.
FALLBACK_SNIPPETS = [
"var _unused0 = function (x) { return x * x; };",
"var _unused1 = [1, 2, 3].map(function (n) { return n + 1; });",
"var _unused2 = { a: 1, b: 2, c: function () { return this.a + this.b; } };",
"var _unused3 = 'deadcode'.split('').reverse().join('');",
"var _unused4 = function f(n) { return n <= 1 ? 1 : n * f(n - 1); };",
]
def generate_false_js_conditions(count=5, rng=None):
rng = rng or random
false_conditions = set()
while len(false_conditions) < count:
option = rng.choice([
lambda: f"if ({rng.randint(1, 100)} > {rng.randint(101, 200)})",
lambda: f"if ({rng.randint(1, 10)} === {rng.randint(11, 20)})",
lambda: f"if ('{rng.choice(string.ascii_lowercase)}' === '{rng.choice(string.ascii_uppercase)}')",
# Always false in both browsers and Node (window/document never differ this way).
lambda: "if (typeof window !== 'undefined' && typeof document === 'undefined')",
lambda: f"if ({rng.random():.2f} && false)",
lambda: f"if (!true && {rng.randint(0, 1)})",
lambda: f"if ('{rng.choice(['true', 'false'])}' === true)",
lambda: f"if (Array.isArray({rng.randint(1, 10)}))",
lambda: f"if ({rng.randint(0, 10)} instanceof String)",
lambda: f"if (null === {rng.randint(0, 5)})",
])
false_conditions.add(option())
return list(false_conditions)
def get_all_js_files(directory='./deadcode'):
js_files = []
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith('.js'):
js_files.append(os.path.abspath(os.path.join(root, file)))
return js_files
def get_random_js(js_files=None, rng=None):
"""Return the contents of a random corpus file, or a fallback snippet."""
rng = rng or random
if js_files is None:
js_files = get_all_js_files()
if js_files:
file_selected = rng.choice(js_files)
with open(file_selected, "r", encoding='utf-8', errors='ignore') as f:
return f.read()
return rng.choice(FALLBACK_SNIPPETS)
def generate_code_cave(rng=None, js_files=None):
rng = rng or random
condition = rng.choice(generate_false_js_conditions(rng=rng))
body = get_random_js(js_files=js_files, rng=rng)
return f"{condition} {{\n {body}\n}}\n"
def inject_code_caves(js_code, num_caves=3, rng=None, js_files=None):
rng = rng or random
lines = js_code.splitlines()
if not lines:
return js_code
# choices() (not sample()) so num_caves is honored even when it exceeds the
# line count - several caves may then share an insertion point.
insertion_points = sorted(rng.choices(range(len(lines)), k=num_caves))
for i, point in enumerate(insertion_points):
cave = generate_code_cave(rng=rng, js_files=js_files)
lines.insert(point + i, cave)
return "\n".join(lines)
def main(argv=None):
parser = argparse.ArgumentParser(description="Add non-executing code caves to JavaScript code.")
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--file', '-f', type=str, help="Path to the JavaScript file to process.")
group.add_argument('--code', '-c', type=str, help="Raw JavaScript code to process.")
parser.add_argument('--output', '-o', type=str, help="Output file to write the result to (optional).")
parser.add_argument('--caves', '-n', type=int, default=3,
help="Number of code caves to insert (default: 3).")
parser.add_argument('--deadcode-dir', type=str, default='./deadcode',
help="Directory with .js files to mine for cave bodies (default: ./deadcode).")
parser.add_argument('--seed', type=int, default=None, help="Random seed for reproducible output.")
args = parser.parse_args(argv)
rng = random.Random(args.seed)
js_files = get_all_js_files(args.deadcode_dir)
if not js_files:
print(f"[!] No .js files found in {args.deadcode_dir} - using built-in fallback snippets.")
if args.file:
with open(args.file, 'r', encoding='utf-8') as f:
js_code = f.read()
else:
js_code = args.code
modified_code = inject_code_caves(js_code, num_caves=args.caves, rng=rng, js_files=js_files)
if args.output:
with open(args.output, 'w', encoding='utf-8') as f:
f.write(modified_code)
print(f"Modified code written to {args.output}")
else:
print(modified_code)
if __name__ == '__main__':
main()