PHPJM混淆解解析与还原

admin 2026-04-10 03:27:00 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文分析phpjm.net平台的PHP代码混淆机制,通过逆向工程揭示其基于base64解码与字符串替换的加密逻辑,发现该混淆仅兼容PHP5且存在固定解密模式。研究提供了具体的逆向分析方法和解密步骤,并给出安全建议。 综合评分: 85 文章分类: 漏洞分析,代码审计,WEB安全,逆向分析,恶意软件


cover_image

PHPJM混淆解解析与还原

原创

Syn3x Syn3x

UNSAFE-TEAM

2026年4月3日 21:10 北京

写在前面

最近在工作中遇到了一些高度混淆的PHP恶意文件,经分析发现其使用了第三方通用加密平台提供的加密服务,文件特征分别对应 phpjm.net 与 phpjiami.com 两个平台。本文以 phpjm.net 为研究对象,对其加密机制展开学习与分析,并进行解密还原。

混淆分析

先编写一段示例代码用来做测试,代码如下

● ● ●

<?php
if&nbsp;($_GET['display'] ==&nbsp;true) {
&nbsp; &nbsp;&nbsp;phpinfo();
}

需要注意的是phpjm.net的加密算法只能使用php<7的版本使用,根据报错推测原因是加密过程中会掺杂一些随机的字符,而这些字符对于php7来说都是非法字符,识别上较为严格,无法执行,php5则会静默忽略或跳过。经过phpjm混淆过后会变成了下面样子方法名字,变量名字全都已经被不可见字符混淆过了,在vsc和phpstorm中显示的都是。分析前使用phpstorm进行格式化一下,分析起来会更加方便一些,格式化后的代码如下

● ● ●

