听说“加密效果同行最高”?
到 http://www.phpjiami.com/phpjiami.html 随意上传一个 php 文件,然后下载加密后的文件,这便是我们要解密的文件。
大略剖析一下
先看看加密后的文件
可以看出这是一个正常的 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 的开源社区多么弘大。
附录代码赏析