├── .github └── FUNDING.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── config.py ├── images └── php-obfuscator.png ├── main.py ├── requirements.txt └── start.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: mnestorov 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### 1.0 (2023.05.07) 4 | - Initial release. 5 | 6 | ### 1.1 (2024.09.04) 7 | - Added `shlex.quote()` in the `obfuscate_php()` function when constructing the command to ensure that paths with spaces or special characters are handled properly. 8 | - Updated the YAKPRO command in `config.py` to use `os.path.expanduser` to make it dynamic and compatible with user home directories -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Martin Nestorov 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![PHP_Obfuscator](images/php-obfuscator.png) 2 | 3 | # PHP Obfuscator 4 | 5 | [![Licence](https://img.shields.io/github/license/Ileriayo/markdown-badges?style=for-the-badge)](./LICENSE) 6 | 7 | ## Support The Project 8 | 9 | Your support is greatly appreciated and will help ensure all of the projects continued development and improvement. Thank you for being a part of the community! 10 | You can send me money on Revolut by following this link: https://revolut.me/mnestorovv 11 | 12 | --- 13 | 14 | ## Overview 15 | 16 | PHP Obfuscator is a command-line tool build with Python to obfuscate PHP source code files using [YAK Pro](https://github.com/pk-fr/yakpro-po) and [PHP-Parser](https://github.com/nikic/PHP-Parser/tree/4.x/) php libraries. 17 | 18 | The tool aims to protect the intellectual property of your PHP project by making it more difficult for others to understand or reverse-engineer your code. 19 | 20 | The tool can be used to obfuscate **single files**, **multiple files**, or an **entire project directory**. 21 | 22 | ## Example 23 | 24 | ### Before 25 | 26 | **_app/Users.php_** 27 | 28 | ```php 29 | hasMany(Link::class); 62 | } 63 | 64 | public function visits() 65 | { 66 | return $this->hasManyThrough(Visit::class, Link::class); 67 | } 68 | 69 | public function getRouteKeyName() { 70 | return 'username'; 71 | } 72 | 73 | } 74 | 75 | ``` 76 | 77 | ### After 78 | 79 | **_app/Users.php_** 80 | 81 | ```php 82 | hasMany(Link::class); } public function visits() { return $this->hasManyThrough(Visit::class, Link::class); } public function getRouteKeyName() { return "\x75\163\145\162\x6e\x61\x6d\145"; } } 84 | ``` 85 | 86 | ## Requirements 87 | 88 | - Python 3.6+ 89 | - PHP-Parser (PHP library) 90 | - YAK Pro - Php Obfuscator (PHP library) 91 | 92 | ## Installation 93 | 94 | 1. Clone the repository 95 | 96 | ``` 97 | git clone https://github.com/your-github-repo/php-obfuscator.git 98 | ``` 99 | 100 | 2. Change to the project directory 101 | 102 | ``` 103 | cd php-obfuscator 104 | ``` 105 | 106 | 3. Install YAK Pro - Php Obfuscator 107 | 108 | You need to install this in to the project directory **_php-obfuscator_** 109 | 110 | ``` 111 | git clone https://github.com/pk-fr/yakpro-po.git 112 | ``` 113 | 114 | After that, you need to configure the package from `yakpro-po.cnf` in to the **_php-obfuscator > yakpro-po_** folder. 115 | 116 | **Recommended settings for the YAK Pro configuration:** 117 | 118 | ```php 119 | // Allowed modes are: 'PREFER_PHP7', 'PREFER_PHP5', 'ONLY_PHP7', 'ONLY_PHP5' 120 | $conf->parser_mode = 'PREFER_PHP7'; 121 | 122 | $conf->obfuscate_constant_name = false; 123 | $conf->obfuscate_variable_name = false; 124 | $conf->obfuscate_function_name = false; 125 | $conf->obfuscate_class_name = false; 126 | $conf->obfuscate_interface_name = false; 127 | $conf->obfuscate_trait_name = false; 128 | $conf->obfuscate_class_constant_name = false; 129 | $conf->obfuscate_property_name = false; 130 | $conf->obfuscate_method_name = false; 131 | $conf->obfuscate_namespace_name = false; 132 | $conf->obfuscate_label_name = false; 133 | 134 | // User comment to insert inside each obfuscated file 135 | $conf->user_comment = false; 136 | ``` 137 | 138 | 4. Install PHP-Parser 139 | 140 | You need to install this in to the **yakpro-po** directory in to the project directory **_php-obfuscator > yakpro-po_** 141 | 142 | ``` 143 | git clone https://github.com/nikic/PHP-Parser.git 144 | ``` 145 | 5. Install the required Python packages 146 | 147 | **From the project directory, you need to run this command:** 148 | 149 | ``` 150 | pip install -r requirements.txt 151 | ``` 152 | 153 | ## Usage 154 | 155 | :warning: **Important:** The paths specified in `config.py` (especially YAKPRO) are correct and point to the YakPro-Po obfuscator on your system. 156 | 157 | 1. Ensure you are in the project directory. 158 | 2. Run the script 159 | 160 | ``` 161 | python main.py 162 | ``` 163 | 164 | or 165 | 166 | ``` 167 | bash start.sh 168 | ``` 169 | 170 | **Note:** Don't forget to change the path to the script in to the `start.sh` bash file. 171 | 172 | 3. Follow the prompts to configure the obfuscation settings, including: 173 | - Mode (single file, multiple files, or entire project directory) 174 | - Output directory path 175 | - File or directory paths to exclude 176 | - Whether to create backups of original PHP files 177 | - Choose from different obfuscation options (e.g., obfuscating variable names, function names, or class names) or combine multiple options 178 | 179 | 4. After the obfuscation process is completed, you can find the obfuscated files in the specified output directory. 180 | 181 | ## Used Libraries 182 | 183 | ### Python 184 | 185 | - **os**, **sys**, **shutil**, and **re** - standard Python libraries for working with the file system and regular expressions 186 | - **logging** - standard Python library for logging 187 | - **concurrent.futures** - standard Python library for parallel processing 188 | - **tqdm** - external library for progress bars 189 | 190 | ### PHP 191 | 192 | - [YAK Pro - a PHP library for obfuscating PHP code](https://github.com/pk-fr/yakpro-po) 193 | - [PHP-Parser - a PHP library to parse and traverse PHP code](https://github.com/nikic/PHP-Parser/tree/4.x/) 194 | 195 | ## Troubleshooting 196 | 197 | If you encounter issues after obfuscating your PHP project, you may need to revert your files to the original (non-obfuscated) versions and reevaluate your obfuscation strategy. Always keep backups of your original code before applying obfuscation, as it can be difficult or impossible to reverse the process and recover the original code. 198 | 199 | ## TODOs 200 | 201 | - [x] **_Custom obfuscation options:_** ~~Allow users to choose from different obfuscation options (e.g., obfuscating variable names, function names, or class names) or combine multiple options.~~ 202 | - [x] **_Preserve original file structure:_** ~~When obfuscating an entire project directory, recreate the original directory structure in the output folder, maintaining the same hierarchy~~. 203 | - **_File filters:_** Add filters to include or exclude specific files based on their names or extensions. 204 | - **_Command-line arguments:_** Implement command-line argument parsing to allow users to run the script with different configurations without manually editing the code. 205 | 206 | ## Support The Project 207 | 208 | If you find this script helpful and would like to support its development and maintenance, please consider the following options: 209 | 210 | - **_Star the repository_**: If you're using this script from a GitHub repository, please give the project a star on GitHub. This helps others discover the project and shows your appreciation for the work done. 211 | 212 | - **_Share your feedback_**: Your feedback, suggestions, and feature requests are invaluable to the project's growth. Please open issues on the GitHub repository or contact the author directly to provide your input. 213 | 214 | - **_Contribute_**: You can contribute to the project by submitting pull requests with bug fixes, improvements, or new features. Make sure to follow the project's coding style and guidelines when making changes. 215 | 216 | - **_Spread the word_**: Share the project with your friends, colleagues, and social media networks to help others benefit from the script as well. 217 | 218 | - **_Donate_**: Show your appreciation with a small donation. Your support will help me maintain and enhance the script. Every little bit helps, and your donation will make a big difference in my ability to keep this project alive and thriving. 219 | 220 | Your support is greatly appreciated and will help ensure all of the projects continued development and improvement. Thank you for being a part of the community! 221 | You can send me money on Revolut by following this link: https://revolut.me/mnestorovv 222 | 223 | --- 224 | 225 | ## License 226 | 227 | This project is released under the MIT License. 228 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # Configure colors for printing messages 4 | YELLOW = '\033[93m' 5 | BLUE = '\033[94m' 6 | GREEN = '\033[92m' 7 | RED = '\033[91m' 8 | RESET = '\033[0m' 9 | 10 | # Configure YakPro packages path (use full path and ensure it's correct) 11 | YAKPRO = [os.path.expanduser("/home/YOUR_USERNAME/PROJECT_DIR/yakpro-po/yakpro-po.php"), "-o"] 12 | 13 | # Log filename 14 | log_filename = 'app.log' 15 | -------------------------------------------------------------------------------- /images/php-obfuscator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mnestorov/php-obfuscator/25a13db8535c6abb6c0ebf1573fa7cec304bae3c/images/php-obfuscator.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import logging 4 | import shutil 5 | from concurrent.futures import ThreadPoolExecutor 6 | from tqdm import tqdm 7 | from config import (log_filename, YELLOW, BLUE, GREEN, RED, RESET, YAKPRO) 8 | from subprocess import call 9 | import shlex 10 | 11 | # Configure logging 12 | logging.basicConfig(filename=log_filename, level=logging.DEBUG, format='%(asctime)s %(levelname)s:%(message)s', datefmt='%Y-%m-%d %H:%M:%S') 13 | 14 | # Configuration for YakPro-Po obfuscation options 15 | OB_OPTIONS = { 16 | "variables": "--obfuscate-variables", 17 | "functions": "--obfuscate-functions", 18 | "classes": "--obfuscate-classes" 19 | } 20 | 21 | def get_obfuscation_options(): 22 | """Prompt user for obfuscation preferences.""" 23 | print(f"{GREEN}Choose the obfuscation options:{RESET}") 24 | options = [] 25 | 26 | for key, desc in OB_OPTIONS.items(): 27 | choice = input(f"{GREEN}Obfuscate {key}? (y/n): {RESET}").lower() 28 | if choice == 'y': 29 | options.append(desc) 30 | 31 | return options 32 | 33 | def obfuscate_php(input_file, obfuscation_options, create_backup, output_directory): 34 | if create_backup: 35 | backup_file = f"{os.path.splitext(input_file)[0]}_backup.php" 36 | shutil.copy2(input_file, backup_file) 37 | logging.info(f"Created backup: {backup_file}") 38 | 39 | try: 40 | # Create the directory if it doesn't exist 41 | os.makedirs(output_directory, exist_ok=True) 42 | 43 | output_file = os.path.join(output_directory, f"obfuscated_{os.path.basename(input_file)}") 44 | logging.info(f"Obfuscating {input_file}") 45 | 46 | # Use shlex.quote to handle paths with spaces or special characters 47 | command = YAKPRO + obfuscation_options + [shlex.quote(output_file), shlex.quote(input_file)] 48 | call(command) 49 | 50 | print(f"{GREEN}Obfuscated file saved as {output_file}{RESET}") 51 | logging.info(f"Obfuscated {input_file} successfully") 52 | 53 | except Exception as e: 54 | logging.error(f"Error while obfuscating {input_file}: {e}") 55 | print(f"{RED}Error while obfuscating {input_file}: {e}{RESET}") 56 | 57 | def obfuscate_file(args): 58 | input_file, obfuscation_options, create_backup, output_directory = args 59 | obfuscate_php(input_file, obfuscation_options, create_backup, output_directory) 60 | 61 | def process_directory(directory, obfuscation_options, exclude_list, create_backup, output_directory, max_workers=4): 62 | total_files = sum(len(files) for _, _, files in os.walk(directory) if any(f.lower().endswith(".php") for f in files)) 63 | 64 | progress_bar = tqdm(total=total_files, desc="Obfuscating", unit="file") 65 | 66 | file_list = [] 67 | for root, _, files in os.walk(directory): 68 | for file in files: 69 | if file.lower().endswith(".php"): 70 | input_file = os.path.join(root, file) 71 | 72 | if any(os.path.commonpath([input_file, exclude]) == os.path.abspath(exclude) for exclude in exclude_list): 73 | logging.info(f"Skipping {input_file}: excluded") 74 | continue 75 | 76 | # Calculate the target directory in the output structure 77 | relative_path = os.path.relpath(root, directory) 78 | target_directory = os.path.join(output_directory, relative_path) 79 | 80 | file_list.append((input_file, obfuscation_options, create_backup, target_directory)) 81 | 82 | progress_bar = tqdm(total=len(file_list), desc="Obfuscating", unit="file") 83 | 84 | with ThreadPoolExecutor(max_workers=max_workers) as executor: 85 | for _ in executor.map(obfuscate_file, file_list): 86 | progress_bar.update() 87 | 88 | progress_bar.close() 89 | 90 | def validate_mode_input(mode): 91 | try: 92 | mode = int(mode) 93 | if mode not in [1, 2, 3]: 94 | raise ValueError 95 | return mode 96 | except ValueError: 97 | logging.error("Invalid mode input. Please enter 1, 2, or 3.") 98 | print(f"{RED}Invalid mode. Choose from: 1, 2, or 3{RESET}") 99 | sys.exit(1) 100 | 101 | def validate_directory_input(directory_path): 102 | if not os.path.isdir(directory_path): 103 | logging.error(f"Invalid directory path: {directory_path}") 104 | print(f"{RED}Invalid directory path{RESET}") 105 | sys.exit(1) 106 | 107 | def main(): 108 | print(f"{YELLOW}Welcome to the PHP Obfuscator!{RESET}") 109 | print(f"{YELLOW}Follow the prompts to obfuscate your PHP files.\n{RESET}") 110 | 111 | print(f"{GREEN}Choose the mode for obfuscating your PHP files:{RESET}") 112 | print(f"{BLUE}1: Single file{RESET}") 113 | print(f"{BLUE}2: Multiple files{RESET}") 114 | print(f"{BLUE}3: Entire project directory{RESET}") 115 | mode = validate_mode_input(input(f"{GREEN}Enter the mode number (1/2/3): {RESET}")) 116 | 117 | output_directory = input(f"{GREEN}Enter the output directory path: {RESET}") 118 | if not os.path.exists(output_directory): 119 | os.makedirs(output_directory) 120 | 121 | exclude_input = input(f"{GREEN}Enter file or directory paths to exclude (separated by a space) (you can skip this step): {RESET}") 122 | exclude_list = [os.path.abspath(exclude.strip()) for exclude in exclude_input.split()] 123 | 124 | create_backup = input(f"{GREEN}Create backups of original PHP files? (y/n): {RESET}").lower() == 'y' 125 | 126 | obfuscation_options = get_obfuscation_options() 127 | 128 | if mode == 1: 129 | input_file = input(f"{GREEN}Enter the PHP file path: {RESET}") 130 | if not input_file.lower().endswith(".php") or not os.path.isfile(input_file): 131 | logging.warning("Invalid PHP file path") 132 | print(f"{RED}Invalid PHP file path{RESET}") 133 | sys.exit(1) 134 | if any(os.path.commonpath([input_file, exclude]) == os.path.abspath(exclude) for exclude in exclude_list): 135 | logging.info(f"Skipping {input_file}: excluded") 136 | else: 137 | obfuscate_php(input_file, obfuscation_options, create_backup, output_directory) 138 | elif mode == 2: 139 | file_paths = input(f"{GREEN}Enter the PHP file paths separated by a space: {RESET}") 140 | files = file_paths.split() 141 | 142 | for input_file in files: 143 | if not input_file.lower().endswith(".php") or not os.path.isfile(input_file): 144 | logging.warning(f"Skipping {input_file}: not a valid PHP file") 145 | print(f"{RED}Skipping {input_file}: not a valid PHP file{RESET}") 146 | continue 147 | obfuscate_php(input_file, obfuscation_options, create_backup, output_directory) 148 | elif mode == 3: 149 | input_directory = input(f"{GREEN}Enter the project directory path: {RESET}") 150 | validate_directory_input(input_directory) 151 | process_directory(input_directory, obfuscation_options, exclude_list, create_backup, output_directory) 152 | else: 153 | logging.warning("Invalid mode. Choose from: 1, 2, or 3") 154 | print(f"{RED}Invalid mode. Choose from: 1, 2, or 3{RESET}") 155 | sys.exit(1) 156 | 157 | if __name__ == "__main__": 158 | main() 159 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | tqdm -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python3 /home/YOUR_USERNAME/path/to/your/script/main.py --------------------------------------------------------------------------------