<?php
/*
������������Ϣ�����DZ�php�ļ������ߣ����Ա��ļ�����������Ϣֻ���ṩ�˶Ա�php�ļ����ܡ������Ҫ��PHP�ļ����м��ܣ��밴������Ϣ��ϵ��
Warning: do not modify this file, otherwise may cause the program to run.
QQ: 1833596
Website: http://www.phpjm.net/
Copyright (c) 2012-2026 phpjm.net All Rights Reserved.
*/
if&nbsp;(!defined("ECFFAFDC")) {
&nbsp; &nbsp;&nbsp;define("ECFFAFDC",&nbsp;__FILE__);
&nbsp; &nbsp;&nbsp;global&nbsp;$�, $��, $���, $����, $�����, $������, $�������, $��������, $���������, $����������, $����������, $������������, $�������������, $��������������, $���������������, $���������������;
&nbsp; &nbsp;&nbsp;function&nbsp;��($��, $��� =&nbsp;"")
&nbsp; &nbsp;&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;global&nbsp;$�, $��, $���, $����, $�����, $������, $�������, $��������, $���������, $����������, $����������, $������������, $�������������, $��������������, $���������������, $���������������;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(empty($���)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnbase64_decode($��);
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;��($����������($��, $���, $����($���)));
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

&nbsp; &nbsp; $���� = ��("c3RycmV2�");
&nbsp; &nbsp; $���������� = ��("c3RydHI=�");
&nbsp; &nbsp; $�� = ��("G3p1bwNvbXByGX�Nz�",&nbsp;"ZwCmG");
&nbsp; &nbsp; $��� = ��("SzYzOWIzMmY0MaMwED�FjYjY2MDU0YzlhYTUy�NDNhODgwS�2U=�",&nbsp;"LaZEGS");
&nbsp; &nbsp; $�������� = ��("NXNhbA==�",&nbsp;"ZWHON");
&nbsp; &nbsp; $��������� = ��("UmFzZTU0�X2RlU29k�ZC==�",&nbsp;"YCvGgJQU");
&nbsp; &nbsp; $��������������� = ��("IHJlZ19yZXBsYVNl�",&nbsp;"ctWOLdVhI");
&nbsp; &nbsp;&nbsp;function&nbsp;����(&$����)
&nbsp; &nbsp;&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;global&nbsp;$�, $��, $���, $����, $�����, $������, $�������, $��������, $���������, $����������, $����������, $������������, $�������������, $��������������, $���������������, $���������������;
&nbsp; &nbsp; &nbsp; &nbsp; $���������������� = ��("SGLL�",&nbsp;"ZTLNlCS");
&nbsp; &nbsp; &nbsp; &nbsp; @$���������������($���, $�������� .&nbsp;"(@$��($���������('eNptkm9P2lAU�xr8KaXhxm1Wl�FgeE3GzMqNgs�mDIchW0h9A9Q�pQsKQ7tpplAM�0iGFFoqA0I+6�3hbJCHt5z/M7�555znuM3oGED�jJYjV3Q6CFWM�wLLXF1yVxvCo�v6M+Grqr19nS�FXcUOa8zEE4Q�wzP7dYSo2lR7�7Ho14tlyJs3A�PtJLR9+V+EnM�LWPoU2tozl2o�JperNTZREeVT�aCIww9drCBoO�dH0+WfS1qcsl�qc9SkuKVscPw�qYt0EjFdtTFo�tpptoz1vuRSf�ZsieQ3Cf9i9L�Z4c0j6jnXtN8�Gt11Fo3pg9d9�Jb1XyLC0kmH1�c3fEPF9M/mCk�CsKnrRfrSVON�3tgazu5cXs4q�VY5SyjPUIHOs�JCRBRmi/83vc�th7Ml4k6GkwM�b3XFOF1WyEgt�w6rJs3wMwmfU�snIgnaayRXd8�G+Zyh8cfD3K5�qFQAG1UAtgO2�37zDv4IdjHBo�wq/1bBz/5R8M�7rs2RK8v5Lfo�rViuiqugYUdv�l8tHL+DFCR92�ya2ccwSPAK8k�8erFksc35l+h�a5aAZUFii6Rw�CNeUf00Ba8p/�8gPEFrWLb2O8�EBYEURQLITL0�Npjf3QtSlBAu�hIJhIVAIiCGM�IHHc/c13c+Pb�WJkNnPbuR6YN�3zvrAf6ZqS4a�uvpHBW4UjbUK�2bAmySJK0Hre�NVBZkpMTdY79�kKpYbOJnC9ms�xFLoHP4CagIz�Aw==�')));",&nbsp;"���
������639b32f40c0d1cb66054c9aa5243a880�����");
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return"/";
&nbsp; &nbsp; }
}&nbsp;else&nbsp;{
&nbsp; &nbsp;&nbsp;global&nbsp;$�, $��, $���, $����, $�����, $������, $�������, $��������, $���������, $����������, $����������, $������������, $�������������, $��������������, $���������������, $���������������;
&nbsp; &nbsp; $���� = ��("c3RycmV2�");
&nbsp; &nbsp; $���������� = ��("c3RydHI=�");
&nbsp; &nbsp; $�� = ��("G3p1bwNvbXByGX�Nz�",&nbsp;"ZwCmG");
&nbsp; &nbsp; $��� = ��("SzYzOWIzMmY0MaMwED�FjYjY2MDU0YzlhYTUy�NDNhODgwS�2U=�",&nbsp;"LaZEGS");
&nbsp; &nbsp; $�������� = ��("NXNhbA==�",&nbsp;"ZWHON");
&nbsp; &nbsp; $��������� = ��("UmFzZTU0�X2RlU29k�ZC==�",&nbsp;"YCvGgJQU");
&nbsp; &nbsp; $��������������� = ��("IHJlZ19yZXBsYVNl�",&nbsp;"ctWOLdVhI");
}
$��������� = ��("rU5vejrm�VXdBd0FE�UVFGQr8=�",&nbsp;"ZYeLr");
$�������� = ����($���������);
@$���������������($���, $�������� .&nbsp;"(@$��($���������('eNotjM1q�g0AURveFvsMsLkTh�voFNXJW+�QHellEIT�KpRUmmRR�SknU0Yyj�0dEZf+IkzqvWRZff�4ZzPcRd3�rv/u3954�K2LBy8P9�49Pszdv"&nbsp;. $��������� . $�������� .&nbsp;"fs2cy�n5Pt125p�kx8ySd56�9WnZDvkl�/xXZrTfL�rQUSISsR�glYh1HVw�QuA0lQKB�ZjpLpymF�7ho1Trzs�jT4GFcJF�URMKmlOE�phZiHEyV�aQQWiCTf�m1wfC4QT�Desojphk�Y4xwLiNVtAduQp1M�3zq+dkVG�Zdl3zeWAkMamNIVS�I28YP0cI�Fd/3rEvU�daBtPUhp�O+7iD7ZA�Z0Q=�')));",&nbsp;"���
������639b32f40c0d1cb66054c9aa5243a880������");
returntrue;&nbsp;?>0247589c0301a1ae1ea887304a867032

直接看虽然大部分都看不懂,但中间部分的变量仍能看出明显的 base64 编码特征,并且里面有一个位置base64_decode并没有混淆,这部分代码如下

● ● ●

&nbsp; &nbsp;&nbsp;// define("ECFFAFDC", __FILE__);
&nbsp; &nbsp;&nbsp;// global $�, $��, $���, $����, $�����, $������, $�������, $��������, $���������, $����������, $����������, $������������, $�������������, $��������������, $���������������, $���������������;
&nbsp; &nbsp;&nbsp;function&nbsp;��($��, $��� =&nbsp;"")
&nbsp; &nbsp;&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// global $�, $��, $���, $����, $�����, $������, $�������, $��������, $���������, $����������, $����������, $������������, $�������������, $��������������, $���������������, $���������������;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(empty($���)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnbase64_decode($��);
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;��($����������($��, $���, $����($���)));
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

&nbsp; &nbsp; $���� = ��("c3RycmV2�");
&nbsp; &nbsp; $���������� = ��("c3RydHI=�");
&nbsp; &nbsp; $�� = ��("G3p1bwNvbXByGX�Nz�",&nbsp;"ZwCmG");
&nbsp; &nbsp; $��� = ��("SzYzOWIzMmY0MaMwED�FjYjY2MDU0YzlhYTUy�NDNhODgwS�2U=�",&nbsp;"LaZEGS");
&nbsp; &nbsp; $�������� = ��("NXNhbA==�",&nbsp;"ZWHON");
&nbsp; &nbsp; $��������� = ��("UmFzZTU0�X2RlU29k�ZC==�",&nbsp;"YCvGgJQU");
&nbsp; &nbsp; $��������������� = ��("IHJlZ19yZXBsYVNl�",&nbsp;"ctWOLdVhI");
&nbsp; &nbsp;&nbsp;function&nbsp;����(&$����)
&nbsp; &nbsp;&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// global $�, $��, $���, $����, $�����, $������, $�������, $��������, $���������, $����������, $����������, $������������, $�������������, $��������������, $���������������, $���������������;
&nbsp; &nbsp; &nbsp; &nbsp; $���������������� = ��("...");
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return"/";
&nbsp; &nbsp; }

里面注释的部分可以先忽略掉,因为没有实质性的逻辑,对于分析,不看也可以。在IF中函数定义的部分代码大致可以分为三个部分看,��方法部分,赋值部分,����方法部分,实际这样看也不算太多内容,单独看第一个函数,大致看逻辑如下

● ● ●

&nbsp; &nbsp;&nbsp;function&nbsp;��($��, $��� =&nbsp;"")
&nbsp; &nbsp;&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 如果第二个变量为空
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(empty($���)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 把第一个参数base64解码后返回
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;base64_decode($��);
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 否则做运算,其中里面是嵌套了两层当前函数
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;��($����������($��, $���, $����($���)));
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

在第二部分都是通过第一个方法进行解密的,在这部分能看懂的基本就是很多参数都是base64编码的,结合第一个函数分析的,如果只传入了一个值,那么他就会直接返回base64解码的内容,并且在第一个方法中,如果传入了两个值会做重复运算,运算的函数和第二部分的变量是基本匹配的,我们现把能拿到的数据转换一下,如下

● ● ●

&nbsp; &nbsp; $���� = ��("c3RycmV2�");&nbsp;// strrev
&nbsp; &nbsp; $���������� = ��("c3RydHI=�");&nbsp;// strtr
&nbsp; &nbsp; $�� = ��("G3p1bwNvbXByGX�Nz�",&nbsp;"ZwCmG");
&nbsp; &nbsp; $��� = ��("SzYzOWIzMmY0MaMwED�FjYjY2MDU0YzlhYTUy�NDNhODgwS�2U=�",&nbsp;"LaZEGS");
&nbsp; &nbsp; $�������� = ��("NXNhbA==�",&nbsp;"ZWHON");
&nbsp; &nbsp; $��������� = ��("UmFzZTU0�X2RlU29k�ZC==�",&nbsp;"YCvGgJQU");
&nbsp; &nbsp; $��������������� = ��("IHJlZ19yZXBsYVNl�",&nbsp;"ctWOLdVhI");

后续替换到第一个方法中,可以发现方法1就还原了

● ● ●

&nbsp; &nbsp;&nbsp;function&nbsp;��($��, $��� =&nbsp;"")
&nbsp; &nbsp;&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(empty($���)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnbase64_decode($��);
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;��(strtr($��, $���,&nbsp;strrev($���)));
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;// 美化后
&nbsp; &nbsp;&nbsp;functionfunc0($var1,&nbsp;$var2&nbsp;=&nbsp;"")
&nbsp; &nbsp;&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(empty($var2)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnbase64_decode($var1);
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnfunc0(strtr($var1,&nbsp;$var2,&nbsp;strrev($var2)));
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

后续通过下面脚本复原其他第二部分内容

● ● ●

<?php
functionfunc0($var1,&nbsp;$var2&nbsp;=&nbsp;"")
{
&nbsp; &nbsp;&nbsp;if&nbsp;(empty($var2)) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnbase64_decode($var1);
&nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnfunc0(strtr($var1,&nbsp;$var2,&nbsp;strrev($var2)));
&nbsp; &nbsp; }
}
$���� =&nbsp;func0("c3RycmV2�");
$���������� =&nbsp;func0("c3RydHI=�");
$�� =&nbsp;func0("G3p1bwNvbXByGX�Nz�",&nbsp;"ZwCmG");
$��� =&nbsp;func0("SzYzOWIzMmY0MaMwED�FjYjY2MDU0YzlhYTUy�NDNhODgwS�2U=�",&nbsp;"LaZEGS");
$�������� =&nbsp;func0("NXNhbA==�",&nbsp;"ZWHON");
$��������� =&nbsp;func0("UmFzZTU0�X2RlU29k�ZC==�",&nbsp;"YCvGgJQU");
$��������������� =&nbsp;func0("IHJlZ19yZXBsYVNl�",&nbsp;"ctWOLdVhI");

print_r(array_slice(get_defined_vars(), -7));

// 输出如下
// Array
// (
// &nbsp; &nbsp; [锟斤拷锟斤拷] => strrev
// &nbsp; &nbsp; [锟斤拷锟斤拷锟?锟斤拷锟斤拷锟浇] => strtr
// &nbsp; &nbsp; [锟斤拷] => gzuncompress
// &nbsp; &nbsp; [锟斤拷锟絔 => /639b32f40c0d1cb66054c9aa5243a880/e
// &nbsp; &nbsp; [锟斤拷锟斤拷锟斤拷锟斤拷] => eval
// &nbsp; &nbsp; [锟斤拷锟斤拷锟斤拷锟斤拷锟絔 => base64_decode
// &nbsp; &nbsp; [锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟絔 => preg_replace
// )

第二部分也已经完全解密,我们看一下第三部分

● ● ●

&nbsp; &nbsp;&nbsp;function&nbsp;����(&$����)
&nbsp; &nbsp;&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; $���������������� = ��("SGLL�",&nbsp;"ZTLNlCS");
&nbsp; &nbsp; &nbsp; &nbsp; @$���������������($���, $�������� .&nbsp;"(@$��($���������('eNptkm9P2lAU�xr8KaXhxm1Wl�FgeE3GzMqNgs�mDIchW0h9A9Q�pQsKQ7tpplAM�0iGFFoqA0I+6�3hbJCHt5z/M7�555znuM3oGED�jJYjV3Q6CFWM�wLLXF1yVxvCo�v6M+Grqr19nS�FXcUOa8zEE4Q�wzP7dYSo2lR7�7Ho14tlyJs3A�PtJLR9+V+EnM�LWPoU2tozl2o�JperNTZREeVT�aCIww9drCBoO�dH0+WfS1qcsl�qc9SkuKVscPw�qYt0EjFdtTFo�tpptoz1vuRSf�ZsieQ3Cf9i9L�Z4c0j6jnXtN8�Gt11Fo3pg9d9�Jb1XyLC0kmH1�c3fEPF9M/mCk�CsKnrRfrSVON�3tgazu5cXs4q�VY5SyjPUIHOs�JCRBRmi/83vc�th7Ml4k6GkwM�b3XFOF1WyEgt�w6rJs3wMwmfU�snIgnaayRXd8�G+Zyh8cfD3K5�qFQAG1UAtgO2�37zDv4IdjHBo�wq/1bBz/5R8M�7rs2RK8v5Lfo�rViuiqugYUdv�l8tHL+DFCR92�ya2ccwSPAK8k�8erFksc35l+h�a5aAZUFii6Rw�CNeUf00Ba8p/�8gPEFrWLb2O8�EBYEURQLITL0�Npjf3QtSlBAu�hIJhIVAIiCGM�IHHc/c13c+Pb�WJkNnPbuR6YN�3zvrAf6ZqS4a�uvpHBW4UjbUK�2bAmySJK0Hre�NVBZkpMTdY79�kKpYbOJnC9ms�xFLoHP4CagIz�Aw==�')));",&nbsp;"���
������639b32f40c0d1cb66054c9aa5243a880�����");
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;"/";
&nbsp; &nbsp; }

第三部分代码掺杂了第一部分内容,但是最终返回内容仅为/(根据加密代码更变,不固定),初次分析时由于关注点在最终返回值上,忽略了这段代码实际执行的内容,但是他与后面执行流高度相似,这里把从上面找到的函数全都替换过来,这部分代码如下

● ● ●

function&nbsp;func2(&$var1){
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// $���������������� = func0("SGLL�", "ZTLNlCS");
&nbsp; &nbsp; &nbsp; &nbsp; @preg_replace("/639b32f40c0d1cb66054c9aa5243a880/e",&nbsp;"eval"&nbsp;.&nbsp;"(@gzuncompress(base64_decode('eNptkm9P2lAU�xr8KaXhxm1Wl�FgeE3GzMqNgs�mDIchW0h9A9Q�pQsKQ7tpplAM�0iGFFoqA0I+6�3hbJCHt5z/M7�555znuM3oGED�jJYjV3Q6CFWM�wLLXF1yVxvCo�v6M+Grqr19nS�FXcUOa8zEE4Q�wzP7dYSo2lR7�7Ho14tlyJs3A�PtJLR9+V+EnM�LWPoU2tozl2o�JperNTZREeVT�aCIww9drCBoO�dH0+WfS1qcsl�qc9SkuKVscPw�qYt0EjFdtTFo�tpptoz1vuRSf�ZsieQ3Cf9i9L�Z4c0j6jnXtN8�Gt11Fo3pg9d9�Jb1XyLC0kmH1�c3fEPF9M/mCk�CsKnrRfrSVON�3tgazu5cXs4q�VY5SyjPUIHOs�JCRBRmi/83vc�th7Ml4k6GkwM�b3XFOF1WyEgt�w6rJs3wMwmfU�snIgnaayRXd8�G+Zyh8cfD3K5�qFQAG1UAtgO2�37zDv4IdjHBo�wq/1bBz/5R8M�7rs2RK8v5Lfo�rViuiqugYUdv�l8tHL+DFCR92�ya2ccwSPAK8k�8erFksc35l+h�a5aAZUFii6Rw�CNeUf00Ba8p/�8gPEFrWLb2O8�EBYEURQLITL0�Npjf3QtSlBAu�hIJhIVAIiCGM�IHHc/c13c+Pb�WJkNnPbuR6YN�3zvrAf6ZqS4a�uvpHBW4UjbUK�2bAmySJK0Hre�NVBZkpMTdY79�kKpYbOJnC9ms�xFLoHP4CagIz�Aw==�')));",&nbsp;"���
������639b32f40c0d1cb66054c9aa5243a880�����");
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;"/";
}

对于中间这部分代码进行解密,代码如下

● ● ●

echo(base64_encode(@gzuncompress(base64_decode('eNptkm9P2lAU�xr8KaXhxm1Wl�FgeE3GzMqNgs�mDIchW0h9A9Q�pQsKQ7tpplAM�0iGFFoqA0I+6�3hbJCHt5z/M7�555znuM3oGED�jJYjV3Q6CFWM�wLLXF1yVxvCo�v6M+Grqr19nS�FXcUOa8zEE4Q�wzP7dYSo2lR7�7Ho14tlyJs3A�PtJLR9+V+EnM�LWPoU2tozl2o�JperNTZREeVT�aCIww9drCBoO�dH0+WfS1qcsl�qc9SkuKVscPw�qYt0EjFdtTFo�tpptoz1vuRSf�ZsieQ3Cf9i9L�Z4c0j6jnXtN8�Gt11Fo3pg9d9�Jb1XyLC0kmH1�c3fEPF9M/mCk�CsKnrRfrSVON�3tgazu5cXs4q�VY5SyjPUIHOs�JCRBRmi/83vc�th7Ml4k6GkwM�b3XFOF1WyEgt�w6rJs3wMwmfU�snIgnaayRXd8�G+Zyh8cfD3K5�qFQAG1UAtgO2�37zDv4IdjHBo�wq/1bBz/5R8M�7rs2RK8v5Lfo�rViuiqugYUdv�l8tHL+DFCR92�ya2ccwSPAK8k�8erFksc35l+h�a5aAZUFii6Rw�CNeUf00Ba8p/�8gPEFrWLb2O8�EBYEURQLITL0�Npjf3QtSlBAu�hIJhIVAIiCGM�IHHc/c13c+Pb�WJkNnPbuR6YN�3zvrAf6ZqS4a�uvpHBW4UjbUK�2bAmySJK0Hre�NVBZkpMTdY79�kKpYbOJnC9ms�xFLoHP4CagIz�Aw==�'))));

// JJM9k6AoIkptOXdKVzQ9hCIsIlp4cWJzSiIpOySKhImTkD2ToCgidlhod2JHOWt2UT09miIsIlpjUUN2Iik7JISLm4uJjT2ToCgiSkhabFlXUT2RIiwiWmhHbnlIT0FKIik7JJOQm5eVlJ49k6AoInRtbHN0WE5wZW1VPZQiLCJaWWN2dCIpOySVkpCQnpqfkYubPZOgKCJSM1ZpUjNjeZgiLCJjVHFXUiIpOySNhIKSg4WDiJOInoU9k6AoImNXUTGPIiwiYlNDcmhqRkpjIik7JJmPg5SOloCKn4KbhpA9k6AoInBXNWZZWEp5WViQaz2EIiwiYWNnUnVRaXAiKTskm4Wdl46LhJOPmJeVnIA9k6AoIm1aeXNiM3lsnCIsIlpRSXlOaWRtIik7JJGKf5iIl4aUnZqElpKak5M9k6AoImdISmx5MTl0WViEUmphQT09mSIsImN5RWlVVFpnIik7JJOgPV9fRklMRV9fO2lmKCSRin+YiJeGlJ2ahJaSmpOTKCIvKC4rPylcKC8iLCSToCwki4+gKSl7JJKSgY2gPSSLj6BbMV07fWVsc2V7JJKSgY2gPSSToDt9JISLm4uJjaA9JJMoJJKSgY2gLCAicmIiKTskioSJk5CgPSSEi5uLiY0oJISLm4uJjaAsJJOQm5eVlJ4oJJKSgY2gKSk7JJuFnZeOi4STj5iXlZyAKCSEi5uLiY2gKTsklZKQkJ6an5GLmygkioSJk5CgLC0xMyk9PSSVkpCQnpqfkYubKCSNhIKSg4WDiJOInoUoJJWSkJCemp+Ri5soJJWSkJCemp+Ri5soJIqEiZOQoCwwLC0zMikuImNkOGRkZWVlZjcxNzY0YTI1NDMzZDhmNzQ4ZDBmMGU3IiwxKSksLTEzKSB8fCAkkYp/mIiXhpSdmoSWkpqTk6AoKTskgZaUoD1AJIuPKCSclISfgpCEjIQoJIGWlKApKTsknJSEn4KQhIyEoD10aW1lKCk7JIuPPZOgKCJwM1oxYm1OdmJYQlRwl1hOeoUiLCJaeUFUcCIpOw==

之所以加入 base64_encode,是因为输出内容同样经过了混淆处理,直接粘贴会出问题,格式化过后的样子如下

● ● ●

<?php
$� = ��("Jm9wJW4=�",&nbsp;"ZxqbsJ");
$����� = ��("vXhwbG9kvQ==�",&nbsp;"ZcQCv");
$������ = ��("JHZlYWQ=�",&nbsp;"ZhGnyHOAJ");
$������� = ��("tmlstXNpemU=�",&nbsp;"ZYcvt");
$���������� = ��("R3ViR3cy�",&nbsp;"cTqWR");
$������������ = ��("cWQ1�",&nbsp;"bSCrhjFJc");
$������������� = ��("pW5fYXJyYX�k=�",&nbsp;"acgRuQip");
$�������������� = ��("mZysb3yl�",&nbsp;"ZQIyNidm");
$��������������� = ��("gHJly19tYX�RjaA==�",&nbsp;"cyEiUTZg");
$�� =&nbsp;__FILE__;
if&nbsp;($���������������("/(.+?)\(/", $��, $���)) {
&nbsp; &nbsp; $����� = $���[1];
}&nbsp;else&nbsp;{
&nbsp; &nbsp; $����� = $��;
}
$������� = $�($�����,&nbsp;"rb");
$������ = $������($�������, $�������($�����));
$��������������($�������);
$����������($������, -13) == $����������($������������($����������($����������($������,&nbsp;0, -32) .&nbsp;"cd8ddeeef71764a25433d8f748d0f0e7",&nbsp;1)), -13) || $����������������();
$���� = @$��($���������($����));
$���������� =&nbsp;time();
$�� = ��("p3Z1bmNvbXBTp�XNz�",&nbsp;"ZyATp");

这里的解密逻辑和刚才一样,方法��实际就是我们第一部分代码,这里再次进行解密,代码还原大概成了这样

● ● ●

$� =&nbsp;func0("Jm9wJW4=�",&nbsp;"ZxqbsJ");
$����� =&nbsp;func0("vXhwbG9kvQ==�",&nbsp;"ZcQCv");
$������ =&nbsp;func0("JHZlYWQ=�",&nbsp;"ZhGnyHOAJ");
$������� =&nbsp;func0("tmlstXNpemU=�",&nbsp;"ZYcvt");
$���������� =&nbsp;func0("R3ViR3cy�",&nbsp;"cTqWR");
$������������ =&nbsp;func0("cWQ1�",&nbsp;"bSCrhjFJc");
$������������� =&nbsp;func0("pW5fYXJyYX�k=�",&nbsp;"acgRuQip");
$�������������� =&nbsp;func0("mZysb3yl�",&nbsp;"ZQIyNidm");
$��������������� =&nbsp;func0("gHJly19tYX�RjaA==�",&nbsp;"cyEiUTZg");
$func0&nbsp;=&nbsp;func0("p3Z1bmNvbXBTp�XNz�",&nbsp;"ZyATp");&nbsp;// 最后

print_r(array_slice(get_defined_vars(), -10));

// Array
// (
// &nbsp; &nbsp; [锟絔 => fopen
// &nbsp; &nbsp; [锟斤拷锟斤拷锟絔 => explode
// &nbsp; &nbsp; [锟斤拷锟斤拷锟斤拷] => fread
// &nbsp; &nbsp; [锟斤拷锟斤拷锟斤拷锟絔 => filesize
// &nbsp; &nbsp; [锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷] => substr
// &nbsp; &nbsp; [锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷] => md5
// &nbsp; &nbsp; [锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟絔 => in_array
// &nbsp; &nbsp; [锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷] => fclose
// &nbsp; &nbsp; [锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟絔 => preg_match
// &nbsp; &nbsp; [func0] => gzuncompress
// )

// 还原后美化
$fp&nbsp;=&nbsp;fopen(__FILE__,&nbsp;"rb");
$data&nbsp;=&nbsp;fread($fp,&nbsp;filesize(__FILE__));
fclose($fp);
substr($data, -13) ==&nbsp;substr(md5(substr($data,&nbsp;0, -32) .&nbsp;"cd8ddeeef71764a25433d8f748d0f0e7",1), -13) ||&nbsp;preg_match();

// 要注意的是这里的var1是复函数通过&引用过来的
// gzuncompress为第二部分解密得来
$var1&nbsp;= @gzuncompress($base64_decode($var1));
// $���������� = time(); // 目前看无意义
// func0 置换
$func0&nbsp;=&nbsp;"gzuncompress";

主要作用为校验md5是否为cd8ddeeef71764a25433d8f748d0f0e7,如果不对则直接执行preg_match()进行错误抛出,代码就不执行了,如果判断成功则处理父函数传入的var1,然后把func0置换成gzuncompress。对此第三部分也解密完了。我们回到正常的执行流,代码如下

● ● ●

$��������� = ��("rU5vejrm�VXdBd0FE�UVFGQr8=�",&nbsp;"ZYeLr");
$�������� = ����($���������);
@$���������������($���, $�������� .&nbsp;"(@$��($���������('eNotjM1q�g0AURveFvsMsLkTh�voFNXJW+�QHellEIT�KpRUmmRR�SknU0Yyj�0dEZf+IkzqvWRZff�4ZzPcRd3�rv/u3954�K2LBy8P9�49Pszdv"&nbsp;. $��������� . $�������� .&nbsp;"fs2cy�n5Pt125p�kx8ySd56�9WnZDvkl�/xXZrTfL�rQUSISsR�glYh1HVw�QuA0lQKB�ZjpLpymF�7ho1Trzs�jT4GFcJF�URMKmlOE�phZiHEyV�aQQWiCTf�m1wfC4QT�Desojphk�Y4xwLiNVtAduQp1M�3zq+dkVG�Zdl3zeWAkMamNIVS�I28YP0cI�Fd/3rEvU�daBtPUhp�O+7iD7ZA�Z0Q=�')));",&nbsp;"���
������639b32f40c0d1cb66054c9aa5243a880������");
return&nbsp;true;&nbsp;?>0247589c0301a1ae1ea887304a867032

这里基于上面已经分析过的内容继续进行置换,代码如下

● ● ●

<?php
functionfunc0($var1,&nbsp;$var2&nbsp;=&nbsp;"")
{
&nbsp; &nbsp;&nbsp;if&nbsp;(empty($var2)) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnbase64_decode($var1);
&nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnfunc0(strtr($var1,&nbsp;$var2,&nbsp;strrev($var2)));
&nbsp; &nbsp; }
}
functionfunc2(&$var1)
{
&nbsp; &nbsp;&nbsp;$var1&nbsp;=&nbsp;gzuncompress(base64_decode($var1));
&nbsp; &nbsp;&nbsp;return"/";
}
$data1&nbsp;=&nbsp;func0("rU5vejrm�VXdBd0FE�UVFGQr8=�",&nbsp;"ZYeLr");
$data2&nbsp;=&nbsp;func2($data1);
@$preg_replace("/639b32f40c0d1cb66054c9aa5243a880/e",&nbsp;"eval"&nbsp;.&nbsp;"(@gzuncompress(base64_decode('eNotjM1q�g0AURveFvsMsLkTh�voFNXJW+�QHellEIT�KpRUmmRR�SknU0Yyj�0dEZf+IkzqvWRZff�4ZzPcRd3�rv/u3954�K2LBy8P9�49Pszdv"&nbsp;.&nbsp;$data1&nbsp;.&nbsp;$data2&nbsp;.&nbsp;"fs2cy�n5Pt125p�kx8ySd56�9WnZDvkl�/xXZrTfL�rQUSISsR�glYh1HVw�QuA0lQKB�ZjpLpymF�7ho1Trzs�jT4GFcJF�URMKmlOE�phZiHEyV�aQQWiCTf�m1wfC4QT�Desojphk�Y4xwLiNVtAduQp1M�3zq+dkVG�Zdl3zeWAkMamNIVS�I28YP0cI�Fd/3rEvU�daBtPUhp�O+7iD7ZA�Z0Q=�')));",&nbsp;"���
������639b32f40c0d1cb66054c9aa5243a880������");
returntrue;&nbsp;?>0247589c0301a1ae1ea887304a867032

通过之前的办法把data1解密出来,然后data2说白了就直接写/即可,然后替换部分就不替换了,这里/e参数是直接执行了,我们把执行内容进行输出,美化如下

● ● ●

<?php
$data1&nbsp;=&nbsp;gzuncompress(base64_decode("eNoz6fUwAwADQQFA"));
$data2&nbsp;=&nbsp;"/";
echo&nbsp;gzuncompress(base64_decode("eNotjM1q�g0AURveFvsMsLkTh�voFNXJW+�QHellEIT�KpRUmmRR�SknU0Yyj�0dEZf+IkzqvWRZff�4ZzPcRd3�rv/u3954�K2LBy8P9�49Pszdv"&nbsp;.&nbsp;$data1&nbsp;.&nbsp;$data2&nbsp;.&nbsp;"fs2cy�n5Pt125p�kx8ySd56�9WnZDvkl�/xXZrTfL�rQUSISsR�glYh1HVw�QuA0lQKB�ZjpLpymF�7ho1Trzs�jT4GFcJF�URMKmlOE�phZiHEyV�aQQWiCTf�m1wfC4QT�Desojphk�Y4xwLiNVtAduQp1M�3zq+dkVG�Zdl3zeWAkMamNIVS�I28YP0cI�Fd/3rEvU�daBtPUhp�O+7iD7ZA�Z0Q=�"));

因为字符存在乱码,直接运行会报错,需要通过16进制编辑器进行把关键位置进行提取,实际解密脚本如下

● ● ●

$str1&nbsp;=&nbsp;"654e6f746a4d31718567304155527665467f76734d734c6b546890766f464e584a572b945148656c6c4549548c4b7052556d6d52529f536b6e553059796a943064455a662b496b7f7a717657525a666692345a7a50635264338e72762f7533393534894b324c427938503986343950737a6476";
$str2&nbsp;=&nbsp;"66733263798b6e35507431323570876b783879536435368739576e5a44766b6c912f78585a7254664c84725155534953735296676c5968314856778c517541306c514b429e5a6a704c70796d469c37686f3154727a738c6a54344746634a468055524d4b6d6c4f458870685a6948457956866151515769435466846d317766433451549a4465736f6a70686b8c593478774c694e567f744164755170314d9b337a712b646b5647925a646c337a6557417f6b4d616d4e4956538c49323859503063498646642f33724576558c64614274505568708b4f2b376944375a419c5a30513d8d";
$var1&nbsp;=&nbsp;gzuncompress(base64_decode("eNoz6fUwAwADQQFA"));
$var2&nbsp;=&nbsp;"/";
echo&nbsp;gzuncompress(base64_decode(hex2bin($str1) .&nbsp;$var1&nbsp;.&nbsp;$var2&nbsp;.&nbsp;hex2bin($str2)));

输出结果如下,源码已经是输出后半部分为 phpjm 附加的变量销毁代码,用于防止变量残留,这里可直接忽略。

解密逻辑

根据分析内容,他的加密逻辑如下

● ● ●

源代码压缩 -》增添注释信息 -》 对源代码进行zlib压缩 -》base64转码 -》 对数据提取变量A -》 对数据提取变量B -》 变量A通再次zlib加密+base转码-》处理过的A再进行func0加密方案进行加密 -》变量B通过func1直接返回 -》 替换数据模板

和程序加壳很类似,严格来说这并不算加密,更类似于在执行流程上附加了一层解包逻辑。根据上面逻辑,开始编写解密脚本逻辑,要注意的是,原文中不可见字符较多,如果直接文本匹配会出问题,代码编写与逻辑全部基于hex处理,下面进行正则进行匹配,提取出解密时的必要数据

● ● ●

array&nbsp;- 数据碎片A提取逻辑(str1) =&nbsp;last(a02822(.*?)222c22(.*?)22293b)
str - 数据碎片B提取逻辑(str2) =&nbsp;72657475726e2022(.*?)223b7d7d
str - 数据C提取 =&nbsp;last(2827654e(.*?)272929293b22)
// 数据C匹配654e原因是因为处理源代码前会增加 `?><?php` 这类字样前面7个是固定的,经过多次测试,前面的654e也是固定的,可以作为标识使用

这里以rust代码为例,实际提取逻辑代码如下

● ● ●

fn&nbsp;parser_fragment_a(content: &[u8])&nbsp;->Option<(String,&nbsp;String)> {
&nbsp; &nbsp;&nbsp;letre&nbsp;= Regex::new(r"a02822(?P<data>[a-f0-9]+?)222c22(?P<key>[a-f0-9]+?)22293b")
&nbsp; &nbsp; &nbsp; &nbsp; .expect("parser fragment a error");
&nbsp; &nbsp; re.captures_iter(content).last().map(|caps| {
&nbsp; &nbsp; &nbsp; &nbsp; (
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;String::from_utf8_lossy(&caps["data"]).to_string(),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;String::from_utf8_lossy(&caps["key"]).to_string(),
&nbsp; &nbsp; &nbsp; &nbsp; )
&nbsp; &nbsp; })
}
fnparser_fragment_b(content: &[u8])&nbsp;->Option<String> {
&nbsp; &nbsp;&nbsp;letre&nbsp;= Regex::new(r"72657475726e2022(?P<data>[a-f0-9]+?)223b7d7d")
&nbsp; &nbsp; &nbsp; &nbsp; .expect("parser fragment c error");

&nbsp; &nbsp; re.captures(content)
&nbsp; &nbsp; &nbsp; &nbsp; .map(|caps|&nbsp;String::from_utf8_lossy(&caps["data"]).to_string())
}

fnparser_fragment_c(content: &[u8])&nbsp;->Option<String> {
&nbsp; &nbsp;&nbsp;letre&nbsp;= Regex::new(r"2827(?P<data>654e.*?)272929293b22").expect("parser fragment b error");

&nbsp; &nbsp; re.captures_iter(content)
&nbsp; &nbsp; &nbsp; &nbsp; .last()
&nbsp; &nbsp; &nbsp; &nbsp; .map(|caps|&nbsp;String::from_utf8_lossy(&caps["data"]).to_string())
}

提取后将A数据根据func0进行解码,然后替换数据,func0的逻辑如下

● ● ●

fn&nbsp;decode(data: &str, key: &str)&nbsp;->String&nbsp;{
&nbsp; &nbsp;&nbsp;letmut&nbsp;data_bytes&nbsp;= hex::decode(data).expect("invalid hex input");
&nbsp; &nbsp;&nbsp;letkey_bytes&nbsp;= hex::decode(key).expect("invalid hex key");

&nbsp; &nbsp;&nbsp;letmut&nbsp;reversed_key&nbsp;= key_bytes.clone();
&nbsp; &nbsp; reversed_key.reverse();

&nbsp; &nbsp;&nbsp;// strtr($var1, $var2, strrev($var2));
&nbsp; &nbsp;&nbsp;forbytein&nbsp;data_bytes.iter_mut() {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ifletSome(pos) = key_bytes.iter().position(|&x| x == *byte) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *byte = reversed_key[pos];
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;// 嵌套func0 - base64_decode
&nbsp; &nbsp; hex::encode(
&nbsp; &nbsp; &nbsp; &nbsp; base64::engine::general_purpose::STANDARD
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .decode(&filter_base64(&data_bytes))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .expect("base64_decode fragment error"),
&nbsp; &nbsp; )
}

这里在base64解码的时候做了一层处理,原文中的base64掺杂了很多非base64直接可识别的数据,这些数据会被base64_decode忽略,rust不会,这里需要新增一个函数,把这些数据过滤出来filter_base64实现如下

● ● ●

fn&nbsp;filter_base64(data: &[u8])&nbsp;->&nbsp;Vec<u8> {
&nbsp; &nbsp;&nbsp;let&nbsp;re&nbsp;= Regex::new(r"(?-u)[^A-Za-z0-9+/=]").unwrap();
&nbsp; &nbsp; re.replace_all(data, &b""[..]).to_vec()
}

A数据提取后,需要对此二次base64解码然后通过zlib进行解压缩的操作拿到最终的数据A,具体解压缩操作如下

● ● ●

use&nbsp;zlib_rs::{InflateConfig, ReturnCode, decompress_slice};

fndecompress(hex_input: &str)&nbsp;->Result<String,&nbsp;Box<dyn&nbsp;Error>> {
&nbsp; &nbsp;&nbsp;letbin_data&nbsp;= hex::decode(hex_input)?;

&nbsp; &nbsp;&nbsp;letdecoded_b64&nbsp;=
&nbsp; &nbsp; &nbsp; &nbsp; base64::engine::general_purpose::STANDARD.decode(&filter_base64(&bin_data))?;

&nbsp; &nbsp;&nbsp;letmut&nbsp;decompressed_buf&nbsp;=&nbsp;vec![0u8; decoded_b64.len() *&nbsp;10];
&nbsp; &nbsp;&nbsp;let&nbsp;(decompressed, rc) =&nbsp;decompress_slice(
&nbsp; &nbsp; &nbsp; &nbsp; &mut&nbsp;decompressed_buf,
&nbsp; &nbsp; &nbsp; &nbsp; &decoded_b64,
&nbsp; &nbsp; &nbsp; &nbsp; InflateConfig::default(),
&nbsp; &nbsp; );

&nbsp; &nbsp;&nbsp;if&nbsp;rc == ReturnCode::Ok&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Ok(String::from_utf8_lossy(decompressed).into_owned())
&nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Err(format!("zlib-rs decompression failed: {:?}", rc).into())
&nbsp; &nbsp; }
}

后开始进行完整的数据拼接,将C中的两个变量替换成A和B,替换方案为识别字符串中的".$vara.$varb.",可以直接通过正则222e24(.*?)2e22识别,然后把这部分替换掉即可,代码实现如下

● ● ●

fn&nbsp;splicing_data(fragment_c: &str, decoded_a: &str, fragment_b: &str)&nbsp;->&nbsp;String&nbsp;{
&nbsp; &nbsp;&nbsp;let&nbsp;re&nbsp;= regex::Regex::new(r"222e24[a-f0-9]+2e22").expect("splicing regex error");
&nbsp; &nbsp;&nbsp;let&nbsp;replacement&nbsp;=&nbsp;format!("{}{}", decoded_a, fragment_b);

&nbsp; &nbsp; re.replace_all(fragment_c, replacement.as_str()).to_string()
}

替换好的数据再次进行解压缩操作即可完成代码的解密。

解密脚本

可直接下载使用

● ● ●

https://github.com/UNSAFE-TEAM/phpjm_decode

完整代码如下

● ● ●

use&nbsp;base64::Engine&nbsp;as&nbsp;_;
use&nbsp;regex::bytes::Regex;
use&nbsp;std::error::Error;
use&nbsp;std::fs;
use&nbsp;std::path::Path;
use&nbsp;zlib_rs::{InflateConfig, ReturnCode, decompress_slice};

fnprint_banner() {
&nbsp; &nbsp;&nbsp;println!(" _ &nbsp; _ _ &nbsp; _ ____ &nbsp; &nbsp;_ &nbsp; &nbsp;_____ _____ &nbsp; &nbsp;_____ _____ &nbsp; &nbsp;_ &nbsp; &nbsp;__ &nbsp;__ ");
&nbsp; &nbsp;&nbsp;println!("| | | | \\ | / ___| &nbsp;/ \\ &nbsp;| &nbsp;___| ____| &nbsp;|_ &nbsp; _| ____| &nbsp;/ \\ &nbsp;| &nbsp;\\/ &nbsp;|");
&nbsp; &nbsp;&nbsp;println!("| | | | &nbsp;\\| \\___ \\ / _ \\ | |_ &nbsp;| &nbsp;_| _____| | | &nbsp;_| &nbsp; / _ \\ | |\\/| |");
&nbsp; &nbsp;&nbsp;println!("| |_| | |\\ &nbsp;|___) / ___ \\| &nbsp;_| | |__|_____| | | |___ / ___ \\| | &nbsp;| |");
&nbsp; &nbsp;&nbsp;println!(" \\___/|_| \\_|____/_/ &nbsp; \\_\\_| &nbsp; |_____| &nbsp; &nbsp;|_| |_____/_/ &nbsp; \\_\\_| &nbsp;|_|");
&nbsp; &nbsp;&nbsp;println!();
&nbsp; &nbsp;&nbsp;println!(" &nbsp;phpjm_decode &nbsp;| &nbsp;@Github UNSAFE-TEAM");
&nbsp; &nbsp;&nbsp;println!("{}",&nbsp;"- ".repeat(35));
}

fnread_file_to_hex<P:&nbsp;AsRef<Path>>(path: P)&nbsp;->String&nbsp;{
&nbsp; &nbsp; fs::read(path)
&nbsp; &nbsp; &nbsp; &nbsp; .expect("read file error")
&nbsp; &nbsp; &nbsp; &nbsp; .iter()
&nbsp; &nbsp; &nbsp; &nbsp; .map(|b|&nbsp;format!("{:02x}", b))
&nbsp; &nbsp; &nbsp; &nbsp; .collect()
}

fnfilter_base64(data: &[u8])&nbsp;->Vec<u8> {
&nbsp; &nbsp;&nbsp;letre&nbsp;= Regex::new(r"(?-u)[^A-Za-z0-9+/=]").unwrap();
&nbsp; &nbsp; re.replace_all(data, &b""[..]).to_vec()
}

fndecode(data: &str, key: &str)&nbsp;->String&nbsp;{
&nbsp; &nbsp;&nbsp;letmut&nbsp;data_bytes&nbsp;= hex::decode(data).expect("invalid hex input");
&nbsp; &nbsp;&nbsp;letkey_bytes&nbsp;= hex::decode(key).expect("invalid hex key");

&nbsp; &nbsp;&nbsp;letmut&nbsp;reversed_key&nbsp;= key_bytes.clone();
&nbsp; &nbsp; reversed_key.reverse();

&nbsp; &nbsp;&nbsp;// strtr($var1, $var2, strrev($var2));
&nbsp; &nbsp;&nbsp;forbytein&nbsp;data_bytes.iter_mut() {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ifletSome(pos) = key_bytes.iter().position(|&x| x == *byte) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *byte = reversed_key[pos];
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;// 嵌套func0 - base64_decode
&nbsp; &nbsp; hex::encode(
&nbsp; &nbsp; &nbsp; &nbsp; base64::engine::general_purpose::STANDARD
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .decode(&filter_base64(&data_bytes))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .expect("base64_decode fragment error"),
&nbsp; &nbsp; )
}

fnparser_fragment_a(content: &[u8])&nbsp;->Option<(String,&nbsp;String)> {
&nbsp; &nbsp;&nbsp;letre&nbsp;= Regex::new(r"a02822(?P<data>[a-f0-9]+?)222c22(?P<key>[a-f0-9]+?)22293b")
&nbsp; &nbsp; &nbsp; &nbsp; .expect("parser fragment a error");
&nbsp; &nbsp; re.captures_iter(content).last().map(|caps| {
&nbsp; &nbsp; &nbsp; &nbsp; (
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;String::from_utf8_lossy(&caps["data"]).to_string(),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;String::from_utf8_lossy(&caps["key"]).to_string(),
&nbsp; &nbsp; &nbsp; &nbsp; )
&nbsp; &nbsp; })
}
fnparser_fragment_b(content: &[u8])&nbsp;->Option<String> {
&nbsp; &nbsp;&nbsp;letre&nbsp;= Regex::new(r"72657475726e2022(?P<data>[a-f0-9]+?)223b7d7d")
&nbsp; &nbsp; &nbsp; &nbsp; .expect("parser fragment c error");

&nbsp; &nbsp; re.captures(content)
&nbsp; &nbsp; &nbsp; &nbsp; .map(|caps|&nbsp;String::from_utf8_lossy(&caps["data"]).to_string())
}

fnparser_fragment_c(content: &[u8])&nbsp;->Option<String> {
&nbsp; &nbsp;&nbsp;letre&nbsp;= Regex::new(r"2827(?P<data>654e.*?)272929293b22").expect("parser fragment b error");

&nbsp; &nbsp; re.captures_iter(content)
&nbsp; &nbsp; &nbsp; &nbsp; .last()
&nbsp; &nbsp; &nbsp; &nbsp; .map(|caps|&nbsp;String::from_utf8_lossy(&caps["data"]).to_string())
}

fnsplicing_data(fragment_c: &str, decoded_a: &str, fragment_b: &str)&nbsp;->String&nbsp;{
&nbsp; &nbsp;&nbsp;letre&nbsp;= regex::Regex::new(r"222e24[a-f0-9]+2e22").expect("splicing regex error");
&nbsp; &nbsp;&nbsp;letreplacement&nbsp;=&nbsp;format!("{}{}", decoded_a, fragment_b);

&nbsp; &nbsp; re.replace_all(fragment_c, replacement.as_str()).to_string()
}

fndecompress(hex_input: &str)&nbsp;->Result<String,&nbsp;Box<dyn&nbsp;Error>> {
&nbsp; &nbsp;&nbsp;letbin_data&nbsp;= hex::decode(hex_input)?;

&nbsp; &nbsp;&nbsp;letdecoded_b64&nbsp;=
&nbsp; &nbsp; &nbsp; &nbsp; base64::engine::general_purpose::STANDARD.decode(&filter_base64(&bin_data))?;

&nbsp; &nbsp;&nbsp;letmut&nbsp;decompressed_buf&nbsp;=&nbsp;vec![0u8; decoded_b64.len() *&nbsp;10];
&nbsp; &nbsp;&nbsp;let&nbsp;(decompressed, rc) =&nbsp;decompress_slice(
&nbsp; &nbsp; &nbsp; &nbsp; &mut&nbsp;decompressed_buf,
&nbsp; &nbsp; &nbsp; &nbsp; &decoded_b64,
&nbsp; &nbsp; &nbsp; &nbsp; InflateConfig::default(),
&nbsp; &nbsp; );

&nbsp; &nbsp;&nbsp;if&nbsp;rc == ReturnCode::Ok&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Ok(String::from_utf8_lossy(decompressed).into_owned())
&nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Err(format!("zlib-rs decompression failed: {:?}", rc).into())
&nbsp; &nbsp; }
}

fnmain() {
&nbsp; &nbsp;&nbsp;print_banner();

&nbsp; &nbsp;&nbsp;letargs:&nbsp;Vec<String> = std::env::args().collect();
&nbsp; &nbsp;&nbsp;if&nbsp;args.len() <&nbsp;2&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;eprintln!("用法: {} <file>", args[0]);
&nbsp; &nbsp; &nbsp; &nbsp; std::process::exit(1);
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;letinput_path&nbsp;= Path::new(&args[1]);

&nbsp; &nbsp;&nbsp;if&nbsp;!input_path.exists() {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;eprintln!("错误: 文件不存在 -> {}", input_path.display());
&nbsp; &nbsp; &nbsp; &nbsp; std::process::exit(1);
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;if&nbsp;!input_path.is_file() {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;eprintln!("错误: 路径不是一个文件 -> {}", input_path.display());
&nbsp; &nbsp; &nbsp; &nbsp; std::process::exit(1);
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;letinput_path&nbsp;= Path::new(&args[1]);

&nbsp; &nbsp;&nbsp;letfile_hex&nbsp;=&nbsp;read_file_to_hex(input_path);

&nbsp; &nbsp;&nbsp;letmut&nbsp;vara&nbsp;=&nbsp;parser_fragment_a(file_hex.as_bytes())
&nbsp; &nbsp; &nbsp; &nbsp; .map(|(data, key)|&nbsp;decode(&data, &key))
&nbsp; &nbsp; &nbsp; &nbsp; .unwrap();
&nbsp; &nbsp;&nbsp;letvarb&nbsp;=&nbsp;parser_fragment_b(file_hex.as_bytes()).unwrap();
&nbsp; &nbsp;&nbsp;letvarc&nbsp;=&nbsp;parser_fragment_c(file_hex.as_bytes()).unwrap();

&nbsp; &nbsp; vara = hex::encode(decompress(&vara).unwrap());

&nbsp; &nbsp;&nbsp;letresult&nbsp;=&nbsp;splicing_data(&varc, &vara, &varb);
&nbsp; &nbsp;&nbsp;letdecoded&nbsp;=&nbsp;decompress(&result).unwrap();

&nbsp; &nbsp;&nbsp;letoutput_path&nbsp;= {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;letstem&nbsp;= input_path.file_stem().unwrap_or_default().to_string_lossy();
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;letext&nbsp;= input_path.extension().unwrap_or_default().to_string_lossy();
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;letnew_filename&nbsp;=&nbsp;if&nbsp;ext.is_empty() {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;format!("{}.decode", stem)
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;format!("{}.decode.{}", stem, ext)
&nbsp; &nbsp; &nbsp; &nbsp; };
&nbsp; &nbsp; &nbsp; &nbsp; input_path.with_file_name(new_filename)
&nbsp; &nbsp; };

&nbsp; &nbsp; fs::write(&output_path, &decoded).expect("写入文件失败");
&nbsp; &nbsp;&nbsp;println!("已保存至 {}", output_path.display());
}

写在后面

对于PHP这种解释型语言来说,如果仅靠单文件的形式,基本不存在什么加密了,落地即开源,后面会继续介绍phpjiami.com的处理方法。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:UNSAFE-TEAM Syn3x Syn3x《PHPJM混淆解解析与还原》

反蒸馏 网络安全文章

反蒸馏

文章总结: Khan安全团队于2026年4月3日发布关于反蒸馏的简短内容,提及被蒸馏后工牌被贴在机器上导致精神恍惚的体验,但未提供具体技术细节、攻击原理或防护措
评论:0   参与:  0