├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── package-test.yml │ └── python-publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── python_obfuscator ├── __init__.py ├── cli │ └── __init__.py ├── helpers │ ├── __init__.py │ ├── random_datatype.py │ └── variable_name_generator.py ├── obfuscator.py └── techniques.py ├── setup.py └── tests └── test_module.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://www.paypal.me/dteather'] 2 | github: davidteather -------------------------------------------------------------------------------- /.github/workflows/package-test.yml: -------------------------------------------------------------------------------- 1 | name: CI Test 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | - nightly 10 | - 'releases/*' 11 | 12 | jobs: 13 | Unit-Tests: 14 | timeout-minutes: 10 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | os: [macos-latest, windows-latest, ubuntu-latest] 20 | python-version: [3.6, 3.7, 3.8, 3.9] 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Install Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v2 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Setup dependencies 28 | run: | 29 | python -m pip install --upgrade pip 30 | pip install pytest 31 | python setup.py install 32 | 33 | - name: Run Tests 34 | run: pytest tests -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install setuptools wheel twine 25 | - name: Build and publish 26 | env: 27 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 29 | run: | 30 | python setup.py sdist 31 | twine upload dist/* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test.py 2 | __pycache__ 3 | dist 4 | build 5 | python_obfuscator.egg-info 6 | .pytest_cache -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 David Teather 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python-Obfuscator 2 | 3 | One night I got bored of writing good code, so I made good code to make bad code. 4 | 5 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/davidteather/python-obfuscator?style=flat-square)](https://github.com/davidteather/python-obfuscator/releases) [![Downloads](https://static.pepy.tech/personalized-badge/python-obfuscator?period=total&units=international_system&left_color=grey&right_color=orange&left_text=Downloads)](https://pypi.org/project/python-obfuscator/) ![](https://visitor-badge.laobi.icu/badge?page_id=davidteather.python-obfuscator) [![Linkedin](https://img.shields.io/badge/LinkedIn-0077B5?style=flat-square&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/david-teather-4400a37a/) 6 | 7 | ### **DONT USE IN PRODUCTION** 8 | 9 | **I just made this because it was interesting to me. I do plan on making this more official in the future, but currently don't have the time!** 10 | 11 | Consider sponsoring me [here](https://github.com/sponsors/davidteather) 12 | 13 | ## Installing 14 | 15 | ``` 16 | pip install python-obfuscator 17 | ``` 18 | 19 | ## Quickstart 20 | 21 | Print out obfuscated code 22 | ``` 23 | pyobfuscate -i your_file.py 24 | ``` 25 | 26 | Apply changes to the input file 27 | ``` 28 | pyobfuscate -i your_file.py -r True 29 | ``` 30 | 31 | ## More Detailed Documentation 32 | 33 | You can use this as a module if you want 34 | ``` 35 | import python_obfuscator 36 | obfuscator = python_obfuscator.obfuscator() 37 | 38 | code_to_obfuscate = "print('hello world')" 39 | ``` 40 | 41 | You can also exclude certain techniques applied for obfuscation 42 | ``` 43 | import python_obfuscator 44 | from python_obfuscator.techniques import add_random_variables 45 | obfuscator = python_obfuscator.obfuscator() 46 | 47 | code_to_obfuscate = "print('hello world')" 48 | obfuscated_code = obfuscator.obfuscate(code_to_obfuscate, remove_techniques=[add_random_variables]) 49 | ``` 50 | Find a list of all techniques [here](https://github.com/davidteather/python-obfuscator/blob/210da2d3dfb96ab7653fad869a43cb67aeb0fe67/python_obfuscator/techniques.py#L87) 51 | 52 | ## Example Obfuscated Code 53 | 54 | Input 55 | ``` 56 | y = input("what's your favorite number") 57 | 58 | user_value = int(y) 59 | print("{} that's a great number!".format(user_value)) 60 | ``` 61 | 62 | [With `pyobfuscate -i file.py`](https://gist.github.com/davidteather/b6ff932140d8c174b9c6f50c9b42fdaf) 63 | 64 | 65 | [With `--one-liner True`](https://gist.github.com/davidteather/75e48c04bf74f0262fe2919239a74295) 66 | 67 | ## Authors 68 | 69 | * **David Teather** - *Initial work* - [davidteather](https://github.com/davidteather) 70 | 71 | See also the list of [contributors](https://github.com/davidteather/python-obfuscator) who participated in this project. 72 | 73 | ## License 74 | 75 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 76 | -------------------------------------------------------------------------------- /python_obfuscator/__init__.py: -------------------------------------------------------------------------------- 1 | from .obfuscator import obfuscator 2 | -------------------------------------------------------------------------------- /python_obfuscator/cli/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import python_obfuscator 3 | from python_obfuscator.techniques import one_liner 4 | import argparse 5 | 6 | 7 | def convert_file(args): 8 | file_path = args.input 9 | obfuscate = python_obfuscator.obfuscator() 10 | 11 | # removed techniques 12 | remove = [] 13 | if not args.one_liner: 14 | remove.append(one_liner) 15 | 16 | with open(file_path, "r") as f: 17 | data = f.read() 18 | obfuscated_data = obfuscate.obfuscate(data, remove_techniques=remove) 19 | 20 | if args.replace: 21 | with open(file_path, "w+") as f: 22 | f.write(obfuscated_data) 23 | else: 24 | print(obfuscated_data) 25 | 26 | 27 | def cli(): 28 | parser = argparse.ArgumentParser(description="Process CLI args") 29 | parser.add_argument( 30 | "-i", "--input", help="File to obfuscate", required=True, type=str 31 | ) 32 | parser.add_argument( 33 | "-r", 34 | "--replace", 35 | help="Replace the file specified", 36 | required=False, 37 | default=False, 38 | type=bool, 39 | ) 40 | parser.add_argument( 41 | "-ol", 42 | "--one-liner", 43 | help="Add the one liner technique", 44 | required=False, 45 | default=False, 46 | type=bool, 47 | ) 48 | args = parser.parse_args() 49 | 50 | convert_file(args) 51 | -------------------------------------------------------------------------------- /python_obfuscator/helpers/__init__.py: -------------------------------------------------------------------------------- 1 | from .variable_name_generator import VariableNameGenerator 2 | from .random_datatype import RandomDataTypeGenerator 3 | -------------------------------------------------------------------------------- /python_obfuscator/helpers/random_datatype.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | import time 4 | 5 | 6 | class RandomDataTypeGenerator: 7 | def __init__(self): 8 | self.generator_options = [self.random_string, self.random_int] 9 | 10 | def get_random(self): 11 | return random.choice(self.generator_options)() 12 | 13 | def random_string(self, length=79): 14 | # Why is it 79 by default? 15 | # See: https://stackoverflow.com/a/16920876/11472374 16 | # As kirelagin commented readability is very important 17 | return "".join( 18 | random.choice(string.ascii_lowercase + string.ascii_uppercase) 19 | for i in range(length) 20 | ) 21 | 22 | def random_int(self): 23 | return random.randint(random.randint(0, 300), random.randint(300, 999)) 24 | -------------------------------------------------------------------------------- /python_obfuscator/helpers/variable_name_generator.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | import time 4 | 5 | 6 | class VariableNameGenerator: 7 | def __init__(self): 8 | self.generator_options = [ 9 | self.random_string, 10 | self.l_and_i, 11 | self.time_based, 12 | self.just_id, 13 | self.scream, 14 | self.single_letter_a_lot, 15 | ] 16 | 17 | def get_random(self, id): 18 | return random.choice(self.generator_options)(id) 19 | 20 | def random_string(self, id, length=79): 21 | # Why is it 79 by default? 22 | # See: https://stackoverflow.com/a/16920876/11472374 23 | # As kirelagin commented readability is very important 24 | return "".join( 25 | random.choice(string.ascii_letters) for i in range(length) 26 | ) + str(id) 27 | 28 | def l_and_i(self, id): 29 | return "".join(random.choice("Il") for i in range(id)) 30 | 31 | def time_based(self, id): 32 | return ( 33 | random.choice(string.ascii_letters) 34 | + str(time.time()).replace(".", "") 35 | + str(id) 36 | ) 37 | 38 | def just_id(self, id): 39 | # python doesn't work with numbers for variable names 40 | return random.choice(string.ascii_letters) + str(id) 41 | 42 | def scream(self, id): 43 | return "".join(random.choice("Aa") for i in range(id)) 44 | 45 | def single_letter_a_lot(self, id): 46 | return random.choice(string.ascii_letters) * id 47 | -------------------------------------------------------------------------------- /python_obfuscator/obfuscator.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from .techniques import obfuscate 3 | 4 | 5 | class obfuscator: 6 | def __init__(self, logging_level=logging.error): 7 | pass 8 | 9 | def obfuscate(self, code, remove_techniques=[]): 10 | return obfuscate(code, remove_techniques) 11 | -------------------------------------------------------------------------------- /python_obfuscator/techniques.py: -------------------------------------------------------------------------------- 1 | import re 2 | import ast 3 | import random 4 | import time 5 | from .helpers import VariableNameGenerator, RandomDataTypeGenerator 6 | import regex 7 | 8 | 9 | def one_liner(code): 10 | # TODO: strings with \n at top 11 | formatted_code = re.sub( 12 | r"(;)\1+", 13 | ";", 14 | """exec(\"\"\"{};\"\"\")""".format( 15 | code.replace("\n", ";").replace('"""', '\\"\\"\\"') 16 | ), 17 | ) 18 | 19 | if formatted_code[0] == ';': 20 | return formatted_code[1:] 21 | return formatted_code 22 | 23 | def variable_renamer(code): 24 | # add \n so regex picks it up 25 | code = "\n" + code 26 | variable_names = re.findall(r"(\w+)(?=( |)=( |))", code) 27 | name_generator = VariableNameGenerator() 28 | for i in range(len(variable_names)): 29 | obfuscated_name = name_generator.get_random(i + 1) 30 | code = re.sub( 31 | r"(?<=[^.])(\b{}\b)".format(variable_names[i][0]), obfuscated_name, code 32 | ) 33 | return code 34 | 35 | 36 | def add_random_variables(code): 37 | useless_variables_to_add = random.randint(100, 400) 38 | name_generator = VariableNameGenerator() 39 | data_generator = RandomDataTypeGenerator() 40 | for v in range(1, useless_variables_to_add): 41 | rand_data = data_generator.get_random() 42 | if type(rand_data) == str: 43 | rand_data = '"{}"'.format(rand_data) 44 | if v % 2 == 0: 45 | code = "{} = {}\n".format(name_generator.get_random(v), rand_data) + code 46 | else: 47 | code = code + "\n{} = {}".format(name_generator.get_random(v), rand_data) 48 | return code 49 | 50 | 51 | def str_to_hex_bytes(code): 52 | # ((?<=(( | |)\w+( |)=( |))("""|"|'))[\W\w]*?(?=("""|"|'))) 53 | # TODO: Fix still buggy and kinda just wanna publish this to github rn 54 | python_string_decoraters = ['"""', "'''", '"', "'"] 55 | 56 | for s in python_string_decoraters: 57 | pattern = r"((?<=(( | |\n)\w+( |)=( |))({}))[\W\w]*?(?=({})))".format(s, s) 58 | t = regex.findall(pattern, code) 59 | for v in t: 60 | string_contents = v[0] 61 | if s == '"' and string_contents == '"': 62 | continue 63 | if s == "'" and string_contents == "'": 64 | continue 65 | hex_bytes = "\\" + "\\".join( 66 | x.encode("utf-8").hex() for x in string_contents 67 | ) 68 | code = regex.sub(pattern, str(hex_bytes).replace("\\", "\\\\"), code) 69 | 70 | return code 71 | 72 | 73 | def obfuscate(code, remove_techniques=[]): 74 | if len(remove_techniques) == 0: 75 | methods = all_methods 76 | else: 77 | methods = all_methods.copy() 78 | for technique in remove_techniques: 79 | methods.remove(technique) 80 | 81 | for technique in methods: 82 | code = technique(code) 83 | 84 | return code 85 | 86 | 87 | all_methods = [variable_renamer, add_random_variables, one_liner] 88 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | import os.path 3 | import setuptools 4 | 5 | with open("README.md", "r") as fh: 6 | long_description = fh.read() 7 | 8 | setuptools.setup( 9 | name="python_obfuscator", 10 | packages=setuptools.find_packages(), 11 | version="0.0.2", 12 | license="MIT", 13 | description="It's a python obfuscator.", 14 | author="David Teather", 15 | author_email="contact.davidteather@gmail.com", 16 | url="https://github.com/davidteather/python-obfuscator", 17 | long_description=long_description, 18 | long_description_content_type="text/markdown", 19 | download_url="https://github.com/davidteather/python-obfuscator/tarball/master", 20 | keywords=["obfuscator"], 21 | install_requires=["regex"], 22 | classifiers=[ 23 | "Development Status :: 3 - Alpha", 24 | "Intended Audience :: Developers", 25 | "Topic :: Software Development :: Build Tools", 26 | "License :: OSI Approved :: MIT License", 27 | "Programming Language :: Python :: 3.6", 28 | "Programming Language :: Python :: 3.7", 29 | "Programming Language :: Python :: 3.8", 30 | "Programming Language :: Python :: 3.9", 31 | ], 32 | entry_points={"console_scripts": ["pyobfuscate=python_obfuscator.cli:cli"]}, 33 | ) 34 | -------------------------------------------------------------------------------- /tests/test_module.py: -------------------------------------------------------------------------------- 1 | import python_obfuscator 2 | 3 | # TODO: Improve tests 4 | def test_module_methods(): 5 | obfuscate = python_obfuscator.obfuscator() 6 | 7 | code = """ 8 | v1 = 0 9 | v2 = 0 10 | v4 = 10 11 | assert v4 + v4 == 20 12 | assert v1 + v2 == 0 13 | """.replace( 14 | " ", "" 15 | ) 16 | 17 | exec(obfuscate.obfuscate(code)) 18 | --------------------------------------------------------------------------------