php encrupt 加密,PHP解密:EnPHP混淆加密

前天收到一个任务,解密一个文件,然后就有了这个故事。

想了解EnPHP同学的移步 EnPHP官网

解密需要使用的工具:PHP-Parser

使用composer安装PHP-Parser

mkdir php-parser

cd php-parser

composer init

composer require nikic/php-parser

EnPHP加密的原理简介:

把需要加密的字符串保存到一个全局数组$_SERVER[STR]中,然后把字符串替换成数组变量,接着把$_SERVER[STR]分割成字符串,然后gzip压缩生成一个乱码字符串,然后在文件开头定义得到STR数组(这里的STR实际上也会被加密成乱码)。

例如:

源代码

$a = 'hello world';

加密后代码:

$_SERVER[STR] = explode('分隔符', gzinflate(substr('乱码', 0xa, -8)));

$x =& $_SERVER[STR][0];

解码原理简介:PHP-Parser可以用php代码生成ast(抽象语法树),然后再用ast生成php代码,只需要基于ast,就可以把代码逐步还原

基于这个理论,开始解密文件:

加密文件打开后是一堆乱码(文件一部分截图):

98b7ec6da761f0f88679a4bf66e1066a.png

第一步:生成ast(抽象语法树)、格式化源码

use PhpParser\Error;

use PhpParser\NodeDumper;

use PhpParser\ParserFactory;

use PhpParser\PrettyPrinter;

require 'vendor/autoload.php';

$filename = 'enphp.php';

$code = file_get_contents($filename);

$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);

try {

$ast = $parser->parse($code);

} catch (Error $error) {

echo "Parse error: {$error->getMessage()}\n";

return;

}

$filename = "enphp";

$i = 2;

$dumper = new NodeDumper;

file_put_contents($filename.$i.".ast", $dumper->dump($ast));

$prettyPrinter = new PrettyPrinter\Standard;

file_put_contents($filename.$i.".php", $prettyPrinter->prettyPrintFile($ast));

/* 接下来需要用到$_SERVER[òÎò]数组,可以从ast中获取explode时对应的参数 */

$str1 = $ast[1]->expr->args[0]->value->value;

$str3 = $ast[2]->expr->expr->args[0]->value->value;

$str4 = $ast[2]->expr->expr->args[1]->value->args[0]->value->args[0]->value->value;

$int1 = $ast[2]->expr->expr->args[1]->value->args[0]->value->args[1]->value->value;

$int2 = -$ast[2]->expr->expr->args[1]->value->args[0]->value->args[2]->value->expr->value;

//$string_array 即为 $_SERVER[òÎò]

$string_array = explode($str3, gzinflate(substr($str4, $int1, $int2)));

生成的ast文件为enphp2.ast,php文件为enphp2.php

打开enphp2.php:

e2c199b47f9d24d733ba271ae3dadeac.png

可以看到代码已经格式化了,代码里有类似$_SERVER[òÎò][0]、$_SERVER[òÎò][0x1]的变量,我们要做的就是把这些变量替换成原来的字符串。

首先,我们打印出$string_array(后面代码多次出现,请读者自动脑补为$_SERVER[òÎò]) 或者 $_SERVER[òÎò]数组

# 这里就是文件中所有乱码对应的字符串Array

(

[0] => error_reporting

[1] => HTTP_HOST

[2] => getTopDomainhuo

[3] => http://check.ieasynet.com/update.php

[4] => ?a=client_check&u=

[5] => curl_init

[6] => curl_setopt

......

[306] => /^\/admin.php/

[307] => /index.php

[308] => admin_url

[309] => /^\/index.php/

[310] => /admin.php

)

可以看到,$_SERVER[òÎò][0]其实就是error_reporting字符串,我们要做的就是替换所有的$_SERVER[òÎò][i]模式为原来的字符串。

打开enphp2.ast

找到$_SERVER[òÎò][i]的ast结构

name: Expr_ArrayDimFetch(

var: Expr_ArrayDimFetch(

var: Expr_Variable(

name: _SERVER

)

dim: Expr_ConstFetch(

name: Name(

parts: array(

0: òÎò

)

)

)

)

dim: Scalar_LNumber(

value: 0

)

)

我们要把这种结构替换成字符串结构,字符串结构是这样的

name: Scalar_String(

value: error_reporting // $string_array[0]

)

替换代码如下

use PhpParser\Node;

use PhpParser\Node\Stmt\Function_;

use PhpParser\NodeTraverser;

use PhpParser\NodeVisitorAbstract;

class GlobalStringNodeVisitor extends NodeVisitorAbstract {

protected $globalVariableName;

protected $stringArray;

public function __construct($globals_name, $string_array)

{

$this->globalVariableName = $globals_name;

$this->stringArray = $string_array;

}

public function enterNode(Node $node)

{

if ($node instanceof Node\Expr\ArrayDimFetch

&& $node->var instanceof Node\Expr\ArrayDimFetch

&& $node->var->var instanceof Node\Expr\Variable

&& $node->var->var->name === '_SERVER'

&& $node->var->dim instanceof Node\Expr\ConstFetch

&& $node->var->dim->name instanceof Node\Name

&& $node->var->dim->name->parts[0] === $this->globalVariableName

&& $node->dim instanceof Node\Scalar\LNumber

) {

return new Node\Scalar\String_($this->stringArray[$node->dim->value]);

}

return null;

}

}

/* translate all variable */

$nodeVisitor = new GlobalStringNodeVisitor($str1, $string_array);

$traverser = new NodeTraverser();

$traverser->addVisitor($nodeVisitor);

$ast = $traverser->traverse($ast);

$filename = "enphp";

$i = 3;

$dumper = new NodeDumper;

