diff --git a/composer.json b/composer.json index 4efdca6..6c6753b 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ ], "require": { - "nikic/php-parser": "~1@dev", + "nikic/php-parser": "~3@dev", "symfony/console": "~2.5", "symfony/dependency-injection": "~2.5", "symfony/config": "~2.5", diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..48e0074 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/Naneau/Obfuscator/Node/Visitor/ScramblePrivateMethod.php b/src/Naneau/Obfuscator/Node/Visitor/ScramblePrivateMethod.php index 257c276..2435f45 100644 --- a/src/Naneau/Obfuscator/Node/Visitor/ScramblePrivateMethod.php +++ b/src/Naneau/Obfuscator/Node/Visitor/ScramblePrivateMethod.php @@ -57,7 +57,9 @@ public function beforeTraverse(array $nodes) ->resetRenamed() ->skip($this->variableMethodCallsUsed($nodes)); - $this->scanMethodDefinitions($nodes); + if (!$this->shouldSkip()) { + $this->scanMethodDefinitions($nodes); + } return $nodes; } @@ -75,8 +77,7 @@ public function enterNode(Node $node) } // Scramble calls - if ($node instanceof MethodCall || $node instanceof StaticCall) { - + if (($node instanceof MethodCall && $node->var->name === 'this') || ($node instanceof StaticCall && $node->class instanceof Node\Name && $node->class->toString() === 'self')) { // Node wasn't renamed if (!$this->isRenamed($node->name)) { return; @@ -96,7 +97,7 @@ public function enterNode(Node $node) private function variableMethodCallsUsed(array $nodes) { foreach ($nodes as $node) { - if ($node instanceof MethodCall && $node->name instanceof Variable) { + if ($node instanceof MethodCall && $node->name instanceof Variable && $node->var->name === "this") { // A method call uses a Variable as its name return true; } diff --git a/src/Naneau/Obfuscator/Node/Visitor/ScramblePrivateProperty.php b/src/Naneau/Obfuscator/Node/Visitor/ScramblePrivateProperty.php index 86c77cf..c101788 100644 --- a/src/Naneau/Obfuscator/Node/Visitor/ScramblePrivateProperty.php +++ b/src/Naneau/Obfuscator/Node/Visitor/ScramblePrivateProperty.php @@ -78,7 +78,18 @@ public function enterNode(Node $node) { if ($node instanceof PropertyFetch) { - if (!is_string($node->name)) { + if (!is_string($node->name) || $node->var->name !== "this") { + return; + } + + if ($this->isRenamed($node->name)) { + $node->name = $this->getNewName($node->name); + return $node; + } + } + + if ($node instanceof Node\Expr\StaticPropertyFetch) { + if ((string)$node->class !== "self") { return; } diff --git a/src/Naneau/Obfuscator/Node/Visitor/ScrambleUse.php b/src/Naneau/Obfuscator/Node/Visitor/ScrambleUse.php index 6a3e074..dbb935e 100644 --- a/src/Naneau/Obfuscator/Node/Visitor/ScrambleUse.php +++ b/src/Naneau/Obfuscator/Node/Visitor/ScrambleUse.php @@ -88,13 +88,16 @@ public function enterNode(Node $node) $extends = $node->extends->toString(); if ($this->isRenamed($extends)) { $node->extends = new Name($this->getNewName($extends)); + } elseif ($this->isRenamed($node->extends->getFirst())) { + reset($node->extends->parts); + $node->extends->parts[key($node->extends->parts)] = $this->getNewName($node->extends->getFirst()); } } // Classes that implement an interface if ($node->implements !== null && count($node->implements) > 0) { - $implements = array(); + $implements = []; foreach($node->implements as $implementsName) { @@ -104,6 +107,10 @@ public function enterNode(Node $node) if ($this->isRenamed($oldName)) { // If renamed, set new one $implements[] = new Name($this->getNewName($oldName)); + } elseif ($this->isRenamed($implementsName->getFirst())) { + reset($implementsName->parts); + $implementsName->parts[key($implementsName->parts)] = $this->getNewName($implementsName->getFirst()); + $implements[] = $implementsName; } else { // If not renamed, pass old one $implements[] = $implementsName; @@ -116,9 +123,56 @@ public function enterNode(Node $node) return $node; } + if ($node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Expr\Closure) { + + if ($node->returnType instanceof Name) { + // Name + $name = $node->returnType->toString(); + + // Has it been renamed? + if ($this->isRenamed($name)) { + $node->returnType = $this->getNewName($name); + return $node; + } elseif ($this->isRenamed($node->returnType->getFirst())) { + reset($node->returnType->parts); + $node->returnType->parts[key($node->returnType->parts)] = $this->getNewName($node->returnType->getFirst()); + return $node; + } + } + + if ($node->returnType instanceof Node\NullableType && $node->returnType->type instanceof Name) { + // Name + $name = $node->returnType->type->toString(); + + // Has it been renamed? + if ($this->isRenamed($name)) { + $node->returnType->type = $this->getNewName($name); + return $node; + } elseif ($this->isRenamed($node->returnType->type->getFirst())) { + reset($node->returnType->type->parts); + $node->returnType->type->parts[key($node->returnType->type->parts)] = $this->getNewName($node->returnType->type->getFirst()); + return $node; + } + } + } + + if ($node instanceof Param && $node->type instanceof Node\NullableType && $node->type->type instanceof Name) { + // Name + $name = $node->type->type->toString(); + + // Has it been renamed? + if ($this->isRenamed($name)) { + $node->type->type = $this->getNewName($name); + return $node; + } elseif ($this->isRenamed($node->type->type->getFirst())) { + reset($node->type->type->parts); + $node->type->type->parts[key($node->type->type->parts)] = $this->getNewName($node->type->type->getFirst()); + return $node; + } + } + // Param rename if ($node instanceof Param && $node->type instanceof Name) { - // Name $name = $node->type->toString(); @@ -126,6 +180,10 @@ public function enterNode(Node $node) if ($this->isRenamed($name)) { $node->type = $this->getNewName($name); return $node; + } elseif ($this->isRenamed($node->type->getFirst())) { + reset($node->type->parts); + $node->type->parts[key($node->type->parts)] = $this->getNewName($node->type->getFirst()); + return $node; } } @@ -138,7 +196,6 @@ public function enterNode(Node $node) || $node instanceof NewExpression || $node instanceof InstanceOfExpression ) { - // We need to be in a class for this to work if (empty($this->classNode)) { return; @@ -160,6 +217,12 @@ public function enterNode(Node $node) if ($this->isRenamed($name)) { $node->class = new Name($this->getNewName($name)); return $node; + } else { + if ($this->isRenamed($node->class->getFirst())) { + reset($node->class->parts); + $node->class->parts[key($node->class->parts)] = $this->getNewName($node->class->getFirst()); + return $node; + } } } } diff --git a/src/Naneau/Obfuscator/Resources/services.yml b/src/Naneau/Obfuscator/Resources/services.yml index bb036e9..8d0b86a 100644 --- a/src/Naneau/Obfuscator/Resources/services.yml +++ b/src/Naneau/Obfuscator/Resources/services.yml @@ -70,7 +70,7 @@ services: # Parser obfuscator.parser: - class: PhpParser\Parser + class: PhpParser\Parser\Php7 arguments: - @obfuscator.lexer diff --git a/tests/Base.php b/tests/Base.php new file mode 100644 index 0000000..e8103da --- /dev/null +++ b/tests/Base.php @@ -0,0 +1,7 @@ +fail("{$expectedFileName} not found"); + } + $this->assertEquals(file_get_contents(__DIR__ . self::EXPECTED_PATH . "/" . $expectedFileName), file_get_contents(__DIR__ . self::AFTER_PATH . "/" . $expectedFileName)); + } + } +} \ No newline at end of file diff --git a/tests/before/Functions.php b/tests/before/Functions.php new file mode 100644 index 0000000..5dd10f1 --- /dev/null +++ b/tests/before/Functions.php @@ -0,0 +1,21 @@ +_privateProperty = parent::$_protectedProperty; + } + + public function publicMethod() { + parent::publicMethod(); + echo "This is public method of second class"; + $this->_privateProperty = parent::$publicProperty; + } + + static public function anotherPublicMethod() { + } +} + +class ThirdClass { + + private $publicProperty; + + static private function anotherPublicMethod() { + + } + + public function __construct(SecondClass $secondObject) { + $secondObject->publicMethod(); + $secondObject::anotherPublicMethod(); + $secondObject->publicProperty = 'test'; + } + + private function publicMethod() { + echo 'test'; + } + + protected function someFunc() { + $this->publicProperty = 'test'; + $this->publicMethod(); + self::anotherPublicMethod(); + } +} \ No newline at end of file diff --git a/tests/before/Namespaces.php b/tests/before/Namespaces.php new file mode 100644 index 0000000..3c2e2f7 --- /dev/null +++ b/tests/before/Namespaces.php @@ -0,0 +1,43 @@ +_objectA = new classC(); + $this->_objectB = new namespaceDAlias\classD(); + } +} + +class classE extends namespaceDAlias\classD implements namespaceDAlias\interfaceA { + + public function method(?namespaceDAlias\classD $objectD) : ?namespaceDAlias\classD { + $func = function () : ?namespaceDAlias\classD { + + }; + } +} \ No newline at end of file diff --git a/tests/before/SimpleClass.php b/tests/before/SimpleClass.php new file mode 100644 index 0000000..b45d53f --- /dev/null +++ b/tests/before/SimpleClass.php @@ -0,0 +1,47 @@ +_privateProperty = $localVar; + $this->_protectedProperty = $localVar; + $this->publicProperty = $localVar; + self::$publicStaticProperty = "test"; + self::$protectedStaticProperty = "test"; + self::$_privateStaticProperty = "test"; + } + + protected function _protectedMethod() { + $localVar = "test"; + $this->_privateProperty = $localVar; + $this->_protectedProperty = $localVar; + $this->publicProperty = $localVar; + } + + public function publicMethod() { + $localVar = "test"; + $this->_privateProperty = $localVar; + $this->_protectedProperty = $localVar; + $this->publicProperty = $localVar; + $this->_protectedMethod(); + $this->_privateMethod(); + $dt = new \DateTime(); + $dtMethodName = "getTimestamp"; + $dt->$dtMethodName(); + } +} + +$simpleObject = new SimpleClass(); +$simpleObject->publicMethod(); \ No newline at end of file diff --git a/tests/config.yml b/tests/config.yml new file mode 100644 index 0000000..7d94a89 --- /dev/null +++ b/tests/config.yml @@ -0,0 +1,16 @@ +services: + + # String scrambler + obfuscator.scrambler: + class: Naneau\Obfuscator\StringScrambler + arguments: + - 'salt' + + # Node traverser + obfuscator.node_traverser: + class: PhpParser\NodeTraverser + calls: + - [addVisitor, [@obfuscator.node_visitor.scramble_variable]] + - [addVisitor, [@obfuscator.node_visitor.scramble_private_method]] + - [addVisitor, [@obfuscator.node_visitor.scramble_private_property]] + - [addVisitor, [@obfuscator.node_visitor.scramble_use]] \ No newline at end of file diff --git a/tests/expected/Functions.php b/tests/expected/Functions.php new file mode 100644 index 0000000..17f923e --- /dev/null +++ b/tests/expected/Functions.php @@ -0,0 +1,2 @@ +sp8839d9 = parent::$_protectedProperty; } public function publicMethod() { parent::publicMethod(); echo "This is public method of second class"; $this->sp8839d9 = parent::$publicProperty; } public static function anotherPublicMethod() { } } class ThirdClass { private $spa36ab6; private static function spe81a11() { } public function __construct(SecondClass $spb91639) { $spb91639->publicMethod(); $spb91639::anotherPublicMethod(); $spb91639->publicProperty = 'test'; } private function sp70ab23() { echo 'test'; } protected function someFunc() { $this->spa36ab6 = 'test'; $this->sp70ab23(); self::spe81a11(); } } \ No newline at end of file diff --git a/tests/expected/Namespaces.php b/tests/expected/Namespaces.php new file mode 100644 index 0000000..f7cf93d --- /dev/null +++ b/tests/expected/Namespaces.php @@ -0,0 +1,2 @@ +spa26210 = new spf0f507(); $this->sp2e1034 = new sp63627e\classD(); } } class classE extends sp63627e\classD implements sp63627e\interfaceA { public function method(?sp63627e\classD $spc1ac55) : ?sp63627e\classD { $sp7009a4 = function () : ?sp63627e\classD { }; } } \ No newline at end of file diff --git a/tests/expected/SimpleClass.php b/tests/expected/SimpleClass.php new file mode 100644 index 0000000..7c0a81a --- /dev/null +++ b/tests/expected/SimpleClass.php @@ -0,0 +1,2 @@ +sp8839d9 = $spd8dce8; $this->_protectedProperty = $spd8dce8; $this->publicProperty = $spd8dce8; self::$publicStaticProperty = "test"; self::$protectedStaticProperty = "test"; self::$sp39db2b = "test"; } protected function _protectedMethod() { $spd8dce8 = "test"; $this->sp8839d9 = $spd8dce8; $this->_protectedProperty = $spd8dce8; $this->publicProperty = $spd8dce8; } public function publicMethod() { $spd8dce8 = "test"; $this->sp8839d9 = $spd8dce8; $this->_protectedProperty = $spd8dce8; $this->publicProperty = $spd8dce8; $this->_protectedMethod(); $this->sp51fa3f(); $spce0ae5 = new \DateTime(); $sp167220 = "getTimestamp"; $spce0ae5->{$sp167220}(); } } $sp5de0e2 = new SimpleClass(); $sp5de0e2->publicMethod(); \ No newline at end of file diff --git a/tests/phpunit.php b/tests/phpunit.php new file mode 100644 index 0000000..6515b5c --- /dev/null +++ b/tests/phpunit.php @@ -0,0 +1,9 @@ +