Skip to content

techlinn/WhatThePy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Python 3.10-3.14 Platform License

People kept telling me my code looked bad. So I wrote a tool to make it worse (on purpose)


What Is This?

WhatThePy obfuscates a single Python file using compression, authenticated encryption, literal-table protection, and a compact runtime loader. The result is compact, messy code that still runs identically but is harder to casually inspect.

Important: This is for fun and learning. It deters casual viewers but will not stop a skilled reverse engineer with live process access. See SECURITY.md for the tested threat model.

Platform: Windows + CPython 3.10–3.14. Build and run on the same CPython minor version.


Before & After

Your Code After WhatThePy
def greet(name):
    message = f"Hello, {name}!"
    print(message)
    return message

if __name__ == "__main__":
    greet("World")
...
洪ΠОΖНСΔΘΜΖФ鷹=lambda:_СΖΙ虎ΣΓФЙТХВГ(_Θ宙鷹荒鷹日ЙΖΥХДΑ(_У荒ЛΡ荒黃ΣНПΘР豹,_ДΒПΘΞНΠΡ麟麟Γ玄,_РК虎Π宙玄ΕНКΛ龍Ж,_麒鳳ΞБΕГОП宙БΞ獅),_ΥДΓ日Ζ地УΙΤ麒熊麒)
def _日И月黃宇ОНΖ麟龍麒洪():return bytes(_洪ΠОΖНСΔΘΜΖФ鷹())
_다허يεمα地러ص서Т風=_日И月黃宇ОНΖ麟龍麒洪()
def _ذרгΚЛΤ조さ洪터βש(_바かたצדαΜח春غР秋,_צカישىこضФオמП寒):
 _אנЮ天라ΘШנ火昃הן=bytes(_v^186 for _v in [210, 219, 201, 210, 214, 211, 216]).decode()
 _木סкעل寒бえいΚ머Φ=bytes(_v^109 for _v in [30, 5, 12, 95, 88, 91]).decode()
 _川거黃麒جウ宙머辰けиף=__import__(_אנЮ天라ΘШנ火昃הן);_ךえ黃لظ허אס昃타麒霜=getattr(_川거黃麒جウ宙머辰けиף,_木סкעل寒бえいΚ머Φ)
 _너さИر雨חר雪러Х퍼風=bytearray(len(_צカישىこضФオמП寒));_玄СβחΓ辰ΡΧסתقケ=32
 for _소ד河そえΑض冬ЫてДذ in range(0,len(_צカישىこضФオמП寒),_玄СβחΓ辰ΡΧסתقケ):
  _رΘЙЙ日ТРЫЪב山Γ=(_소ד河そえΑض冬ЫてДذ//_玄СβחΓ辰ΡΧסתقケ).to_bytes(8,'little')
  _لפへζひחيטΩたカΩ=_ךえ黃لظ허אס昃타麒霜(_바かたצדαΜח春غР秋+_رΘЙЙ日ТРЫЪב山Γ).digest()
  _قて차あ차Э雪ث黃ضΗ雪=_צカישىこضФオמП寒[_소ד河そえΑض冬ЫてДذ:_소ד河そえΑض冬ЫてДذ+_玄СβחΓ辰ΡΧסתقケ]
  for _Яו暑火ءФعجПΣいق,_כ카ΧΣ터γΗכدすלר in enumerate(_قて차あ차Э雪ث黃ضΗ雪):_너さИر雨חר雪러Х퍼風[_소ד河そえΑض冬ЫてДذ+_Яו暑火ءФعجПΣいق]=_כ카ΧΣ터γΗכدすלר^_لפへζひחيטΩたカΩ[_Яו暑火ءФعجПΣいق]
 return bytes(_너さИر雨חר雪러Х퍼風)
def _ع雪כΨ麒麟лΨα盈霜暑(_머머天로ט盈صלΡ커ط저,_머חتתРΥפ타ΨУへت):
 _كМへしう가電けキ荒בね=bytes(_v^35 for _v in [75, 66, 80, 75, 79, 74, 65]).decode()
 _クア風ΣЛд宿火玄Эи저=bytes(_v^237 for _v in [158, 133, 140, 223, 216, 219]).decode()
 _л더פ虎ほ電Кδ冬다Τو=__import__(_كМへしう가電けキ荒בね);_き퍼水暑노זאΗ사لىט=getattr(_л더פ虎ほ電Кδ冬다Τو,_クア風ΣЛд宿火玄Эи저)
 return _き퍼水暑노זאΗ사لىט(_머חتתРΥפ타ΨУへت+_머머天로ט盈صלΡ커ط저).digest()
def _火دね카ζ列УبلてבЗ(_저ЛЭ河로ם오רעخΣ카,_נ黃تعえカعظさΜ川エ):
 _شסすけγЦВεزΞΞ코=_저ЛЭ河로ם오רעخΣ카[:16];_고زつ玄г宇ЦγЮע霜す=_저ЛЭ河로ם오רעخΣ카[16:]
 _פ코ג자طاい코Б秋ىכ=_ع雪כΨ麒麟лΨα盈霜暑(_شסすけγЦВεزΞΞ코,_נ黃تعえカعظさΜ川エ)
 continues...

Installation

git clone https://github.com/techlinn/WhatThePy.git
cd WhatThePy
pip install .

For development:

pip install -e ".[dev]"

Requirements: Python 3.10–3.14 on Windows. WhatThePy has no required runtime dependencies.


Usage

whatthepy [file.py] [flags]
# or: python -m whatthepy [file.py] [flags]

Flags:

  • --output PATH - write the stub to a specific path
  • --force - overwrite existing stub/payload files
  • --quiet - suppress progress and summary output
  • --version - print the installed version
  • --strip-docs - strip module/class/function docstrings. Off by default so frameworks that read __doc__ at runtime (FastAPI endpoint descriptions, click/Typer help, sphinx, help()) keep working.
  • --self-contained - embed everything in a single .py (no .dat sidecar). Default is split mode (stub + .dat).
  • --level {low,high} - high (default) encrypts string literals into a runtime-decrypted table; low skips that for a faster, weaker pass.

WhatThePy is single-file: it obfuscates one .py at a time and does not rewrite cross-file imports.

Performance notes

  • Split mode (default) keeps the encrypted payload in a sidecar .dat file. This is the fastest cold start and the recommended default.
  • Self-contained mode embeds the payload in the stub source. Startup is slower and the file is larger, but distribution is a single file.
  • high level restores literals once at load time and is stronger. low level skips literal-table encryption for faster builds and lower overhead.

Works with PyInstaller (split mode):

pyinstaller --onefile --clean --add-data "yourfile_payload.dat;." yourfile_obfuscated.py

Static imports are emitted under a non-executing PyInstaller scan guard. Add --hidden-import=NAME for modules loaded dynamically (importlib.import_module(...) / __import__(...) with non-literal names).


Compatibility & Limitations

WhatThePy is designed so an obfuscated file runs identically and compiles with PyInstaller without runtime errors. Verified against f-strings, match/case literal patterns, PEP 695 type/generics, from __future__ import annotations + dataclass forward refs, TypedDict, Enum, __all__, bytes literals, decorator string args, typing.get_type_hints, default-string args, walrus, closures, __slots__, try/except messages, struct.pack, asyncio, multiprocessing (spawn), __init_subclass__, metaclasses, generators, and function-local imports.

Things to know:

  • Single-file only. No batch/package mode and no cross-file import rewriting. Obfuscate each entry script individually; sibling modules are imported as-is.
  • Relative imports work when the obfuscated file is imported as part of its package (the import machinery sets __package__). Running an obfuscated package module directly as a top-level script will fail relative imports - same as plain Python.
  • Docstrings are kept by default (so FastAPI/click/sphinx/help() work). Use --strip-docs to remove them.
  • inspect.getsource returns the obfuscated stub source (not your original) rather than raising; inspect.getfile returns the stub path. This is expected.
  • Co-located data files (assets next to your source) are not bundled automatically. With PyInstaller, add each via --add-data and read them from sys._MEIPASS at runtime (fall back to os.path.dirname(__file__) when not frozen).
  • Python version pin. The stub checks sys.version_info and refuses to run on a different CPython minor version than the one it was built with. Build and run on the same Python interpreter. With PyInstaller this is automatic (the bundled interpreter matches).

Protection Level

Threat Protected?
Script kiddies Yes
Casual copy-paste Yes
Quick glances Yes
Basic static analysis Yes
Tampered payload files Yes
Simple Python loader monkeypatches Partially
Motivated attackers with debuggers No
Live code-object dumping No

Good for: Learning, CTF challenges, deterring casual viewers Not for: Protecting secrets, valuable IP, or anything critical

See SECURITY.md for the exact tested and untested attacker capabilities.


Anti-Reverse-Engineering

Every build applies fail-closed payload integrity checks and per-build randomization, so no two builds share a fixed pattern. Corrupted payload or key data is rejected instead of running a fallback. No anti-debugging is used, so legitimate debug / coverage / pytest runs never trip.

The specifics - how the key is derived, what gets randomized, and how the loader validates a payload - are intentionally not documented here. An open-source obfuscator that explains its own defenses in the README hands a roadmap to everyone. Read whatthepy/ if you want to understand a build.

Honest ceiling

Pure Python cannot beat a skilled reverse engineer who dumps the live code object from a running process. The measures above raise the bar for casual and mid-level reverse engineers and defeat static, WhatThePy-specific automated unpackers, but they do not stop a generic dynamic dumper. This is a weekend project, not a security boundary.


Disclaimer

This is a fun project for learning and experimentation. It makes code harder to casually read but doesn't provide serious protection. Use it for fun, learning, or light deterrence - not for anything critical.

Made this as a weekend project and please.. Don't use it for serious security needs!


License

MIT License - Do whatever you want with it, just don't blame me!


Made with questionable decisions and too much redbull😭

About

People kept telling me my code looked bad. So I wrote a tool to make it worse (on purpose)

Topics

Resources

License

Security policy

Stars

50 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages