老虎会游泳原创作品,未经授权禁止转载。
建议使用Linux进行尝试。在Windows中编译PHP非常困难。
Windows可在WSL中进行尝试,可从微软应用商店安装Debian或Ubuntu。
支持的PHP版本:
首先是 PHP 7.3 的教程,PHP 5.4 和 PHP 8.1 的在后面。
其他 PHP 版本也可用类似方法进行修改,不过不同 PHP 版本的compile_string
参数结构可能不同,所以代码需要相应调整。
几乎所有无需扩展就能运行的PHP脚本加密,最终都要靠eval()
函数来运行解密后的代码。
所以我们只需要修改eval()
函数,就能把解密后的代码直接输出。
根本不需要研究具体的加密方法,因为无论它怎么加密,它在运行的时候都会自行解密再调用eval()
,所以eval()
函数收到的代码始终是解密的。
eval()
函数在PHP源代码中定义为zend_op_array *compile_string(
,只要找到它并在里面添加输出代码,就能轻松实现解密。
为什么修改Zend/zend_language_scanner.l
而不是Zend/zend_language_scanner.c
?
因为Zend/zend_language_scanner.c
是通过Zend/zend_language_scanner.l
自动生成的,我们通常不想修改自动生成的文件。
修改Zend/zend_language_scanner.l
后为什么没有生效?
如果Zend/zend_language_scanner.l
的文件时间比Zend/zend_language_scanner.c
更旧就不会生效。你可以touch
它一下,更新文件时间,然后再编译,像这样:
touch Zend/zend_language_scanner.l
make -j$(nproc)
修改还是没有生效!
那你可以再把Zend/zend_language_scanner.c
修改一下,修改方法和Zend/zend_language_scanner.l
相同。两个都改了就一定能生效了。
为什么我的脚本不能解密?
有四种可能:
脚本和当前PHP版本不兼容。如果页面上出现“Fatal error”,通常是这个原因。换个PHP版本即可。
缺少解密需要的扩展。可以看终端中的错误提示,看缺少什么函数,然后在./configure
阶段添加所需扩展。
该代码需要授权才能运行。你的调试环境没有通过授权验证,所以代码还没运行到eval()
阶段就提前退出了。你必须首先可以运行代码,才能通过我这种方法解密。
你的脚本执行代码时不使用eval()
,所以也就无法用这种方法解密。
比如,只进行了混淆,没有进行加密的脚本,不需要eval()
就能直接执行。不过这样的脚本通常也不需要解密,直接用代码编辑器格式化一下代码,就能直接修改。如果打开文件后出现乱码,保存后文件无法使用,可以把编码切换到ISO 8859-1
(又名latin1
)再打开。因为ISO 8859-1
具有单字节0到255的所有码点,所以它实际上是二进制安全的,它能正常处理原始字节流而不产生任何数据丢失。至于被混淆弄乱的变量名那就没办法了,因为原始变量名信息永久丢失了,所以不可能还原。
此外如果你的脚本要求安装加密网站提供的扩展才能运行,那通常也无法用我这种方式解密。不过也不一定,如果解密扩展在内部使用compile_string()
函数运行代码,那也可以被该方法解密。此外还可以参考https://segmentfault.com/a/1190000007035295,里面提到了修改zend_op_array *compile_file(
函数来解密的方法。
输出了一堆东西,哪个是解密后的代码?
如果脚本没有引用其他文件,那么最后的输出应该是解密后的代码。在最终解密结果出现之前,还可能会出现一些中间结果,直接忽略即可。
如果脚本通过include
require
等引用了其他文件,那么最后输出的结果可能是其他文件的,脚本本身的解密结果会在中间,你得自己找一下。
对于中间结果,这里有一个例子:
为什么解密后的代码开头出现了多余的PHP结束标记(?>
)?
因为eval()
函数执行的是“PHP代码”。这句话的含义是:eval()
函数执行的代码,默认就处于PHP模式。
但是php文件默认并不处于PHP模式,而是处于HTML模式(遇到的内容直接输出),在遇到PHP开始标记(<?php
)后才会进入PHP模式(遇到的内容作为PHP代码执行)。
所以,要用eval()
执行一个PHP文件中的内容,就必须先退出PHP模式,回到HTML模式。加一个PHP结束标记(?>
)就能实现这个目的。这个方案没有任何副作用,无论?>
后面紧接着<?php
,还是先出现一段HTML代码,都能正常执行。
所以,复制解密结果时不复制开头的?>
就行啦,它是加密程序因为语法原因而加上的。
相信读了这段话之后,你对PHP的开始和结束标记会有全新的理解。实际上,PHP引擎只是简单的在遇到开始标记时进入PHP模式,在遇到结束标记时进入HTML模式,两者无需配对使用。
源代码太长,终端显示不完整怎么办?
你可以把结果重定向到文件:
../php-7.3.33/sapi/cli/php -S 0.0.0.0:8080 > eval.log
然后打开eval.log
查看完整解密结果。注意写入文件有缓存,所以必须关掉PHP进程才能看到完整结果。或者在浏览器里刷新一次也能让之前的内容出来。
如何关掉用命令启动的PHP服务器?
按 Ctrl + C 组合键。
打开 Zend/zend_language_scanner.l
,把以下代码修改成这样:
zend_op_array *compile_string(zval *source_string, char *filename)
{
zend_lex_state original_lex_state;
zend_op_array *op_array = NULL;
zval tmp;
// 把这些内容粘贴到这里
char *code = malloc(source_string->value.str->len + 1);
memcpy(code, source_string->value.str->val, source_string->value.str->len);
code[source_string->value.str->len] = 0;
printf("\n----- %s -----\n%s\n--------------------\n", filename, code);
free(code);
// 粘贴的内容结束
if (UNEXPECTED(Z_TYPE_P(source_string) != IS_STRING)) {
ZVAL_STR(&tmp, zval_get_string_func(source_string));
} else {
ZVAL_COPY(&tmp, source_string);
}
我只启用了zlib模块(提供gzuncompress()
函数),如果你还需要其他模块,自己在./configure --with-zlib
后面加参数。
这个帖子中的加密PHP需要gzuncompress()
函数才能解密。
# 安装依赖
sudo apt install -y build-essential gcc make pkg-config autoconf re2c bison flex openssl curl libbz2-dev libxml2-dev libsqlite3-dev libjpeg-dev libpng-dev libfreetype6-dev libzip-dev libcurl4-gnutls-dev libssl-dev zlib1g-dev
# 转到PHP源代码文件夹
cd php-7.3.33
# 配置
./configure --with-zlib
# 编译(要很久,耐心等待)
make -j$(nproc)
# 测试编译是否成功
./sapi/cli/php --version
# 回到php源代码文件夹的上级目录
cd ..
# 创建一个网站根目录文件夹
mkdir www
# 转到网站根目录文件夹
cd www
# 启动PHP调试服务器
../php-7.3.33/sapi/cli/php -S 0.0.0.0:8080
现在把要解密的php放进www文件夹,然后再访问http://127.0.0.1:8080/文件名.php
,解密后的结果就会在终端输出。
比如解密该文件:h.php(4.44 KB)
把它放在www文件夹,然后访问 http://127.0.0.1:8080/h.php
啥也没有???
没关系,我们看终端:
哈哈,解密后的源代码就这么自动出现了。加密用尽心机,解密毫不费力。
修改 Zend/acinclude.m4
if test -n "$bison_version_vars"; then
set $bison_version_vars
bison_version="${1}.${2}"
for bison_check_version in $bison_version_list; do
if test "$bison_version" = "$bison_check_version"; then
php_cv_bison_version="$bison_check_version (ok)"
break
fi
done
# 把这行内容粘贴到这里
php_cv_bison_version="$bison_version (ok)"
# 粘贴的内容结束
fi
Zend/zend_language_scanner.l
zend_op_array *compile_string(zval *source_string, char *filename TSRMLS_DC)
{
zend_lex_state original_lex_state;
zend_op_array *op_array = (zend_op_array *) emalloc(sizeof(zend_op_array));
zend_op_array *original_active_op_array = CG(active_op_array);
zend_op_array *retval;
zval tmp;
int compiler_result;
zend_bool original_in_compilation = CG(in_compilation);
// 把这些内容粘贴到这里
char *code = malloc(source_string->value.str.len + 1);
memcpy(code, source_string->value.str.val, source_string->value.str.len);
code[source_string->value.str.len] = 0;
printf("\n----- %s -----\n%s\n--------------------\n", filename, code);
free(code);
// 粘贴的内容结束
if (source_string->value.str.len==0) {
efree(op_array);
return NULL;
}
我只启用了zlib模块(提供gzuncompress()
函数),如果你还需要其他模块,自己在./configure --with-zlib
后面加参数。
# 安装依赖
sudo apt install -y build-essential gcc make pkg-config autoconf re2c bison flex openssl curl libbz2-dev libxml2-dev libsqlite3-dev libjpeg-dev libpng-dev libfreetype6-dev libzip-dev libcurl4-gnutls-dev libssl-dev zlib1g-dev
# 转到PHP源代码文件夹
cd php-5.4.43
# 配置
./configure --with-zlib
# 编译(要很久,耐心等待)
make -j$(nproc)
# 测试编译是否成功
./sapi/cli/php --version
# 回到php源代码文件夹的上级目录
cd ..
# 创建一个网站根目录文件夹
mkdir www
# 转到网站根目录文件夹
cd www
# 启动PHP调试服务器
../php-5.4.43/sapi/cli/php -S 0.0.0.0:8080
现在把要解密的php放进www文件夹,然后再访问http://127.0.0.1:8080/文件名.php
,解密后的结果就会在终端输出。
比如解密该文件:h.php(4.44 KB)
把它放在www文件夹,然后访问 http://127.0.0.1:8080/h.php
修改 Zend/zend_language_scanner.l
zend_op_array *compile_string(zend_string *source_string, const char *filename)
{
zend_lex_state original_lex_state;
zend_op_array *op_array = NULL;
zval tmp;
zend_string *filename_str;
// 把这些内容粘贴到这里
char *code = malloc(source_string->len + 1);
memcpy(code, source_string->val, source_string->len);
code[source_string->len] = 0;
printf("\n----- %s -----\n%s\n--------------------\n", filename, code);
free(code);
// 粘贴的内容结束
if (ZSTR_LEN(source_string) == 0) {
return NULL;
}
我只启用了zlib模块(提供gzuncompress()
函数),如果你还需要其他模块,自己在./configure --with-zlib
后面加参数。
# 安装依赖
sudo apt install -y build-essential gcc make pkg-config autoconf re2c bison flex openssl curl libbz2-dev libxml2-dev libsqlite3-dev libjpeg-dev libpng-dev libfreetype6-dev libzip-dev libcurl4-gnutls-dev libssl-dev zlib1g-dev
# 转到PHP源代码文件夹
cd php-8.1.0
# 配置
./configure --with-zlib
# 编译(要很久,耐心等待)
make -j$(nproc)
# 测试编译是否成功
./sapi/cli/php --version
# 回到php源代码文件夹的上级目录
cd ..
# 创建一个网站根目录文件夹
mkdir www
# 转到网站根目录文件夹
cd www
# 启动PHP调试服务器
../php-8.1.0/sapi/cli/php -S 0.0.0.0:8080
现在把要解密的php放进www文件夹,然后再访问http://127.0.0.1:8080/文件名.php
,解密后的结果就会在终端输出。
比如解密该文件:a.php(4.04 KB)
把它放在www文件夹,然后访问 http://127.0.0.1:8080/a.php
备注:先前的测试脚本h.php
和PHP8不兼容,不必尝试。
给力
xinq.net