PHP重命名代码中的所有变量

时间:2015-09-04 08:13:39

标签: php parsing variables

我想将文件中的所有变量重命名为随机名称。

例如:

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

将成为这个:

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

它看起来不那么容易,所以任何建议都会有所帮助。

5 个答案:

答案 0 :(得分:10)

我会使用token_get_all来解析文档,并在所有有趣的令牌上映射已注册的random string替换。

要混淆所有变量名称,请在一次传递中替换T_VARIABLE,忽略所有the superglobals

此外,对于赏金的必备函数名称,请替换第一遍中的所有T_FUNCTION声明。然后需要第二次传递来替换所有T_STRING调用,因为PHP允许您在声明之前使用函数。

对于这个例子,我生成了所有小写字母,以避免对函数名称进行不区分大小写的碰撞,但显然可以使用您想要的任何字符,并添加额外的条件检查以增加复杂性。请记住,他们不能以数字开头。

我还使用get_defined_functions注册了所有内部函数名称,以防止随机生成的字符串与其中一个函数名称匹配的极端偶然可能性。请记住,这不能防止运行模糊脚本的计算机上安装的特殊扩展,这些扩展在服务器上不存在,会使脚本混淆。这种可能性是天文数字,但你可以随时增加随机生成的字符串的长度来减少这些可能性。

<?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;
}

鉴于此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";
}

生成此输出:

<?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";
}

答案 1 :(得分:6)

编辑4.12.2016 - 请看下面! (在第一次回答后)

我刚刚尝试找到一个可以处理这两种情况的解决方案:您的案例和this example中的Elias Van Ootegerm

当然应该像我的一条评论中提到的那样进行改进,但它适用于你的例子:

$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);

编辑 还有一些案例需要付出一些努力。 至少某些变量声明可能无法正确处理!

另外第一个正则表达式并不完美,我目前的状态如下: '/\$\$?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/' 但这并没有获得预定义变量的索引值......但我认为它有一定的潜力。如果你像here一样使用它,那么你将得到所有18个涉及的变量......下一步可能是确定变量名后面是否[...]。如果有的话,任何预定义的变量和诸如$g = $GLOBALS;之类的情况以及这种$g的任何进一步使用都将被涵盖......

编辑4.12.2016

由于LSerni以及对原始问题和一些解决方案的一些评论,我还写了一个解析解决方案,您可以在下面找到。 它处理扩展的示例文件,这是我的目标。如果您发现任何其他挑战,请告诉我!

新解决方案:

 $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 = '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()});

我的功能输出:

$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()});

我知道还有一些案例,你可以找到变量名称的问题,但是你可能需要扩展$possible_pre_suffixes数组......

也许您还想区分全局变量和“禁止变量”......

答案 2 :(得分:5)

嗯,你可以尝试自己编写,但你必须要处理的奇怪事情的数量可能会让你感到压力,我认为你对使用这样的工具比自己编写和维护这些工具更感兴趣。 (那里有很多破碎的PHP混淆器,人们试图这样做。)

如果你想要一个可靠的,你将它基于解析器,否则你的工具会错误地解析文本并处理错误(这是第一个“奇怪的事情”)。正则表达式根本不会做到这一点。

语义设计PHP Obfuscator(来自我的公司),开箱即用,采用了Elias Van Ootegem的略微修改版本的例子:

 <?php

//non-obfuscated

function getVarname()
{//the return value has to change
return (('foobar'));
}

$format = '%s = %d';
$foobar = 123;

$variableVar = (('format'));//you need to change this string

printf($$variableVar, $variableVar = getVarname(), $$variableVar);

echo PHP_EOL;

var_dump($GLOBALS[(('foobar'))]);//note the key == the var

并制作了这个:

<?php function l0() { return (('O0')); } $l1="%\163 = %d"; $O1=0173; $l2=(('O2')); printf($$l2,$l2=l0(),$$l2); echo PHP_EOL; var_dump($GLOBALS[(('O0'))]);