file_put_contents($filename.$i.".ast", $dumper->dump($ast));

$prettyPrinter = new PrettyPrinter\Standard;

file_put_contents($filename.$i.".php", $prettyPrinter->prettyPrintFile($ast));

生成的ast文件为enphp3.ast,php文件为enphp3.php

打开enphp3.php可以看到这一部分已经替换过来了,不过有些是这样的(‘string’),不符合编码习惯,还得处理一次,这里先不处理,因为方法里还有类似$var =& $_SERVER[òÎò]的代码,$var变量在函数内做操作,还需替换一次,这种代码的ast结构如下:

expr: Expr_AssignRef(

var: Expr_Variable(

name: á<8d>

)

expr: Expr_ArrayDimFetch(

var: Expr_Variable(

name: _SERVER

)

dim: Expr_ConstFetch(

name: Name(

parts: array(

0: òÎò

)

)

)

)

)

我们要把这种结构替换掉(php-parser的删除节点功能在最近的版本中好像去掉了,不能删除,所以我们替换成invalid code)

name: Scalar_String(

value: invalid code // 表示这一行是无效代码

)

同时,我们需要把$var变量都替换成全局变量($string_array)中对应的字符串

class LocalStringNodeVisitor extends NodeVisitorAbstract

{

protected $globalVariableName;

protected $stringArray = [];

protected $localArray = [];

public function __construct($globals_name, $string_array)

{

$this->globalVariableName = $globals_name;

$this->stringArray = $string_array;

}

public function enterNode(Node $node)

{

if ($node instanceof Node\Expr\AssignRef

&& $node->var instanceof Node\Expr\Variable

&& $node->expr instanceof Node\Expr\ArrayDimFetch

&& $node->expr->var instanceof Node\Expr\Variable

&& $node->expr->var->name == "_SERVER"

&& $node->expr->dim instanceof Node\Expr\ConstFetch

&& $node->expr->dim->name instanceof Node\Name

&& $node->expr->dim->name->parts[0] === $this->globalVariableName

) {

$this->localArray[$node->var->name] = 1;

return new Node\Scalar\String_('invalid code');

} elseif (

$node instanceof Node\Expr\ArrayDimFetch

&& $node->var instanceof Node\Expr\Variable

&& isset($this->localArray[$node->var->name])

&& $node->dim instanceof Node\Scalar\LNumber

) {

return new Node\Scalar\String_($this->stringArray[$node->dim->value]);

}

return null;

}

}

/* translate function local string */

$nodeVisitor = new LocalStringNodeVisitor($str1, $string_array);

$traverser = new NodeTraverser();

$traverser->addVisitor($nodeVisitor);

$ast = $traverser->traverse($ast);

$filename = "enphp";

$i = 4;

$dumper = new NodeDumper;

file_put_contents($filename.$i.".ast", $dumper->dump($ast));

$prettyPrinter = new PrettyPrinter\Standard;

file_put_contents($filename.$i.".php", $prettyPrinter->prettyPrintFile($ast));

生成的ast文件为enphp4.ast,php文件为enphp4.php

接下来我们把不符合规范的代码替换一下

class BeautifyNodeVisitor extends NodeVisitorAbstract

{

public function enterNode(Node $node)

{

if ($node instanceof Node\Expr\FuncCall

&& $node->name instanceof Node\Scalar\String_

) {

$node->name = new Node\Name($node->name->value);

}

return null;

}

}

/* beautify function string */

$nodeVisitor = new BeautifyNodeVisitor();

$traverser = new NodeTraverser();

$traverser->addVisitor($nodeVisitor);

$ast = $traverser->traverse($ast);

$filename = "enphp";

$i = 5;

$dumper = new NodeDumper;

file_put_contents($filename.$i.".ast", $dumper->dump($ast));

$prettyPrinter = new PrettyPrinter\Standard;

file_put_contents($filename.$i.".php", $prettyPrinter->prettyPrintFile($ast));

生成的ast文件为enphp5.ast,php文件为enphp5.php

这时候我们的代码中还有一些局部变量是乱码,只需要把局部变量名替换为个正常编码的字符串即可,增加可读性,代码如下

class LocalVarNodeVisitor extends NodeVisitorAbstract

{

protected $paramsArray = [];

protected $i = 0;

protected $varArray = [];

protected $k = 0;

public function enterNode(Node $node)

{

if ($node instanceof Node\Expr\Variable && $node->name != '_SERVER') {

if (!isset($this->varArray[$node->name])) {

$this->varArray[$node->name] = 'arg'.$this->k;

$this->k++;

}

$node->name = $this->varArray[$node->name];

}

if ($node instanceof Node\Stmt\Function_) {

$this->i = 0;

$this->k = 0;

}

return null;

}

}

/* translate function local variable */

$nodeVisitor = new LocalVarNodeVisitor();

$traverser = new NodeTraverser();

$traverser->addVisitor($nodeVisitor);

$ast = $traverser->traverse($ast);

$filename = "enphp";

$i = 6;

$dumper = new NodeDumper;

file_put_contents($filename.$i.".ast", $dumper->dump($ast));

$prettyPrinter = new PrettyPrinter\Standard;

file_put_contents($filename.$i.".php", $prettyPrinter->prettyPrintFile($ast));

生成的ast文件为enphp6.ast,php文件为enphp6.php

打开enphp6.php,后面的代码部分已经正常可读,只有上面的$_SERVER[òÎò] = explode…部分代码是乱码,怎么办呢?直接删掉就好了,代码中的$_SERVER[òÎò][*]我们都替换过了,所以这个全局变量没有用了,至此,代码已经解码完成(还有可能存在不符合编码规范的代码,已经不是乱码了,读者可自行处理或不处理)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值