├── .gitignore ├── README.md ├── bin └── obfuscate ├── composer.json └── src └── Naneau └── Obfuscator ├── Console └── Command │ └── ObfuscateCommand.php ├── Container.php ├── Node └── Visitor │ ├── ScramblePrivateMethod.php │ ├── ScramblePrivateProperty.php │ ├── ScrambleUse.php │ ├── ScrambleVariable.php │ ├── Scrambler.php │ ├── SkipTrait.php │ └── TrackingRenamerTrait.php ├── Obfuscator.php ├── Obfuscator └── Event │ ├── File.php │ └── FileError.php ├── Resources └── services.yml └── StringScrambler.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Obfuscator 2 | 3 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/naneau/php-obfuscator/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/naneau/php-obfuscator/?branch=master) 4 | 5 | This is an "obfuscator" for PSR/OOp PHP code. Different from other obfuscators, which often use a (reversible) `eval()` based obfuscation, this tool actually [parses PHP](https://github.com/nikic/PHP-Parser), and obfuscates variable names, methods, etc. This means is can not be reversed by tools such as [UnPHP](http://www.unphp.net). 6 | 7 | This library was written out of the need to obfuscate the source for a private library which for various reasons could not be shared without steps to protect the source from prying eyes. It is not technically feasible to "encrypt" PHP source code, while retaining the option to run it on a standard PHP runtime. Tools such as [Zend Guard](http://www.zend.com/products/guard) use run-time plugins, but even these offer no real security. 8 | 9 | While this tool does not make PHP code impossible to read, it will make it significantly less legible. 10 | 11 | It is compatible with PHP 5.3, 5.4 and 5.5, but needs PHP 5.4+ to run. 12 | 13 | ## Usage 14 | 15 | After cloning this repository (`git clone https://github.com/naneau/php-obfuscator`) and installing the dependencies through Composer (`composer install`), run the following command to obfuscate a directory of PHP files: 16 | 17 | ```bash 18 | ./bin/obfuscate obfuscate /input/directory /output/directory 19 | ``` 20 | 21 | If you've installed this package through [Composer](https://getcomposer.org), you'll find the `obfuscate` command in the relevant [bin dir](https://getcomposer.org/doc/articles/vendor-binaries.md). 22 | 23 | ### Configuration 24 | 25 | You may find that you'll need to prevent certain variables and methods from being renamed. In this case you can create a simple YAML configuration file 26 | 27 | ```yaml 28 | parameters: 29 | 30 | # Ignore variable names 31 | obfuscator.scramble_variable.ignore: 32 | - foo 33 | - bar 34 | - baz 35 | 36 | # Ignore certain methods names 37 | obfuscator.scramble_private_method.ignore: 38 | - foo 39 | - bar 40 | - baz 41 | ``` 42 | 43 | You can run the obfuscator with a configuration file through 44 | 45 | ```bash 46 | ./bin/obfuscate obfuscate /input/directory /output/directory --config=/foo/bar/config.yml 47 | ``` 48 | -------------------------------------------------------------------------------- /bin/obfuscate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | add(new Naneau\Obfuscator\Console\Command\ObfuscateCommand); 21 | $app->run(); 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "naneau/php-obfuscator", 3 | "description": "A basic but functional PHP obfuscator for object oriented PHP", 4 | "license": "MIT", 5 | "minimum-stability": "dev", 6 | 7 | "bin": [ 8 | "bin/obfuscate" 9 | ], 10 | 11 | "autoload": { 12 | "psr-4": { 13 | "Naneau\\Obfuscator\\": "src/Naneau/Obfuscator/" 14 | } 15 | }, 16 | 17 | "authors": [ 18 | { 19 | "name": "Maurice Fonk", 20 | "email": "git@naneau.net" 21 | } 22 | ], 23 | 24 | "require": { 25 | "nikic/php-parser": "~1@dev", 26 | "symfony/console": "~2.5", 27 | "symfony/dependency-injection": "~2.5", 28 | "symfony/config": "~2.5", 29 | "symfony/yaml": "~2.5", 30 | "symfony/event-dispatcher": "~2.5" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Naneau/Obfuscator/Console/Command/ObfuscateCommand.php: -------------------------------------------------------------------------------- 1 | setName('obfuscate') 59 | ->setDescription('Obfuscate a directory of PHP files') 60 | ->addArgument( 61 | 'input_directory', 62 | InputArgument::REQUIRED, 63 | 'Directory of source files, if no output directory is given, it will be overwritten' 64 | ) 65 | ->addArgument( 66 | 'output_directory', 67 | InputArgument::OPTIONAL, 68 | 'Output directory' 69 | )->addOption( 70 | 'leave_whitespace', 71 | null, 72 | InputOption::VALUE_NONE, 73 | 'Leave whitespace in output?' 74 | )->addOption( 75 | 'ignore_error', 76 | null, 77 | InputOption::VALUE_NONE, 78 | 'Continue processing the next file when error is encountered' 79 | )->addOption( 80 | 'config', 81 | null, 82 | InputOption::VALUE_REQUIRED, 83 | 'Configuration file to use' 84 | )->addOption( 85 | 'memory_limit', 86 | null, 87 | InputOption::VALUE_REQUIRED, 88 | 'Runtime memory when running the obsfucator. ' . 89 | 'Example: 128M ' . 90 | 'See http://php.net/manual/en/ini.core.php#ini.memory-limit' 91 | ); 92 | 93 | $this->setContainer(new Container); 94 | } 95 | 96 | /** 97 | * Execute the command 98 | * 99 | * @param InputInterface $input 100 | * @param OutputInterface $output 101 | * @return void 102 | **/ 103 | protected function execute(InputInterface $input, OutputInterface $output) 104 | { 105 | // Finalize the container 106 | $this->finalizeContainer($input); 107 | 108 | // Change runtime memory 109 | if($memory = $input->getOption('memory_limit')) { 110 | ini_set("memory_limit", $memory); 111 | } 112 | // Input/output dirs 113 | $inputDirectory = $input->getArgument('input_directory'); 114 | $outputDirectory = $input->getArgument('output_directory'); 115 | 116 | if (!empty($outputDirectory)) { 117 | 118 | $output->writeln(sprintf( 119 | 'Copying input directory %s to %s', 120 | $inputDirectory, 121 | $outputDirectory 122 | )); 123 | 124 | $this->copyDir($inputDirectory, $outputDirectory); 125 | 126 | $directory = $outputDirectory; 127 | } else { 128 | $directory = $inputDirectory; 129 | } 130 | 131 | // Strip whitespace? 132 | $stripWhitespace = !$input->getOption('leave_whitespace'); 133 | $ignoreError = !!$input->getOption('ignore_error'); 134 | 135 | // Show every file 136 | $this->getObfuscator()->getEventDispatcher()->addListener( 137 | 'obfuscator.file', 138 | function(FileEvent $event) use ($output, $directory) { 139 | $output->writeln(sprintf( 140 | 'Obfuscating %s', 141 | substr($event->getFile(), strlen($directory)) 142 | )); 143 | } 144 | ); 145 | // Show error processing file 146 | if($ignoreError) { 147 | $this->getObfuscator()->getEventDispatcher()->addListener( 148 | 'obfuscator.file.error', 149 | function(FileErrorEvent $event) use ($output, $directory) { 150 | $output->writeln(sprintf( 151 | 'Error obfuscating %s', 152 | substr($event->getFile(), strlen($directory)) 153 | )); 154 | $output->writeln(sprintf( 155 | 'Parsing error: %s', $event->getErrorMessage() 156 | )); 157 | } 158 | ); 159 | } 160 | 161 | // Actual obfuscation 162 | $this->getObfuscator()->obfuscate($directory, $stripWhitespace, 163 | $ignoreError); 164 | } 165 | 166 | /** 167 | * Get the container 168 | * 169 | * @return Container 170 | */ 171 | public function getContainer() 172 | { 173 | return $this->container; 174 | } 175 | 176 | /** 177 | * Set the container 178 | * 179 | * @param Container $container 180 | * @return ObfuscateCommand 181 | */ 182 | public function setContainer(Container $container) 183 | { 184 | $this->container = $container; 185 | 186 | return $this; 187 | } 188 | 189 | /** 190 | * Get the obfuscator 191 | * 192 | * @return Obfuscator 193 | */ 194 | public function getObfuscator() 195 | { 196 | return $this->getContainer()->getContainer()->get('obfuscator'); 197 | } 198 | 199 | /** 200 | * Copy a directory 201 | * 202 | * @param string $from 203 | * @param string $to 204 | * @return ObfuscateCommand 205 | **/ 206 | private function copyDir($from, $to) 207 | { 208 | // FIXME implement native copy 209 | $output = array(); 210 | $return = 0; 211 | 212 | if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 213 | // WINDOWS 214 | $command = sprintf('XCOPY "%s" "%s" /hievry', $from, $to); 215 | } else { 216 | // *NIX 217 | $command = sprintf('cp -rf %s %s', $from, $to); 218 | } 219 | 220 | exec($command, $output, $return); 221 | 222 | if ($return !== 0) { 223 | throw new \Exception('Could not copy directory'); 224 | } 225 | 226 | return $this; 227 | } 228 | 229 | /** 230 | * Finalize the container 231 | * 232 | * loads any given config file and compiles the container 233 | * 234 | * @return ObfuscateCommand 235 | **/ 236 | private function finalizeContainer(InputInterface $input) 237 | { 238 | // Load config if given 239 | $config = $input->getOption('config'); 240 | if (!empty($config)) { 241 | if (!is_readable($config)) { 242 | throw new InvalidArgumentException(sprintf( 243 | 'Can not read config file "%s"', 244 | $config 245 | )); 246 | } 247 | $this->getContainer()->loadFile($config); 248 | } 249 | 250 | $this->getContainer()->getContainer()->compile(); 251 | 252 | return $this; 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/Naneau/Obfuscator/Container.php: -------------------------------------------------------------------------------- 1 | setContainer(new ContainerBuilder()); 42 | 43 | $this->loadFile(__DIR__ . '/Resources/services.yml'); 44 | } 45 | 46 | /** 47 | * Load a yaml config file 48 | * 49 | * @param string $file 50 | * @return Container 51 | **/ 52 | public function loadFile($file) 53 | { 54 | $loader = new YamlFileLoader( 55 | $this->getContainer(), 56 | new FileLocator(dirname($file)) 57 | ); 58 | $loader->load(basename($file)); 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * Get the container 65 | * 66 | * @return ContainerBuilder 67 | */ 68 | public function getContainer() 69 | { 70 | return $this->container; 71 | } 72 | 73 | /** 74 | * Set the container 75 | * 76 | * @param ContainerBuilder $container 77 | * @return Container 78 | */ 79 | public function setContainer(ContainerBuilder $container) 80 | { 81 | $this->container = $container; 82 | 83 | return $this; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Naneau/Obfuscator/Node/Visitor/ScramblePrivateMethod.php: -------------------------------------------------------------------------------- 1 | resetRenamed() 58 | ->skip($this->variableMethodCallsUsed($nodes)); 59 | 60 | $this->scanMethodDefinitions($nodes); 61 | 62 | return $nodes; 63 | } 64 | 65 | /** 66 | * Check all variable nodes 67 | * 68 | * @param Node $node 69 | * @return void 70 | **/ 71 | public function enterNode(Node $node) 72 | { 73 | if ($this->shouldSkip()) { 74 | return; 75 | } 76 | 77 | // Scramble calls 78 | if ($node instanceof MethodCall || $node instanceof StaticCall) { 79 | 80 | // Node wasn't renamed 81 | if (!$this->isRenamed($node->name)) { 82 | return; 83 | } 84 | 85 | // Scramble usage 86 | return $this->scramble($node); 87 | } 88 | } 89 | 90 | /** 91 | * Recursively scan for method calls and see if variables are used 92 | * 93 | * @param Node[] $nodes 94 | * @return void 95 | **/ 96 | private function variableMethodCallsUsed(array $nodes) 97 | { 98 | foreach ($nodes as $node) { 99 | if ($node instanceof MethodCall && $node->name instanceof Variable) { 100 | // A method call uses a Variable as its name 101 | return true; 102 | } 103 | 104 | // Recurse over child nodes 105 | if (isset($node->stmts) && is_array($node->stmts)) { 106 | $used = $this->variableMethodCallsUsed($node->stmts); 107 | 108 | if ($used) { 109 | return true; 110 | } 111 | } 112 | } 113 | 114 | return false; 115 | } 116 | 117 | /** 118 | * Recursively scan for private method definitions and rename them 119 | * 120 | * @param Node[] $nodes 121 | * @return void 122 | **/ 123 | private function scanMethodDefinitions(array $nodes) 124 | { 125 | foreach ($nodes as $node) { 126 | // Scramble the private method definitions 127 | if ($node instanceof ClassMethod && ($node->type & ClassNode::MODIFIER_PRIVATE)) { 128 | 129 | // Record original name and scramble it 130 | $originalName = $node->name; 131 | $this->scramble($node); 132 | 133 | // Record renaming 134 | $this->renamed($originalName, $node->name); 135 | } 136 | 137 | // Recurse over child nodes 138 | if (isset($node->stmts) && is_array($node->stmts)) { 139 | $this->scanMethodDefinitions($node->stmts); 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Naneau/Obfuscator/Node/Visitor/ScramblePrivateProperty.php: -------------------------------------------------------------------------------- 1 | resetRenamed() 66 | ->scanPropertyDefinitions($nodes); 67 | 68 | return $nodes; 69 | } 70 | 71 | /** 72 | * Check all variable nodes 73 | * 74 | * @param Node $node 75 | * @return void 76 | **/ 77 | public function enterNode(Node $node) 78 | { 79 | if ($node instanceof PropertyFetch) { 80 | 81 | if (!is_string($node->name)) { 82 | return; 83 | } 84 | 85 | if ($this->isRenamed($node->name)) { 86 | $node->name = $this->getNewName($node->name); 87 | return $node; 88 | } 89 | } 90 | } 91 | 92 | /** 93 | * Recursively scan for private method definitions and rename them 94 | * 95 | * @param Node[] $nodes 96 | * @return void 97 | **/ 98 | private function scanPropertyDefinitions(array $nodes) 99 | { 100 | foreach ($nodes as $node) { 101 | // Scramble the private method definitions 102 | if ($node instanceof Property && ($node->type & ClassNode::MODIFIER_PRIVATE)) { 103 | foreach($node->props as $property) { 104 | 105 | // Record original name and scramble it 106 | $originalName = $property->name; 107 | $this->scramble($property); 108 | 109 | // Record renaming 110 | $this->renamed($originalName, $property->name); 111 | } 112 | 113 | } 114 | 115 | // Recurse over child nodes 116 | if (isset($node->stmts) && is_array($node->stmts)) { 117 | $this->scanPropertyDefinitions($node->stmts); 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Naneau/Obfuscator/Node/Visitor/ScrambleUse.php: -------------------------------------------------------------------------------- 1 | resetRenamed(); 66 | 67 | // Find the class node 68 | $this->classNode = $this->findClass($nodes); 69 | 70 | // Scan for use statements 71 | $this->scanUse($nodes); 72 | 73 | return $nodes; 74 | } 75 | 76 | /** 77 | * Check all variable nodes 78 | * 79 | * @param Node $node 80 | * @return void 81 | **/ 82 | public function enterNode(Node $node) 83 | { 84 | // Class statements 85 | if ($node instanceof ClassStatement) { 86 | // Classes that extend another class 87 | if ($node->extends !== null) { 88 | $extends = $node->extends->toString(); 89 | if ($this->isRenamed($extends)) { 90 | $node->extends = new Name($this->getNewName($extends)); 91 | } 92 | } 93 | 94 | // Classes that implement an interface 95 | if ($node->implements !== null && count($node->implements) > 0) { 96 | 97 | $implements = array(); 98 | 99 | foreach($node->implements as $implementsName) { 100 | 101 | // Old name (as string) 102 | $oldName = $implementsName->toString(); 103 | 104 | if ($this->isRenamed($oldName)) { 105 | // If renamed, set new one 106 | $implements[] = new Name($this->getNewName($oldName)); 107 | } else { 108 | // If not renamed, pass old one 109 | $implements[] = $implementsName; 110 | } 111 | } 112 | 113 | $node->implements = $implements; 114 | } 115 | 116 | return $node; 117 | } 118 | 119 | // Param rename 120 | if ($node instanceof Param && $node->type instanceof Name) { 121 | 122 | // Name 123 | $name = $node->type->toString(); 124 | 125 | // Has it been renamed? 126 | if ($this->isRenamed($name)) { 127 | $node->type = $this->getNewName($name); 128 | return $node; 129 | } 130 | } 131 | 132 | // Static call or constant lookup on class 133 | if ( 134 | $node instanceof ClassConstFetch 135 | || $node instanceof StaticCall 136 | || $node instanceof StaticPropertyFetch 137 | || $node instanceof StaticVar 138 | || $node instanceof NewExpression 139 | || $node instanceof InstanceOfExpression 140 | ) { 141 | 142 | // We need to be in a class for this to work 143 | if (empty($this->classNode)) { 144 | return; 145 | } 146 | 147 | // We need a name 148 | if (!($node->class instanceof Name)) { 149 | return; 150 | } 151 | 152 | // Class name 153 | $name = $node->class->toString(); 154 | 155 | if ($name === $this->classNode->name) { 156 | return; 157 | } 158 | 159 | // Has it been renamed? 160 | if ($this->isRenamed($name)) { 161 | $node->class = new Name($this->getNewName($name)); 162 | return $node; 163 | } 164 | } 165 | } 166 | 167 | /** 168 | * Scramble at use statements 169 | * 170 | * @param Node[] $nodes 171 | * @return void 172 | **/ 173 | private function scanUse(array $nodes) 174 | { 175 | foreach ($nodes as $node) { 176 | // Scramble the private method definitions 177 | if ($node instanceof UseStatement) { 178 | foreach($node->uses as $useNode) { 179 | 180 | // Record original name and scramble it 181 | $originalName = $useNode->name->toString(); 182 | 183 | // Prefix all classes with underscores, but don't modify them further 184 | $rename = 185 | strpos($originalName, '_') === false 186 | && 187 | count($useNode->name->parts) > 1; 188 | 189 | if (!$rename) { 190 | $useNode->name = new Name( 191 | '\\' . $useNode->name 192 | ); 193 | 194 | continue; 195 | } 196 | 197 | // Scramble into new use name 198 | $newName = $this->scrambleString( 199 | $originalName 200 | . '-' 201 | . $useNode->alias 202 | ); 203 | 204 | // Record renaming of full class 205 | $this->renamed($originalName, $newName); 206 | 207 | // Record renaming of alias 208 | $this->renamed($useNode->alias, $newName); 209 | 210 | // Set the new alias 211 | $useNode->alias = $newName; 212 | } 213 | } 214 | 215 | // Recurse over child nodes 216 | if (isset($node->stmts) && is_array($node->stmts)) { 217 | $this->scanUse($node->stmts); 218 | } 219 | } 220 | } 221 | 222 | /** 223 | * Find (the first) class node in a set of nodes 224 | * 225 | * @param array $nodes 226 | * @return ClassStatement|bool returns falls if no class can be found 227 | **/ 228 | private function findClass(array $nodes) 229 | { 230 | foreach($nodes as $node) { 231 | if ($node instanceof ClassStatement) { 232 | return $node; 233 | } 234 | 235 | if (isset($node->stmts) && is_array($node->stmts)) { 236 | $class = $this->findClass($node->stmts); 237 | 238 | if ($class instanceof ClassStatement) { 239 | return $class; 240 | } 241 | } 242 | } 243 | 244 | return false; 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/Naneau/Obfuscator/Node/Visitor/ScrambleVariable.php: -------------------------------------------------------------------------------- 1 | setIgnore(array( 48 | 'this', '_SERVER', '_POST', '_GET', '_REQUEST', '_COOKIE', 49 | '_SESSION', '_ENV', '_FILES' 50 | )); 51 | } 52 | 53 | /** 54 | * Check all variable nodes 55 | * 56 | * @param Node $node 57 | * @return void 58 | **/ 59 | public function enterNode(Node $node) 60 | { 61 | // Function param or variable use 62 | if ($node instanceof Param || $node instanceof StaticVar || $node instanceof Variable) { 63 | return $this->scramble($node); 64 | } 65 | 66 | // try {} catch () {} 67 | if ($node instanceof CatchStatement) { 68 | return $this->scramble($node, 'var'); 69 | } 70 | 71 | // Function() use ($x, $y) {} 72 | if ($node instanceof ClosureUse) { 73 | return $this->scramble($node, 'var'); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Naneau/Obfuscator/Node/Visitor/Scrambler.php: -------------------------------------------------------------------------------- 1 | setScrambler($scrambler); 52 | } 53 | 54 | /** 55 | * Scramble a property of a node 56 | * 57 | * @param Node $node 58 | * @param string $var property to scramble 59 | * @return Node 60 | **/ 61 | protected function scramble(Node $node, $var = 'name') 62 | { 63 | // String/value to scramble 64 | $toScramble = $node->$var; 65 | 66 | // We ignore to scramble if it's not string (ex: a variable variable name) 67 | if (!is_string($toScramble)) { 68 | return; 69 | } 70 | 71 | // Make sure there's something to scramble 72 | if (strlen($toScramble) === 0) { 73 | throw new InvalidArgumentException(sprintf( 74 | '"%s" value empty for node, can not scramble', 75 | $var 76 | )); 77 | } 78 | 79 | // Should we ignore it? 80 | if (in_array($toScramble, $this->getIgnore())) { 81 | return $node; 82 | } 83 | 84 | // Prefix with 'p' so we dont' start with an number 85 | $node->$var = $this->scrambleString($toScramble); 86 | 87 | // Return the node 88 | return $node; 89 | } 90 | 91 | /** 92 | * Scramble a string 93 | * 94 | * @param string $string 95 | * @return string 96 | **/ 97 | protected function scrambleString($string) 98 | { 99 | return 's' . $this->getScrambler()->scramble($string); 100 | } 101 | 102 | /** 103 | * Get the string scrambler 104 | * 105 | * @return StringScrambler 106 | */ 107 | public function getScrambler() 108 | { 109 | return $this->scrambler; 110 | } 111 | 112 | /** 113 | * Set the string scrambler 114 | * 115 | * @param StringScrambler $scrambler 116 | * @return RenameParameter 117 | */ 118 | public function setScrambler(StringScrambler $scrambler) 119 | { 120 | $this->scrambler = $scrambler; 121 | 122 | return $this; 123 | } 124 | 125 | /** 126 | * Get variables to ignore 127 | * 128 | * @return string[] 129 | */ 130 | public function getIgnore() 131 | { 132 | return $this->ignore; 133 | } 134 | 135 | /** 136 | * Set variables to ignore 137 | * 138 | * @param string[] $ignore 139 | * @return parent 140 | */ 141 | public function setIgnore(array $ignore) 142 | { 143 | $this->ignore = $ignore; 144 | 145 | return $this; 146 | } 147 | 148 | /** 149 | * Add a variable name to ignore 150 | * 151 | * @param string|string[] $ignore 152 | * @return RenameParameterVisitor 153 | **/ 154 | public function addIgnore($ignore) 155 | { 156 | if (is_string($ignore)) { 157 | $this->ignore = array_merge($this->ignore, array($ignore)); 158 | } else if (is_array($ignore)) { 159 | $this->ignore = array_merge($this->ignore, $ignore); 160 | } else { 161 | throw new InvalidArgumentException('Invalid ignore type passed'); 162 | } 163 | return $this; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/Naneau/Obfuscator/Node/Visitor/SkipTrait.php: -------------------------------------------------------------------------------- 1 | skip = $skip; 38 | 39 | return $this; 40 | } 41 | 42 | /** 43 | * Should we skip processing? 44 | * 45 | * @return bool 46 | **/ 47 | protected function shouldSkip() 48 | { 49 | return $this->skip; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Naneau/Obfuscator/Node/Visitor/TrackingRenamerTrait.php: -------------------------------------------------------------------------------- 1 | renamed[$method] = $newName; 39 | 40 | return $this; 41 | } 42 | 43 | /** 44 | * Has a method been renamed? 45 | * 46 | * @param string $method 47 | * @return bool 48 | **/ 49 | protected function isRenamed($method) 50 | { 51 | if (empty($method)) { 52 | return false; 53 | } 54 | 55 | // Ignore variable functions 56 | if (!is_string($method)) { 57 | return false; 58 | } 59 | 60 | return isset($this->renamed[$method]); 61 | } 62 | 63 | /** 64 | * Get new name of a method 65 | * 66 | * @param string $method 67 | * @return string 68 | **/ 69 | protected function getNewName($method) 70 | { 71 | if (!$this->isRenamed($method)) { 72 | throw new InvalidArgumentException(sprintf( 73 | '"%s" was not renamed', 74 | $method 75 | )); 76 | } 77 | 78 | return $this->renamed[$method]; 79 | } 80 | 81 | /** 82 | * Reset renamed list 83 | * 84 | * @return SkipTrait 85 | **/ 86 | protected function resetRenamed() 87 | { 88 | $this->renamed = array(); 89 | 90 | return $this; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Naneau/Obfuscator/Obfuscator.php: -------------------------------------------------------------------------------- 1 | getFiles($directory) as $file) { 86 | $this->getEventDispatcher()->dispatch( 87 | 'obfuscator.file', 88 | new FileEvent($file) 89 | ); 90 | 91 | // Write obfuscated source 92 | file_put_contents($file, $this->obfuscateFileContents($file, 93 | $ignoreError)); 94 | 95 | // Strip whitespace if required 96 | if ($stripWhitespace) { 97 | file_put_contents($file, php_strip_whitespace($file)); 98 | } 99 | } 100 | } 101 | 102 | /** 103 | * Get the parser 104 | * 105 | * @return Parser 106 | */ 107 | public function getParser() 108 | { 109 | return $this->parser; 110 | } 111 | 112 | /** 113 | * Set the parser 114 | * 115 | * @param Parser $parser 116 | * @return Obfuscator 117 | */ 118 | public function setParser(Parser $parser) 119 | { 120 | $this->parser = $parser; 121 | 122 | return $this; 123 | } 124 | 125 | /** 126 | * Get the node traverser 127 | * 128 | * @return NodeTraverser 129 | */ 130 | public function getTraverser() 131 | { 132 | return $this->traverser; 133 | } 134 | 135 | /** 136 | * Set the node traverser 137 | * 138 | * @param NodeTraverser $traverser 139 | * @return Obfuscator 140 | */ 141 | public function setTraverser(NodeTraverser $traverser) 142 | { 143 | $this->traverser = $traverser; 144 | 145 | return $this; 146 | } 147 | 148 | /** 149 | * Get the "pretty" printer 150 | * 151 | * @return PrettyPrinter 152 | */ 153 | public function getPrettyPrinter() 154 | { 155 | return $this->prettyPrinter; 156 | } 157 | 158 | /** 159 | * Set the "pretty" printer 160 | * 161 | * @param PrettyPrinter $prettyPrinter 162 | * @return Obfuscator 163 | */ 164 | public function setPrettyPrinter(PrettyPrinter $prettyPrinter) 165 | { 166 | $this->prettyPrinter = $prettyPrinter; 167 | 168 | return $this; 169 | } 170 | 171 | /** 172 | * Get the event dispatcher 173 | * 174 | * @return EventDispatcher 175 | */ 176 | public function getEventDispatcher() 177 | { 178 | return $this->eventDispatcher; 179 | } 180 | 181 | /** 182 | * Set the event dispatcher 183 | * 184 | * @param EventDispatcher $eventDispatcher 185 | * @return Obfuscator 186 | */ 187 | public function setEventDispatcher(EventDispatcher $eventDispatcher) 188 | { 189 | $this->eventDispatcher = $eventDispatcher; 190 | 191 | return $this; 192 | } 193 | 194 | /** 195 | * Get the regex for file inclusion 196 | * 197 | * @return string 198 | */ 199 | public function getFileRegex() 200 | { 201 | return $this->fileRegex; 202 | } 203 | 204 | /** 205 | * Set the regex for file inclusion 206 | * 207 | * @param string $fileRegex 208 | * @return Obfuscator 209 | */ 210 | public function setFileRegex($fileRegex) 211 | { 212 | $this->fileRegex = $fileRegex; 213 | 214 | return $this; 215 | } 216 | 217 | /** 218 | * Get the file list 219 | * 220 | * @return SplFileInfo 221 | **/ 222 | private function getFiles($directory) 223 | { 224 | return new RegexIterator( 225 | new RecursiveIteratorIterator( 226 | new RecursiveDirectoryIterator($directory) 227 | ), 228 | $this->getFileRegex() 229 | ); 230 | } 231 | 232 | /** 233 | * Obfuscate a single file's contents 234 | * 235 | * @param string $file 236 | * @param boolean $ignoreError if true, do not throw an Error and 237 | * exit, but continue with next file 238 | * @return string obfuscated contents 239 | **/ 240 | private function obfuscateFileContents($file, $ignoreError) 241 | { 242 | try { 243 | // Input code 244 | $source = php_strip_whitespace($file); 245 | 246 | // Get AST 247 | $ast = $this->getTraverser()->traverse( 248 | $this->getParser()->parse($source) 249 | ); 250 | 251 | return "getPrettyPrinter()->prettyPrint($ast); 252 | } catch (Exception $e) { 253 | if($ignoreError) { 254 | sprintf('Could not parse file "%s"', $file); 255 | $this->getEventDispatcher()->dispatch( 256 | 'obfuscator.file.error', 257 | new FileErrorEvent($file, $e->getMessage()) 258 | ); 259 | } else { 260 | throw new Exception( 261 | sprintf('Could not parse file "%s"', $file), 262 | null, 263 | $e 264 | ); 265 | } 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/Naneau/Obfuscator/Obfuscator/Event/File.php: -------------------------------------------------------------------------------- 1 | setFile($file); 40 | } 41 | 42 | /** 43 | * Get the file 44 | * 45 | * @return string 46 | */ 47 | public function getFile() 48 | { 49 | return $this->file; 50 | } 51 | 52 | /** 53 | * Set the file 54 | * 55 | * @param string $file 56 | * @return parent 57 | */ 58 | public function setFile($file) 59 | { 60 | $this->file = $file; 61 | 62 | return $this; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Naneau/Obfuscator/Obfuscator/Event/FileError.php: -------------------------------------------------------------------------------- 1 | errorMessage = $errorMessage; 41 | } 42 | 43 | /** 44 | * Get the error message 45 | * 46 | * @return string 47 | */ 48 | public function getErrorMessage() 49 | { 50 | return $this->errorMessage; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Naneau/Obfuscator/Resources/services.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | 3 | # Ignore lists 4 | obfuscator.scramble_variable.ignore: [] 5 | obfuscator.scramble_private_method.ignore: [] 6 | obfuscator.scramble_private_property.ignore: [] 7 | obfuscator.scramble_use.ignore: [] 8 | 9 | # Files to parse 10 | obfuscator.files: "/\.php$/" 11 | 12 | services: 13 | 14 | # Obfuscator 15 | obfuscator: 16 | class: Naneau\Obfuscator\Obfuscator 17 | calls: 18 | - [setParser, [@obfuscator.parser]] 19 | - [setTraverser, [@obfuscator.node_traverser]] 20 | - [setPrettyPrinter, [@obfuscator.pretty_printer]] 21 | - [setEventDispatcher, [@obfuscator.event_dispatcher]] 22 | - [setFileRegex, [%obfuscator.files%]] 23 | 24 | # String scrambler 25 | obfuscator.scrambler: 26 | class: Naneau\Obfuscator\StringScrambler 27 | 28 | # Node traverser 29 | obfuscator.node_traverser: 30 | class: PhpParser\NodeTraverser 31 | calls: 32 | - [addVisitor, [@obfuscator.node_visitor.scramble_variable]] 33 | - [addVisitor, [@obfuscator.node_visitor.scramble_private_method]] 34 | 35 | # Variable scrambler 36 | obfuscator.node_visitor.scramble_variable: 37 | class: Naneau\Obfuscator\Node\Visitor\ScrambleVariable 38 | arguments: 39 | - @obfuscator.scrambler 40 | calls: 41 | - [addIgnore, [%obfuscator.scramble_variable.ignore%]] 42 | 43 | # Scramble private methods 44 | obfuscator.node_visitor.scramble_private_method: 45 | class: Naneau\Obfuscator\Node\Visitor\ScramblePrivateMethod 46 | arguments: 47 | - @obfuscator.scrambler 48 | calls: 49 | - [addIgnore, [%obfuscator.scramble_private_method.ignore%]] 50 | 51 | # Scramble private properties 52 | obfuscator.node_visitor.scramble_private_property: 53 | class: Naneau\Obfuscator\Node\Visitor\ScramblePrivateProperty 54 | arguments: 55 | - @obfuscator.scrambler 56 | calls: 57 | - [addIgnore, [%obfuscator.scramble_private_property.ignore%]] 58 | 59 | # Scramble use statements 60 | obfuscator.node_visitor.scramble_use: 61 | class: Naneau\Obfuscator\Node\Visitor\ScrambleUse 62 | arguments: 63 | - @obfuscator.scrambler 64 | calls: 65 | - [addIgnore, [%obfuscator.scramble_use.ignore%]] 66 | 67 | # Name resolver (needed before scramble_use) 68 | obfuscator.node_visitor.name_resolver: 69 | class: PhpParser\NodeVisitor\NameResolver 70 | 71 | # Parser 72 | obfuscator.parser: 73 | class: PhpParser\Parser 74 | arguments: 75 | - @obfuscator.lexer 76 | 77 | # Lexer 78 | obfuscator.lexer: 79 | class: PhpParser\Lexer 80 | 81 | # Pretty printer 82 | obfuscator.pretty_printer: 83 | class: PhpParser\PrettyPrinter\Standard 84 | 85 | # Event dispatcher 86 | obfuscator.event_dispatcher: 87 | class: Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher 88 | arguments: 89 | - @service_container 90 | -------------------------------------------------------------------------------- /src/Naneau/Obfuscator/StringScrambler.php: -------------------------------------------------------------------------------- 1 | setSalt( 40 | md5(microtime(true) . rand(0,1)) 41 | ); 42 | } else { 43 | $this->setSalt($salt); 44 | } 45 | } 46 | 47 | /** 48 | * Scramble a string 49 | * 50 | * @param string $string 51 | * @return string 52 | **/ 53 | public function scramble($string) 54 | { 55 | return 'p' . substr(md5($string . $this->getSalt()), 0, 6); 56 | } 57 | 58 | /** 59 | * Get the salt 60 | * 61 | * @return string 62 | */ 63 | public function getSalt() 64 | { 65 | return $this->salt; 66 | } 67 | 68 | /** 69 | * Set the salt 70 | * 71 | * @param string $salt 72 | * @return StringScrambler 73 | */ 74 | public function setSalt($salt) 75 | { 76 | $this->salt = $salt; 77 | 78 | return $this; 79 | } 80 | } 81 | --------------------------------------------------------------------------------