Elias示例中的关键问题是实际包含变量名称的字符串。通常,工具无法知道“x”变量名,而不仅仅是包含字母x的字符串。但是,程序员知道。我们坚持要将这些字符串标记为[通过将它们包含在((..))中,然后混淆器可以正确地混淆其内容。 有时字符串包含变量名称和其他内容;就是这样, 程序员必须将字符串分解为“变量名”内容和其他所有内容。这在实践中很容易做到,而且是  我对他提供的代码所做的“轻微改动”。 没有标记的其他字符串是独立的。你只需这样做 一次到源文件。 [你可以说这是作弊,但没有其他实际的答案可行;工具无法可靠地知道。停止问题,如果你坚持的话。]。

接下来要做的就是跨多个文件进行可靠的混淆。您不能一次执行此文件。这个混淆器已用于非常大的PHP应用程序(数千个PHP脚本文件)。

是的,它确实使用了完整的PHP解析器。不是nikic的。

答案 3 :(得分:2)

我最终得到了这个简单的代码:

$tokens = token_get_all($src);
$skip = array('$this','$_GET','$_POST','$_REQUEST','$_SERVER','$_COOKIE','$_SESSION');
function renameVars($tokens,$content,$skip){
  $vars = array();
  foreach($tokens as $token) {
      if ($token[0] == T_VARIABLE && !in_array($token[1],$skip))
          $vars[generateRandomString()]= $token[1];
  }
  $vars = array_unique($vars);
  $vars2 = $vars;

  foreach($vars as $new => $old){
    foreach($vars2 as $var){
      if($old!=$var && strpos($var,$old)!==false){
        continue 2;
      }
    }  
    $content = str_replace($old,'${"'.$new.'"}',$content);
    //function(${"example"}) will trigger error. This is why we need this:
    $content = str_replace('(${"'.$new.'"}','($'.$new,$content);
    $content = str_replace(',${"'.$new.'"}',',$'.$new,$content);
    $chars = array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z');
    //for things like function deleteExpired(Varien_Event_Observer $fz5eDWIt1si), Exception, 
    foreach($chars as $char){
      $content = str_replace($char.' ${"'.$new.'"}',$char.' $'.$new,$content);
    }           
} 

它对我有用,因为代码很简单。我猜它不适用于所有场景。

答案 4 :(得分:0)

我现在正在使用它,但可能仍然存在一些漏洞,因为PHP允许动态生成函数名称和变量名称。

第一个函数用函数替换$_SESSION$_POST等:

function replaceArrayVariable($str, $arr, $function)
{
    $str = str_replace($arr, $function, $str);
    $lastPos = 0;

    while (($lastPos = strpos($str, $function, $lastPos)) !== false)
    {
        $lastPos = $lastPos + strlen($function);
        $currentPos = $lastPos;
        $openSqrBrackets = 1;
        while ($openSqrBrackets > 0)
        {
            if ($str[$currentPos] === '[')
                 $openSqrBrackets++;
            elseif ($str[$currentPos] === ']')
                 $openSqrBrackets--;
            $currentPos++;
        }
        $str[$currentPos - 1] = ')';
    }
    return $str;
}

第二个重命名功能,忽略列入白名单的关键字:

function renameFunctions($str)
{
    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $str, $matches, PREG_OFFSET_CAPTURE);
    $totalMatches = count($matches[0]);
    $offset = 0;
    for ($i = 0; $i < $totalMatches; $i++)
    {
        $matchIndex = $matches[0][$i][1] + $offset;
        if ($matchIndex === 0 || $str[$matchIndex - 1] !== '$')
        {
            $keyword = $matches[0][$i][0];
            if ($keyword !== 'true' && $keyword !== 'false' && $keyword !== 'if' && $keyword !== 'else' && $keyword !== 'getPost' && $keyword !== 'getSession')
            {
                $str = substr_replace($str, 'qq', $matchIndex, 0);
                $offset += 2;
            }
        }
    }
    return $str;
}

然后重命名函数,变量和非白名单关键字,我使用此代码:

$str = replaceArrayVariable($str, '$_POST[', 'getPost(');
$str = replaceArrayVariable($str, '$_SESSION[', 'getSession(');
preg_match_all('/\'(?:\\\\.|[^\\\\\'])*\'|.[^\']+/', $str, $matches);
$str = '';
foreach ($matches[0] as $match)
{
    if ($match[0] != "'")
    {
        $match = preg_replace('!\s+!', ' ', $match);
        $match = renameFunctions($match);
        $match = str_replace('$', '$qq', $match);
    }
    $str .= $match;
}