Testing encoders for PHP

Warning: This blogpost has been posted over two years ago. That is a long time in development-world! The story here may not be relevant, complete or secure. Code might not be complete or obsoleted, and even my current vision might have (completely) changed on the subject. So please do read further, but use it with caution.
Posted on 14 Aug 2010
Tagged with: [ encoding ]  [ PHP

A friend of mine posted a tweet about problems with Zend Guard just the other day. My friendly advise was: try using another encoder. Which he kindly ignored :) Which on my turn again made me wonder: how many encoders are out there, and more important how easy are they to work with?

Why encoding?

First, let’s answer the question on WHY you want to encode PHP files. There are 2 main reasons for it:

  • When distributing PHP applications to third parties (under a licensed model).
  • For extra security measurements on your web servers.

More and more companies deliver PHP applications to third parties these days. For instance, CMS systems or ERP packages that are written in PHP. It’s quite possible that software developers don’t want their customers to browse through their code, make adjustments or even distribute the code to others. With a PHP encoder, this is not (easily) possible. Most encoders can ‘lock’ software to be usable only for a certain amount of time (for instance, in a licensed model that needs to be renewed every year) or even to certain IP addresses or hardware systems (so it cannot be distributed to other systems).

A second reason is to secure your software in case your web server(s) gets taken over by malicious users. There is always a possibility that this can happen, and by encoding your software, it makes it harder for hackers to find out what is going on (like getting passwords to your database server, browsing your configurations etc).

How does encoding work

Using encoded PHP files is a two-step process. First step is to encode the file(s) with an encoder. This converts the PHP source into a non-readable format. This format can be read by the loader, which is the second step. This loader is a zend_extension which needs to be loaded on the web server that needs to run the encoded files. If a loader is not present, the encrypted fails gracefully with an error-message.

But be warned: just like any software encryption system: everything is reversible in the end. It will always be possible to reverse engineer software. Maybe not back to the complete source code, but close enough for others to work with. This is true for normal binary compiled applications, as well as for PHP encoded software. Be aware that in the end you are only building a barrier which will never be 100% proof against malicious users.

Test environment

The tests I did run are done on a standard standard Debian 5.0 system (codename Lenny) running inside a virtualbox system. This system is running the standard software except for PHP, which was taken from the dotdeb repository. So instead of PHP 5.2, the test system is running php 5.3.3 (PHP 5.3.3-0.dotdeb.0 with Suhosin-Patch to be exact). The main reason for this is 2-fold:

First of: PHP 5.3 is released for over a year now and mature enough to be used in production environments. Secondly, the PHP development team has announced the end-of-support for 5.2, which means an end-of-life will not be very far away. It’s time to move on to newer PHP releases.

Encoders tested

I’ve initially started out with 4 encoders, but ended up with only 2 actually working in the end. These are the candidates:

Product: Zend guard
Version: 5.0.1
Price: $600 annual
By: Zend
Trail: http://www.zend.com/en/products/guard/downloads

Product: IonCube PHP Encoder
Version: 6.5
Price: ranging frm $199 to $379
By: IonCube
Trail: http://www.ioncube.com/sa_encoder.php

Product: SourceGuardian
Version: 8.2
Price: $199
By: SourceGuardian
Trail: http://www.sourceguardian.com/profile/index.php

Product: Nu-Coder
Version: 2.0
Price: $149
By: NuSphere
Trail: http://www.nusphere.com/products/nucoder.htm

Applications encoded:

Because this test has to reflect real-life situations, i’ve tested 4 applications:

  • A basic helloworld script, which does nothing than displaying ‘hello world’.
<?php
  print "Hello world";
?>
  • A 5.3 testscript with 5.3 features (late static binding, oo5, namespaces etc)
<?php

  /**
   * PHP 5.3 Features test
   */

// Create namespace
namespace ns\one;

// Create simple class
class Class1 {
    protected $_name;

    function __construct($name = "Default name") {
        $this->_name = $name;
    }

    function Test() {
        print "\\ns\\one\\Class1->Test(): ".$this->_name."\n";
    }
}

