78模板网分享cms建站教程,提供网站模板、网站插件、办公模板等模板教程免费学习,找模板教程就上78模板网!

PHP 解密:EnPHP 混淆加密

下面两段话摘录自 EnPHP 官方网站

加密、混淆#

EnPHP 支持加密混淆 PHP 代码。

EnPHP 可以对函数、参数、变量、常量以及内嵌 HTML 代码进行加密、混淆。

支持不同的加密强度、混淆方式

EnPHP 可以开心吗?#

代码,机器能解析就能还原,您使用任何一个加密工具都会有这个风险,理论上 EnPHP 被还原代码部分是可以的,但是 EnPHP 主打是的混淆+加密,打散、混淆才是 EnPHP的核心,EnPHP 是根据语法进行打散和混淆的,就算解密后,也是不可能还原变量名的!!!除非重新读一遍代码,将变量重新写上去。所以,那些所谓开心,是不可能还原语法和变量名的。如果您需要高强度的加密,可以联系管理员订制化加密。

我们的结论#

我们应该是能还原代码的,但是不能还原变量名、函数名、方法名。如果想还原成原始代码是不可能了,但是如果只是想改一个注册码验证之类的,应该还是比较简单的。

分析过程#

简单分析一下原理#

我使用 VSCode 打开这个文件,这个文件全都是不可读字符,如果用 UTF-8 来显示的话很不友好。

使用 Ctrl + Shift + P 打开快捷指令,输入 encoding,选择用 Change File Encoding,选择 Reopen with Encoding,选择 Western (Windows 1252)。

Windows 1252 是个单字节的字节集,不会出现任何 2 个字节被显示成 1 个字符的问题,其他的单字节集通常也可以。

我们只看代码部分,不看乱码部分。

PHP 解密:EnPHP 混淆加密  解密 EnPHP 第1张代码概况

1
2
3
4
5
6
7
error_reporting(E_ALL ^ E_NOTICE);
define('字符串1', '字符串2');
一堆乱码1;
$GLOBALS[字符串1] = explode('字符串3', gzinflate(substr('字符串4', 0x0a, -8)));
一堆乱码2;
include $GLOBALS{字符串1}[0];
include $GLOBALS{字符串1}{0x001}(__FILE__) . $GLOBALS{字符串1}[0x0002];

解释一下我们分析出来的代码的含义

  1. 抑制错误显示

  2. 定义一个全局常量作为被加密字符串储存的名称

  3. 一个不知道什么常量,毫无意义

  4. gzinflate 就是 gzip 解压缩,把一个二进制的字节串还原成原始字符串,并用 explode 分成一堆小字符串。

  5. 一个不知道什么常量,毫无意义

  6. $GLOBALS{字符串1} 就是那一堆小字符串的储存位置,从中提取出第一个元素 $GLOBALS{字符串1}[0] 就是我们要还原的内容了。

PHP-Parser#

既然是乱码,我们又得请出我们的重量级选手了 PHP-Parser

这个库的作者是 nikic,其实他是 PHP 核心开发组的人员,这个解释器真的堪称完美。

新建一个文件夹作为这个工程的文件夹

创建 Composer 文件,安装 PHP-Parser

1
2
composer init
composer require nikic/php-parser

然后新建一个 index.php 先把 AST 解析写好。

这个初始代码来自 https://github.com/nikic/php-parser#quick-start

看乱码我用 VSCode,但是写代码我还是选择 PHPStorm。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

<?php
use PhpParserError;
use PhpParserNodeDumper;
use PhpParserParserFactory;

require 'vendor/autoload.php';

$code = file_get_contents(__DIR__ . '/tests//images/2019-03-14-enphp-decode/admin.php');

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

try {
   $ast = $parser->parse($code);
} catch (Error $error) {
   echo "Parse error: {$error->getMessage()}n";
   return;
}

$dumper = new NodeDumper;
echo $dumper->dump($ast) . "n";

找出有用的参数#

我们必须从 AST 中把有用信息提取出来。

什么是有用的呢?

就是上面的字符串 1、3、4,不包括字符串 2,因为代码中根本就没用到,他只是一个临时的变量名称。还有 substr 的参数 0x0a 和 -8

我们根据他在 AST 中的位置编写代码

1
2
3
4
5
$str1 = $ast[1]->expr->args[0]->value->value;
$str3 = $ast[3]->expr->expr->args[0]->value->value;
$str4 = $ast[3]->expr->expr->args[1]->value->args[0]->value->args[0]->value->value;
$int1 = $ast[3]->expr->expr->args[1]->value->args[0]->value->args[1]->value->value;
$int2 = -$ast[3]->expr->expr->args[1]->value->args[0]->value->args[2]->value->expr->value;

如何知道他的位置?

PHP 解密:EnPHP 混淆加密  解密 EnPHP 第2张代码调试 1

PHP 解密:EnPHP 混淆加密  解密 EnPHP 第3张代码调试 2

调试必须得配置好 XDebug,配置过程请自行百度。

PHP 解密:EnPHP 混淆加密  解密 EnPHP 第4张代码调试 3

然后就可以得到 $ast[1]->expr->args[0]->value->value 这个了。

先看看解密之后的字符串是什么样子的#

在原来的代码之后添加

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

