0


phpjiami加密原理详解及解密

零、引言

最近工作中遇到一些使用phpjiami进行加密的php代码,所以对这个加密进行了详细的分析。
本文包括如下内容:

  1. phpjiami的加密原理
  2. 详细的phpjiami的解密方法
  3. 略带一些Php-parser使用方法

一、管中窥豹-了解phpjiami使用

phpjiami的官方网站为:https://www.phpjiami.com/phpjiami.html
使用phpjiami有几个关键的参数:

  1. 独立加密后,解密的代码会在原本的代码中。如果使用_lib库会生成一个单独的_lib.php,enc.php会通过include(’_lib.php’)进行解密,实际的解密代码和独立加密相同,后面不做单独分析。
  2. 控制参数,免费用户只能锁定ip、文件名和过期时间。在这里插入图片描述 为了测试加密解密的效果创建一个包含类、函数的测试代码
<?phpfunctioninfo($a,$b){return$a.':'.$b;}classpeople{protected$a;protected$b;publicfunction__construct($a,$b){$this->a=$a;$this->b=$b;}publicfunctioninfo(){return$this->a.':'.$this->b;}publicstaticfunctionphpinfo(){phpinfo();}}$name=$_GET['name'];$age=$_GET['age'];echoinfo($name,$age);$p=newpeople($name,$age);echo$p->info();people::phpinfo();

加密后代码如下,可以发现如下特点

  1. 函数名、变量名都被替换为了不可见字符,所有代码都缩到了一行,干扰正常分析。
  2. 代码中有3个函数,如果多加密几个文件会发现都是3个函数,因此3个函数就是解密代码运行的关键。
  3. 在代码?>后面还有一坨乱码,猜测保存了原始的加密代码。在这里插入图片描述

二、磨刀霍霍-Php-parser美化phpjiami代码

