『代码审计』如何解密 PHP 代码
【推荐学习】暗月渗透测试培训 十多年渗透经验,体系化培训渗透测试 、高效学习渗透测试,欢迎添加微信好友aptimeok 咨询。
0x00 写在前面
sg11
全称为SourceGuardian
,是PHP
下的加密扩展,通过将PHP
源代码编译为字节码格式,然后编译为加密层,来保护PHP
代码。
目前互联网上并没有详细的解密教程,经过再三搜索,找到的多为留下QQ
人工解密,单文件价格高达150-200
元。
至于为什么会想到尝试解密它,故事要从这里开始讲起………
那晚,我的一个不愿意透露姓名的朋友找到我,发来一个压缩包,是一个知名发卡平台的源码。
他问我:审一下,看看能搞吗?😟
但是我回答:
我能受这气????
后来,我们尝试了一晚上的解密,最终还是以失败告终。
再后来,就有了这篇文章。
0x01 环境准备
在之前的尝试中,我们发现,SourceGuardian
对加解密环境要求十分严格,加解密环境必须一致。
并且,ubuntu18
的LIBC
版本不适配SourceGuardian
加密程序,会导致报错。
所以,本文的测试环境为:
Ubuntu 20.04.1
PHP 7.4.3
apt install libjpeg62-dev
apt install php7.4-dev
SourceGuardian
本身是收费软件,但是提供14
天免费试用。
首先打开官网https://www.sourceguardian.com/
点击首页的 Try it Now
目前它的版本已经更新到了13
,我们选择 SourceGuardian 13 Evaluation for Linux
。
之后填写信息进行注册。
注册完成后,可进行下载,有命令行版本和GUI
版本,我们选择 SourceGuardian 13 Evaluation for Linux GUI
。
下载完成后,将压缩包解压,之后运行install-menu-icons.sh
,这一步是为了在桌面创建快捷方式。
运行完成后,会在桌面生成一个 SourceGuardian.desktop
。
右键,选择 Allow Launching
,即可双击运行。
或者可通过文件夹中的RUNME.sh
脚本,进行命令行启动。
在安装完加密程序(encoder
)之后,还需要运行加密文件所需的加载器(loader
)。
访问网站https://www.sourceguardian.com/loaders/download/loaders.linux-x86_64.tar.gz
,进行下载。
解压后,找到对应版本的文件ixed.7.4.lin
,将其放入PHP
的lib
目录,例如:/usr/lib/php/20190902/
之后在php.ini
中,加入extension=ixed.7.4.lin
注意,php.ini
会有两个文件,分别对应Apache
和命令行,建议都添加上。
php.ini
路径为:
/etc/php/7.4/apache2/php.ini
/etc/php/7.4/cli/php.ini
接下来需要安装解密和调试PHP
文件所需的扩展vld
。
vld
是PECL
(PHP
扩展和应用仓库)的一个PHP
扩展,现在最新版本是 0.17.2
(2021-11-30
),它的作用是:显示转储PHP
脚本(opcode
)的内部表示。
简单来说就是,可以查看PHP
程序的opcode
。
访问网站http://pecl.php.net/get/vld-0.17.0.tgz
进行下载,解压。
在之前的测试中发现,官方版本的vld
扩展在对sg11
加密文件进行调试时,并不会显示完整的opcode
。
于是我们需要下载补丁,对vld
进行魔改。
补丁地址:https://github.com/clouds-flight/php7-vld-sg11-patch
将vld_patch.c
和srm_oparray_patch.c
拷贝到vld
扩展源码目录下。
在命令行中执行:
patch -p0 < vld_patch.c
patch -p0 < srm_oparray_patch.c
phpize
./configure
make && make install