// Create another namespace
namespace ns\two;

// Create class with same name as the one in the first namespace
class Class1 {
    function Test() {
        print "\\ns\\two\\Class2->Test()\n";
    }
}

// Simple baseclass for testing late static binding
class ClassBase {
    static function whoami() {
        print "\\ns\\two\\ClassBase::WhoAmI()\n";
    }

    // When this is called, it should display the whoami() function from THIS class
    static function StaticTest() {
        self::whoami ();
    }
}

// Class 2 extends the baseclass
class Class2 extends ClassBase{
    function Test() {
        print "Test from namespace 2\n";
    }

    static function whoami() {
       print "\\ns\\two\\Class2::WhoAmI()\n";
    }

    // This should call whoami() function from THIS class in php 5.3 (late
    // static binding), but will call the whoami() from ClassBase in php5.2.
    static function StaticTest() {
        self::whoami ();
    }
}

// Every line is printed twice, first the line it generated, the second line what
// it should have been. Should all be equal when everything went ok..
use \ns\one as ns1;

$tmp = new ns1\Class1();
$tmp->Test();
print "\\ns\\one\\Class1->Test(): Default name\n";
print "\n";

$tmp = new ns1\Class1("Another name");
$tmp->Test();
print "\\ns\\one\\Class1->Test(): Another name\n";
print "\n";

$tmp = new \ns\two\Class2();
$tmp->Test ();
print "Test from namespace 2\n";
print "\n";

$tmp::StaticTest();
print "\\ns\\two\\Class2::WhoAmI()\n";
print "\n";

// Simple closure testing

print  preg_replace_callback('~-([a-z])~', function ($match) {
    return strtoupper($match[1]);
}, 'hello-world')."\n";
print "helloWorld\n";
print "\n";

$greet = function($name) { printf("Hello %s\r\n", $name); };
$greet('World');
print "Hello World\n";
print "\n";

