Skip to content

Instantly share code, notes, and snippets.

@mermshaus
Created December 7, 2012 17:12
Show Gist options
  • Save mermshaus/4234769 to your computer and use it in GitHub Desktop.
Save mermshaus/4234769 to your computer and use it in GitHub Desktop.
PHP source code obfuscator
<?php
namespace org\ermshaus;
use Exception;
/**
* Obfuscates PHP source code
*
* *** The code is by no means feature complete. ***
*
* Example input:
*
* <?php
*
* namespace org\example;
*
* // A class
* class App {
* public function __construct() { $this->sayHello(); }
* public function sayHello() { echo "Hello World!\n"; }
* }
*
* new App();
*
* Obfuscated output (line breaks added):
*
* <?php
* namespace _0\_1;class _0{public function __construct(){$this->_1();}publi
* c function _1(){echo"\110\145\154\154\157\40\127\157\162\154\144\41\12";}
* }new _0();
*
* Obfuscate a file:
*
* $ php -f obfuscate.php < file.php > file-obfuscated.php
*
* Obfuscate this file (obfuscate.php) and then test the obfuscated obfuscator:
*
* $ php -f obfuscate.php < obfuscate.php > obfuscate
* $ echo '<?php echo "Hello world!\n";' | php -f obfuscate | php
*
* @todo $funcmap should not be used to hold functions as well as methods and
* instance variables.
*
* @copyright (c) 2012, Marc Ermshaus <marc@ermshaus.org>
* @license http://www.gnu.org/licenses/gpl-3.0.en.html
* GNU General Public License v3
* @version 2013-06-02
*/
class Obfuscator
{
/**
* The obfuscated string will be assembled in this variable
*
* @var string
*/
protected $out;
// Arrays used for alias mappings
protected $varmap;
protected $funcmap;
protected $namespacemap;
// Parser states
protected $stateNextVariableIsInstanceVariable;
protected $stateNextStringIsClassName;
protected $stateNextStringIsFunctionName;
protected $stateNextStringIsNamespaceName;
protected $stateNextStringIsObjectMember;
// Data
protected $reservedVariableNames = array(
'$this'
);
protected $reservedConstantNames = array(
'false',
'true',
'null',
'STDERR',
'STDIN',
'STDOUT',
);
protected $reservedFunctionNames = array(
'__call',
'__callStactic',
'__clone',
'__construct',
'__destruct',
'__get',
'__invoke',
'__isset',
'__set',
'__set_state',
'__sleep',
'__toString',
'__unset',
'__wakeup'
);
// Configuration settings
protected $removeWhiteSpace = false;
protected $removeComments = false;
protected $whiteSpaceChar = ' ';
protected $obfuscateStrings = true;
protected $obfuscateStringsMode = 'oct'; // oct, hex
protected $replaceConstants = true;
protected $replaceNamespaces = true;
protected $variableNamePattern = '_%s';
protected $functionNamePattern = '_%s';
protected $namespaceNamePattern = '_%s';
/**
*
*/
public function __construct()
{
$this->reset();
$newNamespaceMap = new AliasMap();
$newNamespaceMap->addAlias('test');
if ($newNamespaceMap->hasAlias('test')) {
$x = $newNamespaceMap->getAlias('test');
}
}
/**
*
*/
protected function reset()
{
$this->varmap = array();
$this->funcmap = array();
$this->namespacemap = array();
$this->out = '';
$this->stateNextVariableIsInstanceVariable = false;
$this->stateNextStringIsClassName = false;
$this->stateNextStringIsFunctionName = false;
$this->stateNextStringIsNamespaceName = false;
$this->stateNextStringIsObjectMember = false;
}
/**
*
* @param string $s
* @return string
*/
protected function unescapeString($s)
{
ob_start();
eval('echo ' . $s . ';');
return ob_get_clean();
}
/**
* Appends whitespace to end of working string if necessary
*
* @param string $s
* @return string
*/
protected function appendWhiteSpaceIfNecessary($s)
{
if (
1 === preg_match('/^\w/u', $s)
&& 1 === preg_match('/\w(?!\n)$/u', $this->out)
) {
$s = $this->whiteSpaceChar . $s;
}
return $s;
}
/**
*
* @param string $s
* @return string
*/
protected function handleEncapsedString($s)
{
if (!$this->obfuscateStrings) {
return $s;
}
$ret = '';
$unescaped = $this->unescapeString($s);
$chars = str_split($unescaped);
foreach ($chars as $c) {
if ($c === '') {
continue;
}
if ($this->obfuscateStringsMode === 'oct') {
$ret .= "\\" . decoct(ord($c));
} else {
$ret .= "\\x" . sprintf('%02x', ord($c));
}
}
$ret = '"' . $ret . '"';
return $ret;
}
protected function getFuncName($s)
{
if (!array_key_exists($s, $this->funcmap)) {
$name = sprintf($this->functionNamePattern, count($this->funcmap));
$this->funcmap[$s] = $name;
}
return $this->funcmap[$s];
}
protected function hasFuncName($s)
{
return array_key_exists($s, $this->funcmap);
}
protected function getNamespaceName($s)
{
if (!$this->replaceNamespaces) {
return $s;
}
if (!array_key_exists($s, $this->namespacemap)) {
$name = sprintf(
$this->namespaceNamePattern,
count($this->namespacemap)
);
$this->namespacemap[$s] = $name;
}
return $this->namespacemap[$s];
}
/**
*
* @param string $s
* @return string
*/
protected function handleString($s)
{
if ($this->stateNextStringIsNamespaceName) {
return $this->getNamespaceName($s);
}
$ret = '';
if ($this->stateNextStringIsFunctionName) {
if (in_array($s, $this->reservedFunctionNames)) {
$ret = $s;
} else {
$ret = $this->getFuncName($s);
}
$this->stateNextStringIsFunctionName = false;
} elseif ($this->stateNextStringIsClassName) {
$ret = $this->getFuncName($s);
$this->stateNextStringIsClassName = false;
} elseif ($this->hasFuncName($s)) {
$ret = $this->getFuncName($s);
} elseif ($this->hasFuncName('$' . $s)) {
$var = $this->getFuncName('$' . $s);
$ret = substr($var, 1);
} elseif (
$this->replaceConstants
&& defined($s)
&& !(in_array($s, $this->reservedConstantNames))
) {
$ret = constant($s);
} elseif ($this->stateNextStringIsObjectMember) {
$ret = $this->getFuncName($s);
} else {
$ret = $s;
}
$this->stateNextStringIsObjectMember = false;
return $ret;
}
/**
*
* @param string $var
* @return string
*/
protected function handleVariable($var)
{
if (in_array($var, $this->reservedVariableNames)) {
return $var;
}
if ($this->stateNextVariableIsInstanceVariable) {
if (!array_key_exists($var, $this->funcmap)) {
$name = sprintf(
'$' . $this->variableNamePattern,
count($this->funcmap)
);
$this->funcmap[$var] = $name;
}
$this->stateNextVariableIsInstanceVariable = false;
return $this->funcmap[$var];
}
if (!array_key_exists($var, $this->varmap)) {
$name = sprintf(
'$' . $this->variableNamePattern,
count($this->varmap)
);
$this->varmap[$var] = $name;
}
return $this->varmap[$var];
}
/**
*
* @param string $source Source to obfuscate
* @return string Obfuscated source
*/
public function obfuscate($source)
{
$this->reset();
$tokens = token_get_all($source);
$tokenName = '';
$valueToAppend = '';
foreach ($tokens as $token) {
if (is_string($token)) {
$tokenName = $token;
$valueToAppend = $token;
} else {
$tokenName = $token[0];
$valueToAppend = $token[1];
}
switch ($tokenName) {
case ';':
$this->stateNextStringIsNamespaceName = false;
break;
case T_COMMENT:
case T_DOC_COMMENT:
if ($this->removeComments) {
$valueToAppend = '';
}
break;
case T_WHITESPACE:
if ($this->removeWhiteSpace) {
$valueToAppend = '';
}
break;
case T_NEW:
case T_CLASS:
$this->stateNextStringIsClassName = true;
break;
case T_FUNCTION:
$this->stateNextVariableIsInstanceVariable = false;
$this->stateNextStringIsFunctionName = true;
break;
case T_STRING:
$valueToAppend = $this->handleString($valueToAppend);
break;
case T_CONSTANT_ENCAPSED_STRING:
$valueToAppend = $this->handleEncapsedString(
$valueToAppend
);
break;
case T_VARIABLE:
$valueToAppend = $this->handleVariable($valueToAppend);
break;
case T_PUBLIC:
case T_PROTECTED:
case T_PRIVATE:
$this->stateNextVariableIsInstanceVariable = true;
break;
case T_NAMESPACE:
$this->stateNextStringIsNamespaceName = true;
break;
case T_OBJECT_OPERATOR:
$this->stateNextStringIsObjectMember = true;
break;
default:
// nop
break;
}
$this->out .= $this->appendWhiteSpaceIfNecessary($valueToAppend);
}
// Because we can
$this->out .= "\n";
return $this->out;
}
}
/**
*
*/
class AliasMap
{
protected $symbolToAliasMap;
protected $generationStrategy;
public function __construct(Closure $generationStrategy = null)
{
$this->symbolToAliasMap = array();
$this->generationStrategy = function ($map, $symbol) {
#return sprintf('_%s', count($map->symbolToAliasMap));
};
if ($generationStrategy !== null) {
$this->generationStrategy = $generationStrategy;
}
}
public function addAlias($symbol)
{
$func = $this->generationStrategy;
$alias = $func($this, $symbol);
$this->symbolToAliasMap[$symbol] = $alias;
}
public function hasAlias($symbol)
{
return array_key_exists($symbol, $this->symbolToAliasMap);
}
public function getAlias($symbol)
{
if (!$this->hasAlias($symbol)) {
throw new Exception('');
}
return $this->symbolToAliasMap[$symbol];
}
}
error_reporting(-1);
ini_set('display_errors', 1);
$source = '';
while (!feof(STDIN)) {
$source .= fgets(STDIN);
}
$obfuscator = new Obfuscator();
$obfuscated = $obfuscator->obfuscate($source);
fwrite(STDOUT, $obfuscated);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment