Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP rename all variables inside code

I would like to rename all variables within the file to random name.

For example this:

$example = "some $string";
function ($variable2) {
    echo $variable2;
}
foreach ($variable3 as $key => $var3val) {
    echo $var3val . "somestring";
}

Will become this:

$frk43r = "some $string";
function ($izi34ee) {
    echo $izi34ee;
}
foreach ($erew7er as $iure7 => $er3k2) {
    echo $er3k2 . "some$string";
}  

It doesn't look so easy task so any suggestions will be helpful.

like image 277
JohnyFree Avatar asked Sep 04 '15 08:09

JohnyFree


2 Answers

I would use token_get_all to parse the document and map a registered random string replacement on all interesting tokens.

To obfuscate all the variable names, replace T_VARIABLE in one pass, ignoring all the superglobals.

Additionally, for the bounty's requisite function names, replace all the T_FUNCTION declarations in the first pass. Then a second pass is needed to replace all the T_STRING invocations because PHP allows you to use a function before it's declared.

For this example, I generated all lowercase letters to avoid case-insensitive clashes to function names, but you can obviously use whatever characters you want and add an extra conditional check for increased complexity. Just remember that they can't start with a number.

I also registered all the internal function names with get_defined_functions to protect against the extremely off-chance possibility that a randomly generated string would match one of those function names. Keep in mind this won't protect against special extensions installed on the machine running the obfuscated script that are not present on the server obfuscating the script. The chances of that are astronomical, but you can always ratchet up the length of the randomly generated string to diminish those odds even more.

<?php

$tokens = token_get_all(file_get_contents('example.php'));

$globals = array(
    '$GLOBALS',
    '$_SERVER',
    '$_GET',
    '$_POST',
    '$_FILES',
    '$_COOKIE',
    '$_SESSION',
    '$_REQUEST',
    '$_ENV',
);

// prevent name clashes with randomly generated strings and native functions
$registry = get_defined_functions();
$registry = $registry['internal'];

// first pass to change all the variable names and function name declarations
foreach($tokens as $key => $element){
    // make sure it's an interesting token
    if(!is_array($element)){
        continue;
    }
    switch ($element[0]) {
        case T_FUNCTION:
            $prefix = '';
            // this jumps over the whitespace to get the function name
            $index = $key + 2;
            break;

        case T_VARIABLE:
            // ignore the superglobals
            if(in_array($element[1], $globals)){
                continue 2;
            }
            $prefix = '$';
            $index = $key;
            break;

        default:
            continue 2;
    }

    // check to see if we've already registered it
    if(!isset($registry[$tokens[$index][1]])){
        // make sure our random string hasn't already been generated
        // or just so crazily happens to be the same name as an internal function
        do {
            $replacement = $prefix.random_str(16);
        } while(in_array($replacement, $registry));

        // map the original and register the replacement
        $registry[$tokens[$index][1]] = $replacement;
    }

    // rename the variable
    $tokens[$index][1] = $registry[$tokens[$index][1]];
}

// second pass to rename all the function invocations
$tokens = array_map(function($element) use ($registry){
    // check to see if it's a function identifier
    if(is_array($element) && $element[0] === T_STRING){
        // make sure it's one of our registered function names
        if(isset($registry[$element[1]])){
            // rename the variable
            $element[1] = $registry[$element[1]];
        }
    }
    return $element;
},$tokens);

// dump the tokens back out to rebuild the page with obfuscated names
foreach($tokens as $token){
    echo $token[1] ?? $token;
}

/**
 * https://stackoverflow.com/a/31107425/4233593
 * Generate a random string, using a cryptographically secure
 * pseudorandom number generator (random_int)
 *
 * For PHP 7, random_int is a PHP core function
 * For PHP 5.x, depends on https://github.com/paragonie/random_compat
 *
 * @param int $length      How many characters do we want?
 * @param string $keyspace A string of all possible characters
 *                         to select from
 * @return string
 */
function random_str($length, $keyspace = 'abcdefghijklmnopqrstuvwxyz')
{
    $str = '';
    $max = mb_strlen($keyspace, '8bit') - 1;
    for ($i = 0; $i < $length; ++$i) {
        $str .= $keyspace[random_int(0, $max)];
    }
    return $str;
}

Given this example.php

<?php

$example = 'some $string';

if(isset($_POST['something'])){
  echo $_POST['something'];
}

function exampleFunction($variable2){
  echo $variable2;
}

exampleFunction($example);

$variable3 = array('example','another');

foreach($variable3 as $key => $var3val){
  echo $var3val."somestring";
}

Produces this output:

<?php

$vsodjbobqokkaabv = 'some $string';

if(isset($_POST['something'])){
  echo $_POST['something'];
}

function gkfadicwputpvroj($zwnjrxupprkbudlr){
  echo $zwnjrxupprkbudlr;
}

gkfadicwputpvroj($vsodjbobqokkaabv);

$vfjzehtvmzzurxor = array('example','another');

foreach($vfjzehtvmzzurxor as $riuqtlravsenpspv => $mkdgtnpxaqziqkgo){
  echo $mkdgtnpxaqziqkgo."somestring";
}
like image 199
Jeff Puckett Avatar answered Sep 25 '22 20:09

Jeff Puckett


EDIT 4.12.2016 - please see below! (after first answer)

I've just tried to find a solution which can handle both cases: your given case and this example from Elias Van Ootegerm.

of course it should be improved as mentioned in one of my comments, but it works for your example:

$source = file_get_contents("source.php");

// this should get all Variables BUT isn't right at the moment if a variable is followed by an ' or " !!
preg_match_all('/\$[\$a-zA-Z0-9\[\'.*\'\]]*/', $source, $matches);
$matches = array_unique($matches[0]);

// this array saves all old and new variable names to track all replacements
$replacements = array();
$obfuscated_source = $source;
foreach($matches as $varName)
{
    do // generates random string and tests if it already is used by an earlier replaced variable name
    {
        // generate a random string -> should be improved.
        $randomName = substr(md5(rand()), 0, 7);
        // ensure that first part of variable name is a character.
        // there could also be a random character...
        $randomName = "a" . $randomName;
    }
    while(in_array("$" . $randomName, $replacements));

    if(substr($varName, 0,8) == '$GLOBALS')
    {
        // this handles the case of GLOBALS variables
        $delimiter = substr($varName, 9, 1);
        if($delimiter == '$') $delimiter = '';
        $newName = '$GLOBALS[' .$delimiter . $randomName . $delimiter . ']'; 
    }
    else if(substr($varName, 0,8) == '$_SERVER')
    {
        // this handles the case of SERVER variables
        $delimiter = substr($varName, 9, 1);
        if($delimiter == '$') $delimiter = '';
        $newName = '$_SERVER[' .$delimiter . $randomName . $delimiter . ']'; 
    }
    else if(substr($varName, 0,5) == '$_GET')
    {
        // this handles the case of GET variables
        $delimiter = substr($varName, 6, 1);
        if($delimiter == '$') $delimiter = '';
        $newName = '$_GET[' .$delimiter . $randomName . $delimiter . ']'; 
    }
    else if(substr($varName, 0,6) == '$_POST')
    {
        // this handles the case of POST variables
        $delimiter = substr($varName, 7, 1);
        if($delimiter == '$') $delimiter = '';
        $newName = '$_POST[' .$delimiter . $randomName . $delimiter . ']'; 
    }
    else if(substr($varName, 0,7) == '$_FILES')
    {
        // this handles the case of FILES variables
        $delimiter = substr($varName, 8, 1);
        if($delimiter == '$') $delimiter = '';
        $newName = '$_FILES[' .$delimiter . $randomName . $delimiter . ']'; 
    }
    else if(substr($varName, 0,9) == '$_REQUEST')
    {
        // this handles the case of REQUEST variables
        $delimiter = substr($varName, 10, 1);
        if($delimiter == '$') $delimiter = '';
        $newName = '$_REQUEST[' .$delimiter . $randomName . $delimiter . ']'; 
    }
    else if(substr($varName, 0,9) == '$_SESSION')
    {
        // this handles the case of SESSION variables
        $delimiter = substr($varName, 10, 1);
        if($delimiter == '$') $delimiter = '';
        $newName = '$_SESSION[' .$delimiter . $randomName . $delimiter . ']'; 
    }
    else if(substr($varName, 0,5) == '$_ENV')
    {
        // this handles the case of ENV variables
        $delimiter = substr($varName, 6, 1);
        if($delimiter == '$') $delimiter = '';
        $newName = '$_ENV[' .$delimiter . $randomName . $delimiter . ']'; 
    }
    else if(substr($varName, 0,8) == '$_COOKIE')
    {
        // this handles the case of COOKIE variables
        $delimiter = substr($varName, 9, 1);
        if($delimiter == '$') $delimiter = '';
        $newName = '$_COOKIE[' .$delimiter . $randomName . $delimiter . ']'; 
    }
    else if(substr($varName, 1, 1) == '$')
    {
        // this handles the case of variable variables
        $name = substr($varName, 2, strlen($varName)-2);
        $pattern = '/(?=\$)\$' . $name . '.*;/';
        preg_match_all($pattern, $source, $varDeclaration);
        $varDeclaration = $varDeclaration[0][0];

        preg_match('/\s*=\s*["\'](?:\\.|[^"\\]])*["\']/', $varDeclaration, $varContent);
        $varContent = $varContent[0];

        preg_match('/["\'](?:\\.|[^"\\]])*["\']/', $varContent, $varContentDetail);
        $varContentDetail = substr($varContentDetail[0], 1, strlen($varContentDetail[0])-2);

        $replacementDetail = str_replace($varContent, substr($replacements["$" . $varContentDetail], 1, strlen($replacements["$" . $varContentDetail])-1), $varContent);

        $explode = explode($varContentDetail, $varContent);
        $replacement = $explode[0] . $replacementDetail . $explode[1];
        $obfuscated_source = str_replace($varContent, $replacement, $obfuscated_source);
    }
    else
    {
        $newName = '$' . $randomName;   
    }

    $obfuscated_source = str_replace($varName, $newName, $obfuscated_source);

    $replacements[$varName] = $newName;
}

