听说“加密效果同行最高”?

到 http://www.phpjiami.com/phpjiami.html 随意上传一个 php 文件,然后下载加密后的文件,这便是我们要解密的文件。

大略剖析一下

先看看加密后的文件

phpgzuncompressITins分享一下某PHP加密文件调试解密进程 React

可以看出这是一个正常的 php 文件,只不过所有的变量名都是乱码,还真亏了 php 引擎支持任意字符集的变量名,这个加密后的文件变量名的字节部都在 ASCII 范围以外,全是 0x80以上的字符。

我们看到中间有一个 php 代码段结束标签 ?>,而他的前面还有一个 return $xxx;来结束脚本运行,这解释结束标签后面的数据都不会被正常输出,后面极可能是源文件加密后的数据,而前面的 php 代码只是用来解密的。

调试之前的准备

这里利用的 IDE 是 VSCode(最开始我利用的是 PHPStorm,后来我创造 VSCode的效果更好)。
首先,安装 PHP Debug 插件。

然后,按照 https://xdebug.org/docs/install 的解释安装 XDebug插件。

把稳:运行未知的 php 代码还是很危险的,最好能在虚拟机上运行,真机上一定要担保你的 XDebug和 PHP Debug调试插件可以正常下断点。
断开网络。
最好同时打开任务管理器,一旦发生未知征象(比如 CPU 占用率或磁盘占用率),或者调试断点没断下来,或者涌现某些问题,急速结束 php 进程。

开始调试

代码格式化

这个代码太乱了,我们须要格式化一下代码。

最开始我用的是 PHPStorm 自带的代码格式化,格式化之后数据变了,PHPStorm 对未知字符集的支持还是比较差的。

然后我就想对 php 文件的 AST (Abstract Syntax Tree 抽象语法树)进行剖析,看能不能顺便把变量名都改成可显示字符。
后来想想彷佛弗成,由于这种代码肯定是带 eval 的,改了变量名之后,eval 的字符串中的变量名就对应不上了。

我找到了这个工具:https://github.com/nikic/PHP-Parser

首先 composer require nikic/php-parser。

然后将下列代码保存到一个文件中(比如 format.php),读取下载下来的 1.php,把格式化之后的代码写入 2.php。

然后,实行 php format.php。

利用这个方法格式化的 php 文件内容并没有被破坏,我们可以连续剖析了。

如果,还弗成,那就只能用十六进制编辑器查找 ; 和 } 手动更换了,添加 \r\n 了。

调试

最前面这两行我们得先注释掉,不然出了什么缺点的话会莫名其妙的。

error_reporting(0);

ini_set(\公众display_errors\"大众, 0);

保存。
然后塌台了,代码又乱了。

我们须要一个支持非可显示字符的编辑器,或者...变动显示编码,选择一个不是多字节的字符集,比如 Western (ISO 8859-1)

现在,开始我们的调试。

在第一行下断点。
实行 php 2.php运行程序。
然后单步调试,一边实行,一边把稳变量的值,剖析函数的实行流程。

利用 VSCode的调试功能,我们可以方便的查看变量的详细内容。

单步调试到这一行,彷佛有些不对劲。

php_sapi_name() == 'cli' ? die() : '';

我们用命令走运行的,以是实行完这一句,肯定程序就结束了。

那就让他结束吧,我们把这一行注释掉,在他下面下断点。
重新运行程序。

下面这行是便是读取当前文件,这句话没有什么问题。

$f = file_get_contents(constant('rnfzwpch'));

然后就又是验证运行环境。

if(!isset($_SERVER['HTTP_HOST']) !isset($_SERVER['SERVER_ADDR']) && !isset($_SERVER['REMOTE_ADDR'])) {

die();

}

注释掉,保存,重新运行。

当然,也可以通过调试掌握台,实行类似 $_SERVER['HTTP_HOST'] = '127.0.0.1'; 这类指令,来让验证通过。

再看下面的代码,我想到 exe 反调试了,不得不佩服想这个方法的人。
防止下断点调试的,如果下断点调试,这里就超过 100 毫秒了。

$t = microtime(true) 1000;eval(\"大众\公众);if (microtime(true) 1000 - $t > 100) {

die();

}

我们直接在这条语句之后下断点,让他们持续串实行完,这样就不会超过 100 毫秒了。
当然,直接注释掉是最粗暴的方法。

下面的 eval我们须要通过“单步进入”来研究,不过结果是对我们的影响不大,当然注释掉也没问题。

接下来这个便是校验数据完全性的了

!strpos(decode_func(substr($f, -45, -1)), md5(substr($f, 0, -46))) ? $undefined1() : $undefined2;

这里的$undefined1和 $undefined2都没有定义。
如果验证失落败,就会调用 $undefined1会直接 Error退出程序。
而如果验证成功,虽然 $undefined2变量不存在,但是只是一个 Warning,并没有太大问题。
decode_func便是文件中末了一个函数,专门卖力字符串解码的。

这个验证方法便是把文件尾部分解密和前面的文件主体部分的 md5 比拟,这次实行肯定又不能通过。

退出程序,注释掉,再重新运行。

$decrypted = str_rot13(@gzuncompress(decode_func(substr($f, -2358, -46))));

我们找到了这个解码的关键语句了,可以看到解密之后的代码已经出来了。

到了代码的末了,终于要实行脚本了。

$f_varname = '_f_';$decrypted = check_and_decrypt(${$f_varname});

set_include_path(dirname(${$f_varname}));$base64_encoded_decrypted = base64_encode($decrypted);$eval_string = 'eval(base64_decode($base64_encoded_decrypted));';$result = eval($eval_string);

set_include_path(dirname(${$f_varname}));return $result;

折腾了半天,还是 eval语句。
如何把内容输出呢。
直接在 $decrypted后面加上一行 file_put_contents就可以了。

成果

通用解密程序

我们可以连续剖析一下他的解密算法

算法是固定的,只是个中内联了一个秘钥,我们只要通过字符串函数截取出这个秘钥就可以了。

末了的解码程序如下。

这个程序可以解密此网站全部免费加密的代码。

利用方法:php decrypt.php 1.php

总结

php 这种动态阐明措辞还想加密?做梦去吧。
不过稠浊还是有可能的。

这个代码中的暗桩挺故意思,算是学到了点知识。

php 这种东西为什么要加密?php 的开源社区多么弘大。

附录

代码赏析