乱码太严重,而且格式不规范,是时候祭出神器PHP-Parser对代码美化一下。(百科:PHP Parser 是由 nikic 开发的一款 php 抽象语法树(AST)解析工具。PHP Parser 同时兼顾接口易用,结构简洁,工具链完善等诸多优点。在工程上,普遍使用 PHP Paser 生成模板代码,或使用其生成的抽象语法树进行静态分析,https://github.com/nikic/PHP-Parser),挖个坑之后有机会再详细研究研究怎么使用,在enphp mzphp2的解密中就需要大量使用。

  1. 安装PHP-Parser
安装PHP-Parser
  1. 使用PhpParser解析加密后的代码获取AST树
$code=file_get_contents('./test_enc.php');$parser=(newParserFactory)->create(ParserFactory::PREFER_PHP7);try{$ast=$parser->parse($code);}catch(Error$error){echo"Parse error: {$error->getMessage()}\n";return;}
  1. 使用NodeFinder获取所有的函数,并将乱码函数名替换为func1,func2
$nodeFinder=newNodeFinder;$Funcs=$nodeFinder->findInstanceOf($ast,PhpParser\Node\Stmt\Function_::class);$map=[];$v=0;foreach($Funcsas$func){$funcname=$func->name->name;if(!isset($map[$funcname])){if(!preg_match('/^[a-z0-9A-Z_]+$/',$funcname)){$code=str_replace($funcname,"func".$v,$code);$v++;$map[$funcname]=$v;}}}
  1. 使用token_get_all获取php代码中的基本令牌,并将乱码变量名替换为v1,v2
//将乱码变量名,替换变量为$v1,$v2$v=0;$map=[];$tokens=token_get_all($code);foreach($tokensas$token){if($token[0]===T_VARIABLE){if(!isset($map[$token[1]])){if(!preg_match('/^\$[a-zA-Z0-9_]+$/',$token[1])){$code=str_replace($token[1],'$v'.$v++,$code);$map[$token[1]]=$v;}}}}
  1. 美化格式并保存
//美化格式输出$parser=(newParserFactory)->create(ParserFactory::PREFER_PHP7);try{$ast=$parser->parse($code);}catch(Error$error){echo"Parse error: {$error->getMessage()}\n";return;}$prettyPrinter=newPrettyPrinter\Standard;$prettyCode=$prettyPrinter->prettyPrintFile($ast);echo$prettyCode;file_put_contents('./test_enc_format.php',$prettyCode);

解密完成后,代码基本可读。
在这里插入图片描述
完整代码如下

<?phpusePhpParser\Error;usePhpParser\ParserFactory;usePhpParser\PrettyPrinter;usePhpParser\NodeFinder;require'vendor/autoload.php';//1. 读取代码并解析$code=file_get_contents('./test_enc.php');$parser=(newParserFactory)->create(ParserFactory::PREFER_PHP7);try{$ast=$parser->parse($code);}catch(Error$error){echo"Parse error: {$error->getMessage()}\n";return;}//将乱码函数名,替换函数为func1,fun2$nodeFinder=newNodeFinder;$Funcs=$nodeFinder->findInstanceOf($ast,PhpParser\Node\Stmt\Function_::class);$map=[];$v=0;foreach($Funcsas$func){$funcname=$func->name->name;if(!isset($map[$funcname])){if(!preg_match('/^[a-z0-9A-Z_]+$/',$funcname)){$code=str_replace($funcname,"func".$v,$code);$v++;$map[$funcname]=$v;}}}//将乱码变量名,替换变量为$v1,$v2$v=0;$map=[];$tokens=token_get_all($code);foreach($tokensas$token){if($token[0]===T_VARIABLE){if(!isset($map[$token[1]])){if(!preg_match('/^\$[a-zA-Z0-9_]+$/',$token[1])){$code=str_replace($token[1],'$v'.$v++,$code);$map[$token[1]]=$v;}}}}//美化格式输出$parser=(newParserFactory)->create(ParserFactory::PREFER_PHP7);try{$ast=$parser->parse($code);}catch(Error$error){echo"Parse error: {$error->getMessage()}\n";return;}$prettyPrinter=newPrettyPrinter\Standard;$prettyCode=$prettyPrinter->prettyPrintFile($ast);echo$prettyCode;file_put_contents('./test_enc_format.php',$prettyCode);

三、一探究竟-phpjiami加密原理

先给出整个加密代码的结构,后面再各个击破

functionfunc0($v0)//反调试,解密代码functionfunc1(&$v1,$v2)//全局函数名恢复functionfunc2($v1,$v2='')//输入$v1需要解密的字符串,返回解密后的字符串if(!$v1){
    n个func1解密函数名并赋值全局变量
}$v46=func0(_f_);//读取解密代码set_include_path(dirname('当前文件名'));$v90=base64_encode($v46);//base64编码代码eval(func2('乱码字符串'));//真正执行的地方,乱码字符串解密后:eval(base64_decode($��ƻ��));//真正执行代码的地方,因为修改了变量名导致最终无法正常执行。set_include_path(dirname('当前文件名'));returnnull;?>加密压缩的代码+1个随机字符+32字节加密代码的md5值

phpjiami核心就是3个函数,每次加密3个函数的顺序会不一样,可以通过参数进行区分。

func2函数分析(解密函数)