// this part may be useful to change hard-coded returns of functions.
// it changes all remaining words in the document which are like the previous changed variable names to the new variable names
// attention: if the variables in the document have common names it could also change text you don't like to change...
foreach($replacements as $before => $after)
{
    $name_before = str_replace("$", "", $before);
    $name_after = str_replace("$", "", $after);
    $obfuscated_source = str_replace($name_before, $name_after, $obfuscated_source);
}

// here you can place code to write back the obfuscated code to the same or to a new file, e.g:
$file = fopen("result.php", "w");
fwrite($file, $obfuscated_source);
fclose($file);

EDIT there are still some cases left which require some effort. At least some kinds of variable declarations may not be handled correctly!

Also the first regex is not perfect, my current status is like: '/\$\$?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/' but this does not get the index-values of predefined variables... But I think it has some potential. If you use it like here you get all 18 involved variables... The next step could be to determine if a [..] follws after the variable name. If so any predefined variable AND such cases like $g = $GLOBALS; and any further use of such a $g would be covered...


EDIT 4.12.2016

due to LSerni and several comments on both the original quesion and some solutions I also wrote a parsing solution which you can find below. It handles an extended example file which was my aim. If you find any other challenge, please tell me!

new solution:

 $variable_names_before = array();
 $variable_names_after  = array();
 $function_names_before = array();
 $function_names_after  = array();
 $forbidden_variables = array(
    '$GLOBALS',
    '$_SERVER',
    '$_GET',
    '$_POST',
    '$_FILES',
    '$_COOKIE',
    '$_SESSION',
    '$_REQUEST',
    '$_ENV',
 );
 $forbidden_functions = array(
     'unlink'
 );

 // read file
 $data = file_get_contents("example.php");

 $lock = false;
 $lock_quote = '';
 for($i = 0; $i < strlen($data); $i++)
 {
     // check if there are quotation marks
     if(($data[$i] == "'" || $data[$i] == '"'))
     {
         // if first quote
         if($lock_quote == '')
         {
             // remember quotation mark
             $lock_quote = $data[$i];
             $lock = true;
         }
         else if($data[$i] == $lock_quote)
         {
             $lock_quote = '';
             $lock = false;
         }
     }

     // detect variables
     if(!$lock && $data[$i] == '$')
     {
         $start = $i;
         // detect variable variable names
         if($data[$i+1] == '$')
         {
             $start++;
             // increment $i to avoid second detection of variable variable as "normal variable"
             $i++;
         }

         $end = 1;
         // find end of variable name
         while(ctype_alpha($data[$start+$end]) || is_numeric($data[$start+$end]) || $data[$start+$end] == "_")
         {
             $end++;
         }
         // extract variable name
         $variable_name = substr($data, $start, $end);
         if($variable_name == '$')
         {
             continue;
         }
         // check if variable name is allowed
         if(in_array($variable_name, $forbidden_variables))
         {
             // forbidden variable deteced, do whatever you want!
         }
         else
         {
             // check if variable name already has been detected
             if(!in_array($variable_name, $variable_names_before))
             {
                 $variable_names_before[] = $variable_name;
                 // generate random name for variable
                 $new_variable_name = "";
                 do
                 {
                     $new_variable_name = random_str(rand(5, 20));
                 }
                 while(in_array($new_variable_name, $variable_names_after));
                 $variable_names_after[] = $new_variable_name;
             }
             //var_dump("variable: " . $variable_name);
         }
     }

     // detect function-definitions
     // the third condition checks if the symbol before 'function' is neither a character nor a number
     if(!$lock && strtolower(substr($data, $i, 8)) == 'function' && (!ctype_alpha($data[$i-1]) && !is_numeric($data[$i-1])))
     {
         // find end of function name
         $end = strpos($data, '(', $i);
         // extract function name and remove possible spaces on the right side
         $function_name = rtrim(substr($data, ($i+9), $end-$i-9));
         // check if function name is allowed
         if(in_array($function_name, $forbidden_functions))
         {
             // forbidden function detected, do whatever you want!
         }
         else
         {
             // check if function name already has been deteced
             if(!in_array($function_name, $function_names_before))
             {
                 $function_names_before[] = $function_name;
                 // generate random name for variable
                 $new_function_name = "";
                 do
                 {
                     $new_function_name = random_str(rand(5, 20));
                 }
                 while(in_array($new_function_name, $function_names_after));
                 $function_names_after[] = $new_function_name;
             }
             //var_dump("function: " . $function_name);
         }
     }
 }