安装完成后,会显示扩展安装后的路径,正常应该是PHP
的扩展目录。
同样,需要在php.ini
中,加入extension=vld.so
。
以上,前期环境准备全部完成。
Ubuntu 20.04.1
PHP 7.4.3
apt install libjpeg62-dev
apt install php7.4-dev
SourceGuardian 13 Evaluation for Linux GUI
ixed.7.4.lin
vld 0.17.0
0x02 加密解密
2.1 加密
加密的流程还是比较简单的。
双击打开加密程序,输入注册时的账号密码进行授权。
点击 File
-> New
-> add
,添加一个需要加密的PHP
文件,之后选择一个加密后文件的保存目录即可。
加密后的文件有明显的特征,例如:sg_load
。
并且,文件可以正常运行。
2.2 解密
之前说过,目前互联网上没有自动化的解密工具,大部分解密都是人工解密,导致价格很高。
并且也没有一篇完整的解密相关的文档,大部分都是引流。
具体原因,应该是解密流程过于复杂。
解密,就用到了上面提到的vld
扩展,我们需要根据opcode
,人工还原代码。
命令如下:
php -dvld.active=1 -dvld.execute=0 test.php
其中:
dvld.active=1
表示启用vld
扩展,dvld.execute=0
表示不执行PHP
文件。
这两个参数加在一起,就表示只显示opcode
。
运行结果很长,我们把它分成三部分来看。
✍️首先是第一部分:
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
2 0 E > INIT_FCALL 'error_reporting'
1 SEND_VAL 0
2 DO_FCALL
28 3 INIT_FCALL 'unserialize'
4 FETCH_R global ~1 '_POST'
5 FETCH_DIM_R ~2 ~1,url
6 SEND_VAL ~2
7 DO_FCALL
31 8 > RETURN 1
先看列名,我们主要关注的是line
、op
和operands
。
line
表示当前对应的是哪一行php
语句。
op
则是opcode
。
而operands
是具体描述。
举个栗子🌰,例如前三行。
INIT_FCALL
准备了执行函数时所需要的上下文数据。
DO_FCALL
负责执行函数。
SEND_VAL
表示函数调用时传递值作为参数。
综合以上信息,可知:
error_reporting(0);
并且在文件的第二行。
而分析第一部分内容,不难得出以下代码👇
error_reporting(0);
class Welcome {
function gogogo() {}
}
.
.
.
unserialize($_POST['url']);
之后,继续分析下一部分内容:
这部分内容为gogogo
函数。
具体opcode
的功能解释,可参考OPCODE的功能列表
http://dezend.qiling.org/985.html
文中不再赘述了哦~
需要注意的是compiled vars
,里面表明了所用到的变量名。
标号0
是函数传入的参数,$url
。
而标号12
的指令含义是条件跳转,也可以理解为if
,而它一直持续到了最后。
也就是说明,这其中的代码都在if
的花括号里面。
而1-11
则是if
的条件。
分析结果如下:
error_reporting(0);
class Welcome {
function gogogo($url) {
if(!preg_match("/file|ftp/i", $url) && preg_match("/^w+://127.0.0.1/i", $url)) {
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
}
}
unserialize($_POST['url']);
现在,继续分析最后一部分内容。
这一部分的主要内容是Class
中的__wakeup
和__destruct
两个函数。
内容很少,其中__wakeup
是直接进行了赋值操作。
__destruct
则是进行了一个简单的判断,判断url
是否存在。
至此,分析结束。
但是,一直到最后,我们并没有发现类中定义变量的操作。
所以,还需要以下操作:
include('./test.php');
$a = new Welcome();
$arr = get_defined_vars();
#$func=get_defined_functions();
var_dump($arr['a']);
#var_dump($func);
结果如下:
object(Welcome)#1 (1) {
["url":protected]=>
NULL
}
所以,class
中定义了protected
类型的url
变量。
复原代码如下:
error_reporting(0);
class Welcome {
protected $url;
function gogogo($url) {
if(!preg_match("/file|ftp/i", $url) && preg_match("/^w+://127.0.0.1/i", $url)) {
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
}
function __wakeup() {
$this->url='http://127.0.0.1/index.php';
}
function __destruct() {
if(!empty($this->url)) {
$this->gogogo($this->url);
}
}
}
unserialize($_POST['url']);
0x03 写在最后
文章中提到的样例,是最理想的状态。
如果在opcode
不能明显看出具体调用的话,则可能需要:
include('./test.php');
$func=get_defined_functions();
$arr = get_defined_vars();
这就需要更加细心,也更耗费时间和精力。
总的来说,vld
魔改之后,对于解密sg11
的帮助是很大的。
只要稍微看懂一点opcode
,就可以大概复原出代码逻辑。
本文只是起到一个入门级的指引作用,作者本身对于opcode
和sg11
的原理并不是很了解。
之所以写这篇文章,也只是记录一下自己的学习流程。
文章所述,仅供参考,如有错误,欢迎指正。
References
http://dezend.qiling.org/985.html
免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。
原创文章,作者:moonsec,如若转载,请注明出处:https://www.moonsec.com/7103.html