Generating IDE Stubs for IonCube-Encoded Classes

Generating IDE Stubs for IonCube-Encoded Classes

I recently inherited a legacy PHP project built on a closed-source framework where all the core classes were encrypted with IonCube. Working with closed-source code is hard enough, but not having any code hints in the IDE makes it even more challenging.

Here's what the codebase looks like:

Encrypted PHP code

What am I supposed to do with this?! Not knowing what classes and methods exist makes it extremely difficult to use that functionality. In order to make life easier, I set out to create a stubs.php file listing all of those "hidden" classes and methods.

Per the framework's license, decrypting the IonCube-protected code was not allowed. This meant it was impossible to recover the original source code. However, I could require those files and execute them in PHP, which would cause those classes to become usable in code. So how does one figure out what code just got loaded & executed?

Reflection to the Rescue!

The answer, of course, was to use reflection! PHP's reflection API allows us to inspect things like classes and their methods at runtime. This is extremely useful in this situation because we can only access the encrypted classes at runtime (after they've been loaded and decrypted by IonCube).

This is the approach I wanted to use:

  1. Generate a list of already declared classes
  2. Load in all of the encrypted files (via require_once)
  3. Re-generate the list of delcared classes
  4. Diff the two lists to see which classes were loaded during step 2
  5. Iterate through those classes using reflection and generate PHP code for each one

Steps 1-4 are easily done with a few lines of PHP. The only remaining challenge was how to generate PHP code based on reflection objects. PHP's reflection API produces output which looks something like this:

Method [ <user> public method find ] {
 
  - Parameters [4] {
    Parameter #0 [ <optional> $conditions = NULL ]
    Parameter #1 [ <optional> $fields = Array ]
    Parameter #2 [ <optional> $order = NULL ]
    Parameter #3 [ <optional> $recursive = NULL ]
  }
}

While it's certainly helpful information, it's not valid PHP code. Having a valid PHP representation of the classes would allow my IDE to provide code hints. So how do we use reflection data to generate PHP?

Using ocramius/code-generator-utils

Thankfully the community has already solved this problem! I found an awesome library written by Marco Pivetta called ocramius/code-generator-utils which provided the exact tools I needed.

Using this library, I implemented a quick script to generate a PHP representation of those classes:

<?php

require_once 'vendor/autoload.php';

$before = get_declared_classes();

// The legacy code produced tons of notices and warnings
error_reporting(0);

foreach (glob("/path/to/encrypted/files/*.php") as $filename) {
    if (strpos(file_get_contents($filename), 'ionCube')) {
        require_once $filename;
    }
}

$after = get_declared_classes();

$builder = new \CodeGenerationUtils\ReflectionBuilder\ClassBuilder();
$standard = new \PhpParser\PrettyPrinter\Standard();

$stubs = [];
foreach (array_diff($after, $before) as $class) {
    $ast = $builder->fromReflection(new \ReflectionClass($class));
    $stubs[] = $standard->prettyPrint($ast);
}

file_put_contents('stubs.php', "<?php\n\n" . implode("\n\n", $stubs));

This generates a single file named stubs.php containing a PHP representation of all the encrypted classes and their methods/members/constants:

I can actually understand this now!

Much better!

By simply having this file available in my project root the IDE is able to reference these definitions for code hints!

The one downside is that reflection cannot tell you what code is inside the methods - you'd probably need to reverse-engineer the opcodes for that, which is beyond the scope of this article. And for classes without return types, it can't tell you what might be returned. Nevertheless, simply knowing the methods and parameters is a step forward and makes development just a little bit easier.

After posting this, /u/bob4ever on Reddit told me about an open-source project he created which does exactly this! Go check it out: https://github.com/Setasign/php-stub-generator

Enjoy this article?

About Colin O'Dell

Colin O'Dell

Colin O'Dell is a Senior Software Engineer at SeatGeek. In addition to being an active member of the PHP League and maintainer of the league/commonmark project, Colin is also a PHP docs contributor, conference speaker, and author of the PHP 7 Migration Guide.