func2函数输入两个参数,解密参数1得到对应的字符串。

  1. base64_decode解密需要用到的函数md5、ord、strlen、chr函数
  2. 计算特定不可见字符串的md5,用于后续异或解密
  3. 小于0xF5设置为 v 1 [ v1[ v1[i],大于0xF5设置为’’
  4. 异或解密字符串在这里插入图片描述 手动实现类似的代码
functionfunc2($v1,$v2=''){//base64_decode解密使用到的函数,$md5=md5(pack("H*",'EDE5E0E5ECEA'));$v2=!$v2?0xf5:$v2;$i=0;$str='';for(;$i<strlen($v1);$i++){$str.=ord($v1[$i])<0xf5?ord($v1[$i])>$v2&&ord($v1[$i])<0xF5?chr(ord($v1[$i])/2):$v1[$i]:'';//v2并未设置,因此小于0xF5设置为$v1[$i],大于0xF5设置为''}$str=base64_decode($str);$i=0;$result='';$j=$md5_len=strlen($md5);for(;$i<strlen($str);$i++)//循环和md5值进行异或{$j=$j?$j:$md5_len;$j--;$result.=$str[$i]^$md5[$j];}return$result;}

func1函数分析(全局函数名恢复)

func1通过str_rot13、gzuncompress、stripcslashes、func2对字符串进行解密,并赋值给传入的全局变量。
在这里插入图片描述

手动恢复代码(ps:因为编码格式不同,直接将加密字符串复制出来会解密失败,这也是为什么在phpstorm里面修改了加密代码保存之后无法正常运行的原因)(pps:可以在010editor里面进行修改,或者phpstorm里面有什么地方进行设置,知道的大佬可以交流一下)

functionfunc1(&$v1,$v2){$funcs=str_rot13(gzuncompress(stripcslashes(func2("一串不可见字符"))));$arrays_func=explode(',',$funcs);$v1=$arrays_func[$v2];}

func0函数分析(核心函数)

func0主要用于反调试和最后文件解密。
在这里插入图片描述

反调试1—启动方式反调试:如果是cli启动,则退出程序。

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

反调试2—服务端信息反调试:如果没有设置相关的服务器变量,则退出程序。

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

反调试3—时间反调试:两个语句运行时间超过100毫秒,则退出程序。

$time1=microtime(true)*1000;if(microtime(true)*100-$time1>100){die();}

反调试4—文件完整性反调试:先读取最后44个字节并调用func2进行解密得到33个字节内容,再读取除了后44个字节的文件内容并计算md5,最后查看md5是非在前面解密内容中。

可以知道加密后文件结构=解密代码+加密压缩后的代码+1字节随机字节+32字节md5(加密代码)

!strpos(func2(substr($file,func2(pack('H*','54ee5947')),func2(pack('H*','\x54\xee\x4d\x3d')))),md5(substr($file,func2(pack('H*','55ce3d3d')),func2('H*','54ee5946'))))?$nothisfunc():$nothisvar;

反反调试最简单的方法就是将所有的反调试注释掉。

最后的代码:计算了需要解密的代码偏移,使用str_rot13、@gzuncompress、func2、substr解密得到最终的代码。
在这里插入图片描述

四、直捣黄龙-完整解密

通过前面的分析可以知道

加密后的文件=解密代码+加密压缩后的代码+1字节随机字节+32字节md5(加密代码)

只需要获取到加密压缩后的代码进行解密就好了(ps:func2中用于计算md5值的不可见字符会变化,需要手动获取)

functionfunc2($v1,$v2=''){//base64_decode解密使用到的函数,$md5=md5(pack("H*",'EDE5E0E5ECEA'));$v2=!$v2?0xf5:$v2;$i=0;$str='';for(;$i<strlen($v1);$i++){$str.=ord($v1[$i])<0xf5?ord($v1[$i])>$v2&&ord($v1[$i])<0xF5?chr(ord($v1[$i])/2):$v1[$i]:'';//v2并未设置,因此小于0xF5设置为$v1[$i],大于0xF5设置为''}$str=base64_decode($str);$i=0;$result='';$j=$md5_len=strlen($md5);for(;$i<strlen($str);$i++)//循环和md5值进行异或{$j=$j?$j:$md5_len;$j--;$result.=$str[$i]^$md5[$j];}return$result;}$file=file_get_contents('test_enc.php');$enc_code=explode('?>',$file);//try{$dec_code=str_rot13(@gzuncompress(func2(substr($enc_code[1],0,-44))));//解密代码print_r($dec_code);file_put_contents('test_dec.php',$dec_code);}catch(Error$error){echo"Parse error: {$error->getMessage()}\n";return;}

成功解密
在这里插入图片描述

五、总结

phpjiami有点像最早的android加固的感觉。其实获取到的代码还使用了enphp、mzphp2进行了加密,和phpjiami也有点类似,挖个坑后续有空补上解密过程。

参考:

  1. 《PHP解密:zym加密 带乱码调试过程 》https://www.52pojie.cn/thread-693641-1-1.html
  2. 《初探PHP-Parser和PHP代码混淆》https://www.redteaming.top/2020/05/07/%E5%88%9D%E6%8E%A2PHP-Parser%E5%92%8CPHP%E4%BB%A3%E7%A0%81%E6%B7%B7%E6%B7%86/
  3. 《PHPJiaMi 免扩展加密分析及解密》

本文转载自: https://blog.csdn.net/abel_big_xu/article/details/127827902
版权归原作者 努力学习的大康 所有, 如有侵权,请联系我们删除。

“phpjiami加密原理详解及解密”的评论:

还没有评论