// this array contains prefixes and suffixes for string literals which
// may contain variable names.
// if string literals as a return of functions should not be changed
// remove the last two inner arrays of $possible_pre_suffixes
// this will enable correct handling of situations like
// - $func = 'getNewName'; echo $func();
// but it will break variable variable names like
// - ${getNewName()}
$possible_pre_suffixes = array(
    array(
        "prefix" => "= '",
        "suffix" => "'"
    ),
    array(
        "prefix" => '= "',
        "suffix" => '"'
    ),
    array(
        "prefix" => "='",
        "suffix" => "'"
    ),
    array(
        "prefix" => '="',
        "suffix" => '"'
    ),
    array(
        "prefix" => 'rn "', // return " ";
        "suffix" => '"'
    ),
    array(
        "prefix" => "rn '", // return ' ';
        "suffix" => "'"
    )
);
// replace variable names
for($i = 0; $i < count($variable_names_before); $i++)
{
    $data = str_replace($variable_names_before[$i], '$' . $variable_names_after[$i], $data);

    // try to find strings which equals variable names
    // this is an attempt to handle situations like:
    // $a = "123";
    // $b = "a";    <--
    // $$b = "321"; <--

    // and also
    // function getName() { return "a"; }
    // echo ${getName()};
    $name = substr($variable_names_before[$i], 1);
    for($j = 0; $j < count($possible_pre_suffixes); $j++)
    {
        $data = str_replace($possible_pre_suffixes[$j]["prefix"] . $name . $possible_pre_suffixes[$j]["suffix"],
                            $possible_pre_suffixes[$j]["prefix"] . $variable_names_after[$i] . $possible_pre_suffixes[$j]["suffix"],
                            $data);
    }
}
// replace funciton names
for($i = 0; $i < count($function_names_before); $i++)
{
    $data = str_replace($function_names_before[$i], $function_names_after[$i], $data);
}

/**
 * https://stackoverflow.com/a/31107425/4233593
 * Generate a random string, using a cryptographically secure
 * pseudorandom number generator (random_int)
 *
 * For PHP 7, random_int is a PHP core function
 * For PHP 5.x, depends on https://github.com/paragonie/random_compat
 *
 * @param int $length      How many characters do we want?
 * @param string $keyspace A string of all possible characters
 *                         to select from
 * @return string
 */
function random_str($length, $keyspace = 'abcdefghijklmnopqrstuvwxyz')
{
    $str = '';
    $max = mb_strlen($keyspace, '8bit') - 1;
    for ($i = 0; $i < $length; ++$i)
    {
        $str .= $keyspace[random_int(0, $max)];
    }
    return $str;
}

example input file:

$example = 'some $string';
$test = '$abc 123' . $example . '$hello here I "$am"';

if(isset($_POST['something'])){
  echo $_POST['something'];
}

function exampleFunction($variable2){
  echo $variable2;
}

exampleFunction($example);

$variable3 = array('example','another');

foreach($variable3 as $key => $var3val){
  echo $var3val."somestring";
}

$test = "example";
$$test = 'hello';

exampleFunction($example);
exampleFunction($$test);

function getNewName()
{
    return "test";
}
exampleFunction(${getNewName()});

output of my function:

$fesvffyn = 'some $string';
$zimskk = '$abc 123' . $fesvffyn . '$hello here I "$am"';

if(isset($_POST['something'])){
  echo $_POST['something'];
}

function kainbtqpybl($yxjvlvmyfskwqcevo){
  echo $yxjvlvmyfskwqcevo;
}

kainbtqpybl($fesvffyn);

$lmiphctfgjfdnonjpia = array('example','another');

foreach($lmiphctfgjfdnonjpia as $qypdfcpcla => $gwlpcpnvnhbvbyflr){
  echo $gwlpcpnvnhbvbyflr."somestring";
}

$zimskk = "fesvffyn";
$$zimskk = 'hello';

kainbtqpybl($fesvffyn);
kainbtqpybl($$zimskk);

function tauevjkk()
{
    return "zimskk";
}
kainbtqpybl(${tauevjkk()});

I know there are some cases left, where you can find an issue with variable variable names, but then you may have to expand the $possible_pre_suffixes array...

Maybe you also want to differentiate between global variables and "forbidden variables"...

like image 20
SaschaP Avatar answered Sep 21 '22 20:09

SaschaP