$greet('PHP');
print "Hello PHP\n";
print "\n";
  • WordPress 3.0.1 (downloaded from http://wordpress.org/latest.tar.gz)

  • phpmyadmin 3.3.5 (downloaded from http://www.phpmyadmin.net/home_page/index.php)

In order to succesfully pass the test, each application must meet the following:

  1. Encode without errors
  2. Run inside a web browser without errors
  3. Display the correct results (in case of the php53 and hello world test scripts)

Furthermore, there are extra bonuspoints for ease of use, speed, documentation, features etc. However, extra features (like IP or hardware locking) are not tested.

Ioncube:

Ioncube is a very simple and fast encoder with a lot of options. Downloading a trail needs an account on the ioncube site. Installation consists of unpacking a tar file which contains a few binaries and the loaders. Ioncube does not support 5.3 files and so it was not possible to encode the test53 application. Other applications encoded properly without any hassle or need for documentation.

Installing the loader is easy as well. Pick the correct loader (loader_5.3 when you have php version 5.3, loader_5.2 when you use 5.2 etc). It even comes with a loader wizard, which tells you which loader you need for your server in case you get confused (all loaders also come with a thread-safe version, so that might be confusing).

Running the encrypted PHP files did not reveal any problems. The helloworld application nicely displayed ‘hello world’, phpmyadmin let me browse through the MySQL database and wordpress installed correctly from scratch.

The only catch with installing wordpress is that wordpress creates his configuration file from the wp-config-sample.php. This file was (obviously) encoded as well, which means it could not create a decent wp-config.php. I have to use a unencoded wp-config-sample.php, run the setup process again, and afterwards, encode both files and place it in the wordpress directory. Afterwards, browsing through wordpress and adding new posts worked perfectly.

SourceGuardian:

SourceGuardian says on it’s website it supports PHP 5.x. This normally must start sounding alarm-bells, because a php5.1 is nowhere near the same as php5.3 so I was a bit anxious on testing my 5.3 test application. However, running the encoder on all files worked perfectly. Before you can actually start to encode files, you have to create a license file (encode.lic), for which you must register at the website. It’s a cumbersome process which I don’t think should be needed for a trail version.

Setting up the loader is just as easy as it was with ioncube. It comes with a very wide range of loaders, and a install-wizard makes it easy for you to find the correct loader in case of trouble.

However, running the encoded applications didn’t work on the first attempt. This was because SourceGuardian created PHP code that needed the dl() function. This is a function that dynamically loads libraries from the server. Something that needs to be turned on specifically in your php.ini with the “enable_dl = on” setting. Even though ioncube also uses the dl() function, it silences this operation (using the @ operator) and uses a different method when it fails).

Testing the applications works, except for wordpress. That application segfaulted when entering the second stage during wordpress installation. I don’t know exact the cause of this (will find out later). I’ve tested the phpmyadmin even better but couldn’t detect any segfaults here. It might had to do with reading or writing the wp-config-sample.php file, but this does not change the fact that it didn’t run correctly out-of-the-box.

Nu-Coder:

Testing nu-coder was simple enough. I skipped it. This software only runs on windows, so I didn’t test it. Even though there are some mentions of running nu-coder correctly under wine, I want to test encoders for their out-of-the-box use.

Zend Guard:

Testing Zend Guard started out difficult and ended up impossible. We got encoded files in the end, but there wasn’t a possibility of getting the loader up and running, so we ended up not testing this package as well.

There are many, many things wrong with Zend Guard. First of all, it’s WAY to big. It’s about 80MB of application just for ENCODING files (note: this is not even included the loaders). It’s written in Java, which doesn’t make me jump for joy in the first place, but you need X11 (a Gui) for installing the encoder. Yuk.. After browsing through the manual (actually googling for it), I found there is a command-line installation possible (a silent install). It was silent enough: just a text mode progress bar and as suspected: no way of telling where it would copy files to. Luckily, it more or less adhered the LFH standards and placed the encoder in /usr/local/Zend.

The encoder itself is (off course) gui-based, so I haven’t tested that. Browsing through the ZendGuard directory I discovered zendenc and zendenc5. Those are the command-line versions of the encoder which you can use. Running zendenc on all four applications worked without any problems, but it is very VERY slow compared to the other encoders.

After encoding, I stared to look for the loaders, which should be present somewhere in the Zend directory if it remotely worked the same was as all the other encoders. Unfortunately, they were missing, and after reading the manual (and looking at the encoded php files), I found out eventually that you need another Zend application in order to run the encoded files: Zend Optimizer.

This is thankfully only a 8MB tar file which holds the loaders. I assume it does lots of other things as well, which I didn’t bother to find out, since I want to focus on the encoding. Installation works the same way: copy the right loader to your extension directory (did I already mentioned they DON’T have a loader-wizard?), add it to your php.ini and run..

At least.. that was the plan. Turns out, Zend does not have any loaders that can be used with the Zend API I was using. The latest version of the Zend optimizer (3.3.9) needs API version 220060519, while dotdeb already was working with API version 220090626. So in the end, I could not test ANY ZendGuard encoded files, but something tells me I’m not missing out very much.

Conclusion

Even though I have tested these encoders, more things are important when using encoders. Support, price, ability to run with optimizers or accelerators, stability etc are important issues when deploying encoders into a production environment. Always make sure the encoder you want to use meet the requirements you have set.

First place:  IonCube.
Even though not capable of encoding PHP 5.3, I like the ease of use, speed and the fact it worked out-of-the-box without any hassle. It has got lots of features which can be usefull.

Second place: SourceGuardian
Even though it’s the only one capable of handling PHP 5.3 (and running it), it’s just as easy as working with ioncube. The only 2 problems:

  • You need to generate a license file. Even though this is a 2 minute hassle, it’s still a hassle.
  • Segfault on the wordpress installation.

Third place: Nu-coder
Yes, that’s right. The encoder that didn’t even work under Linux gets a higher ranking than Zend Guard.

Last place: Zend Guard
I don’t know where to begin. It’s big, clumsy, annoying and in the end didn’t even work. I had to spend too much time figuring out what to do even though we are talking about a simple encoder (which should be 1 binary + couple of loaders + maybe a readme.txt).