再次调试,看调试输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Array
(
   [0] => config.php
   [1] => dirname
   [2] => /../include/class.db.php
   [3] => filter_has_var
   [4] => type
   [5] => json_encode
   [6] => success
   [7] => icon
   [8] => m
   [9] => 请勿非法调用!
   [10] => filter_input
......
   [152] => id错误,没有找到id!
   [153] => ua
   [154] => Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36
   [155] => curl
)

的确不出所料,我们要的字符串都出来了。

逐步还原#

我们需要把代码中所有的 $GLOBALS{字符串1}[0],都换成原来的字符串。

我们需要用到 NodeTraverser 了,他负责遍历 AST 的每一个节点

当他发现任何一个 Node 是下面这种结构的时候

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Expr_ArrayDimFetch(
   var: Expr_ArrayDimFetch(
       var: Expr_Variable(
           name: GLOBALS
       )
       dim: Expr_ConstFetch(
           name: Name(
               parts: array(
                   0: �
               )
           )
       )
   )
   dim: Scalar_LNumber(
       value: 0
   )
)

他将直接把这个 Node 替换成

1
2
3
Scalar_String(
   value: $string_array[0]
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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 leaveNode(Node $node)
   {
       if ($node instanceof NodeExprArrayDimFetch
           && $node->var instanceof NodeExprArrayDimFetch
           && $node->var->var instanceof NodeExprVariable
           && $node->var->var->name === 'GLOBALS'
           && $node->var->dim instanceof NodeExprConstFetch
           && $node->var->dim->name instanceof NodeName
           && $node->var->dim->name->parts[0] === $this->globalVariableName
           && $node->dim instanceof NodeScalarLNumber
       ) {
           return new NodeScalarString_($this->stringArray[$node->dim->value]);
       }
       return null;
   }
}

$nodeVisitor = new GlobalStringNodeVisitor($str1, $string_array);
$traverser = new NodeTraverser();
$traverser->addVisitor($nodeVisitor);
$ast = $traverser->traverse($ast);

$prettyPrinter = new Standard;
echo $prettyPrinter->prettyPrintFile($ast);

运行结果

PHP 解密:EnPHP 混淆加密  解密 EnPHP 第5张初步运行结果 1

美化代码#

我们看到 ('dirname')(__FILE__) 这种代码不太符合正常代码书写习惯,我们需要把它改成 dirname(__FILE__)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class BeautifyNodeVisitor extends NodeVisitorAbstract
{
   public function enterNode(Node $node)
   {
       if ($node instanceof NodeExprFuncCall
           && $node->name instanceof NodeScalarString_) {
           $node->name = new NodeName($node->name->value);
       }
       return null;
   }
}

$nodeVisitor = new BeautifyNodeVisitor();
$traverser = new NodeTraverser();
$traverser->addVisitor($nodeVisitor);
$ast = $traverser->traverse($ast);

运行结果

PHP 解密:EnPHP 混淆加密  解密 EnPHP 第6张代码美化

函数内部字符串#

前面全局部分的代码看上去还不错,但是后面的函数内部代码还是有些乱码

PHP 解密:EnPHP 混淆加密  解密 EnPHP 第7张初步运行结果 2

它使用一个 $局部变量1 =& $GLOBALS[字符串1] 把这个全局变量变成局部变量了,我们必须遍历所有函数,把这些字符串替换掉。

原理就是发现 $局部变量1 =& $GLOBALS[字符串1] 则把 局部变量1 保存下来,之后再发现 $局部变量1[0] 则替换成 $string_array[0]

(代码较长,此处省略)

运行结果

PHP 解密:EnPHP 混淆加密  解密 EnPHP 第8张函数内字符串

函数局部变量名#

这里的原理就是把所有参数名统一替换成 $arg0$arg1,所有变量名统一替换成 $v0$v1

(代码较长,此处省略)

类的方法#

由于样例文件中没有包含类的方法,所以对类方法变量名的去除乱码可能并不是很好。

去除无用常量语句#

这个代码里面有一堆无用的调用常量的语句,完全不知道是干什么的,毫无意义,去掉。

(代码较长,此处省略)

PHP 解密:EnPHP 混淆加密  解密 EnPHP 第9张最终结果

自动寻找全局字符串变量#

1
2
3
4
5
$str1 = $ast[1]->expr->args[0]->value->value;
$str3 = $ast[3]->expr->expr->args[0]->value->value;
$str4 = $ast[3]->expr->expr->args[1]->value->args[0]->value->args[0]->value->value;
$int1 = $ast[3]->expr->expr->args[1]->value->args[0]->value->args[1]->value->value;
$int2 = -$ast[3]->expr->expr->args[1]->value->args[0]->value->args[2]->value->expr->value;

这个代码用的是固定的 $ast[1]$ast[3],但是实际上他并不一定总是 1 或 3,所以我们做得适用性强一些。自动寻找

1
$GLOBALS[globalVarName] = explode('delimiter', gzinflate(substr('data', start, -length)))

这个句话的位置。

总结#

只有局部变量的变量名不能还原。

其他所有的标识符(函数名、类名、方法名、函数调用、常量名)、字符串、数字全部都能成功还原。

代码结构完全没有加密,只需替换被混淆的名称即可。


本文链接:http://78moban.cn/post/14271.html

版权声明:站内所有文章皆来自网络转载,只供模板演示使用,并无任何其它意义!

联系技术
文章删除 友链合作 技术交流群
1050177837
公众号
公众号
公众号
返回顶部