mirror of
https://github.com/bikini/patchwork
synced 2026-06-27 08:08:41 +00:00
125 lines
3.9 KiB
Python
125 lines
3.9 KiB
Python
from __future__ import annotations
|
|
|
|
import random
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
import tempfile
|
|
import textwrap
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from patchwork import Obfuscator
|
|
|
|
|
|
GENERATED_SEEDS = [3, 11, 29, 101, 1009]
|
|
OBFUSCATION_SEEDS = [5, 17, 257]
|
|
|
|
|
|
def run(path: Path) -> str:
|
|
result = subprocess.run([sys.executable, str(path)], capture_output=True, text=True, timeout=20)
|
|
if result.returncode != 0:
|
|
raise RuntimeError(f"{path} exited {result.returncode}\nstderr:\n{result.stderr}")
|
|
return result.stdout
|
|
|
|
|
|
def generated_program(seed: int) -> str:
|
|
rng = random.Random(seed)
|
|
numbers = [rng.randint(-5000, 5000) for _ in range(16)]
|
|
strings = [f"s{idx}_{rng.randrange(1 << 20):x}" for idx in range(6)]
|
|
pairs = [(rng.randint(-20, 20), rng.randint(-20, 20)) for _ in range(8)]
|
|
return textwrap.dedent(
|
|
f"""
|
|
from __future__ import annotations
|
|
|
|
NUMBERS = {numbers!r}
|
|
STRINGS = {strings!r}
|
|
PAIRS = {pairs!r}
|
|
|
|
def fold(values):
|
|
acc = 0
|
|
for idx, value in enumerate(values):
|
|
if idx % 3 == 0:
|
|
acc ^= value
|
|
elif idx % 3 == 1:
|
|
acc += value << (idx % 5)
|
|
else:
|
|
acc -= value >> (idx % 4)
|
|
return acc
|
|
|
|
def classify(item):
|
|
match item:
|
|
case (0, y):
|
|
return f"zero:{{y}}"
|
|
case (x, y) if x == y:
|
|
return f"same:{{x}}"
|
|
case (x, y):
|
|
return f"pair:{{x + y}}"
|
|
case _:
|
|
return "other"
|
|
|
|
def closure(base):
|
|
state = base
|
|
def inner(delta):
|
|
nonlocal state
|
|
state = (state + delta) ^ (delta << 2)
|
|
return state
|
|
return inner
|
|
|
|
def run():
|
|
fn = closure(7)
|
|
print("fold", fold(NUMBERS))
|
|
print("strings", "|".join(s[::-1] for s in STRINGS))
|
|
print("pairs", [classify(pair) for pair in PAIRS])
|
|
print("closure", [fn(x) for x in range(6)])
|
|
print("bytes", bytes(range(8)).hex())
|
|
|
|
if __name__ == "__main__":
|
|
run()
|
|
"""
|
|
).lstrip()
|
|
|
|
|
|
def check_source(source: str, *, label: str, seed: int, **kwargs) -> None:
|
|
obf_source = Obfuscator(seed=seed, **kwargs).obfuscate(source)
|
|
with tempfile.TemporaryDirectory() as raw:
|
|
directory = Path(raw)
|
|
src_path = directory / f"{label}.py"
|
|
obf_path = directory / f"{label}_obf.py"
|
|
src_path.write_text(source, encoding="utf-8")
|
|
obf_path.write_text(obf_source, encoding="utf-8")
|
|
expected = run(src_path)
|
|
actual = run(obf_path)
|
|
if actual != expected:
|
|
raise AssertionError(f"{label} seed={seed} mismatch\n--- expected ---\n{expected}--- actual ---\n{actual}")
|
|
|
|
|
|
def main() -> int:
|
|
failures = 0
|
|
matrix = [
|
|
{},
|
|
{"lazy_funcs": False},
|
|
{"anti_debug": False},
|
|
{"rename": False},
|
|
{"encrypt_strings": False},
|
|
{"opaque_predicates": False, "junk_branches": False},
|
|
]
|
|
for generated_seed in GENERATED_SEEDS:
|
|
source = generated_program(generated_seed)
|
|
for obfuscation_seed in OBFUSCATION_SEEDS:
|
|
for options in matrix:
|
|
label = f"generated_{generated_seed}_{obfuscation_seed}_{len(options)}"
|
|
try:
|
|
check_source(source, label=label, seed=obfuscation_seed, **options)
|
|
print(f"ok: {label} {options}")
|
|
except Exception as exc:
|
|
failures += 1
|
|
print(f"FAIL: {label} {options}: {exc}")
|
|
print(f"\n{failures} failures" if failures else "\nstress matrix all good")
|
|
return 1 if failures else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|