mirror of
https://github.com/bikini/patchwork
synced 2026-06-27 08:08:41 +00:00
94 lines
4.3 KiB
Python
94 lines
4.3 KiB
Python
from __future__ import annotations
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
from .audit import analyze_source, build_manifest, write_json
|
|
from .core import Obfuscator
|
|
|
|
def _parser() -> argparse.ArgumentParser:
|
|
p = argparse.ArgumentParser(prog='patchwork')
|
|
p.add_argument('input')
|
|
p.add_argument('-o', '--output')
|
|
p.add_argument('--seed', type=int, default=None)
|
|
p.add_argument('--layers', type=int, default=3)
|
|
p.add_argument('--stage2-layers', type=int, default=3, dest='stage2_layers')
|
|
p.add_argument('--keep', action='append', default=[], metavar='NAME')
|
|
p.add_argument('--no-rename', action='store_true')
|
|
p.add_argument('--no-encrypt-strings', action='store_true')
|
|
p.add_argument('--no-obfuscate-numbers', action='store_true')
|
|
p.add_argument('--no-opaque', action='store_true')
|
|
p.add_argument('--no-mba', action='store_true')
|
|
p.add_argument('--no-junk', action='store_true')
|
|
p.add_argument('--no-lazy', action='store_true')
|
|
p.add_argument('--no-anti-debug', action='store_true')
|
|
p.add_argument('--audit-only', action='store_true', help='analyze the input and exit without writing obfuscated output')
|
|
p.add_argument('--audit-json', metavar='PATH', help='write static audit metadata as JSON')
|
|
p.add_argument('--manifest', metavar='PATH', help='write build manifest with hashes, options, and audit metadata')
|
|
p.add_argument('--strict-audit', action='store_true', help='refuse to obfuscate when sensitive API indicators are present')
|
|
p.add_argument('--quiet', '-q', action='store_true')
|
|
return p
|
|
|
|
def _options_dict(args: argparse.Namespace) -> dict[str, object]:
|
|
return {
|
|
'rename': not args.no_rename,
|
|
'encrypt_strings': not args.no_encrypt_strings,
|
|
'obfuscate_numbers': not args.no_obfuscate_numbers,
|
|
'opaque_predicates': not args.no_opaque,
|
|
'mba': not args.no_mba,
|
|
'junk_branches': not args.no_junk,
|
|
'lazy_funcs': not args.no_lazy,
|
|
'anti_debug': not args.no_anti_debug,
|
|
'layers': args.layers,
|
|
'stage2_layers': args.stage2_layers,
|
|
'keep': sorted(set(args.keep)),
|
|
}
|
|
|
|
def main(argv: list[str] | None=None) -> int:
|
|
args = _parser().parse_args(argv)
|
|
inp = Path(args.input)
|
|
if not inp.is_file():
|
|
print(f'patchwork: input file not found: {inp}', file=sys.stderr)
|
|
return 2
|
|
src = inp.read_text(encoding='utf-8')
|
|
audit = analyze_source(src, path=inp)
|
|
if args.audit_json:
|
|
write_json(args.audit_json, audit)
|
|
if args.audit_only:
|
|
if not args.quiet:
|
|
print(json.dumps(audit, indent=2, sort_keys=True))
|
|
return 1 if args.strict_audit and audit['review']['requires_human_review'] else 0
|
|
if args.strict_audit and audit['review']['requires_human_review']:
|
|
print('patchwork: strict audit refused to obfuscate input with review indicators', file=sys.stderr)
|
|
for indicator in audit['review']['indicators']:
|
|
print(f" line {indicator['line']}: {indicator['kind']} {indicator['name']} - {indicator['reason']}", file=sys.stderr)
|
|
return 3
|
|
options = _options_dict(args)
|
|
obf = Obfuscator(seed=args.seed, keep=set(args.keep), **{key: value for key, value in options.items() if key != 'keep'})
|
|
out_src = obf.obfuscate(src)
|
|
out_path = Path(args.output) if args.output else inp.with_name(inp.stem + '_obf.py')
|
|
out_path.write_text(out_src, encoding='utf-8')
|
|
if args.manifest:
|
|
manifest = build_manifest(
|
|
input_path=inp,
|
|
output_path=out_path,
|
|
input_source=src,
|
|
output_source=out_src,
|
|
seed=obf.seed,
|
|
options=options,
|
|
audit=audit,
|
|
)
|
|
write_json(args.manifest, manifest)
|
|
if not args.quiet:
|
|
in_size = len(src)
|
|
out_size = len(out_src)
|
|
ratio = out_size / max(in_size, 1)
|
|
print(f'patchwork: {inp} -> {out_path}\n input: {in_size:,} bytes\n output: {out_size:,} bytes ({ratio:.1f}x)\n seed: {obf.seed}\n layers: {obf.layers}', file=sys.stderr)
|
|
if args.manifest:
|
|
print(f' manifest: {args.manifest}', file=sys.stderr)
|
|
if audit['review']['requires_human_review']:
|
|
print(f" audit: {audit['review']['indicator_count']} review indicator(s)", file=sys.stderr)
|
|
return 0
|
|
if __name__ == '__main__':
|
|
raise SystemExit(main())
|