• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

phpjiami加密原理和解密

武飞扬头像
努力学习的大康
帮助1

零、引言

最近工作中遇到一些使用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、文件名和过期时间。
    学新通
    为了测试加密解密的效果创建一个包含类、函数的测试代码
<?php
function info($a,$b){
    return $a.':'.$b;
}
class people{
    protected $a;
    protected $b;
    public function __construct($a,$b)
    {
        $this->a = $a;
        $this->b = $b;
    }

    public function info()
    {
        return $this->a.':'.$this->b;
    }

    public static function phpinfo()
    {
        phpinfo();
    }
}
$name = $_GET['name'];
$age = $_GET['age'];
echo info($name,$age);

$p = new people($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 = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
try {
    $ast = $parser->parse($code);
} catch (Error $error) {
    echo "Parse error: {$error->getMessage()}\n";
    return;
}
  1. 使用NodeFinder获取所有的函数,并将乱码函数名替换为func1,func2
$nodeFinder = new NodeFinder;
$Funcs = $nodeFinder->findInstanceOf($ast, PhpParser\Node\Stmt\Function_::class);
$map = [];
$v = 0;

foreach ($Funcs as $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 ($tokens as $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 = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
try {
    $ast = $parser->parse($code);
} catch (Error $error) {
    echo "Parse error: {$error->getMessage()}\n";
    return;
}
$prettyPrinter = new PrettyPrinter\Standard;
$prettyCode = $prettyPrinter->prettyPrintFile($ast);
echo $prettyCode;
file_put_contents('./test_enc_format.php', $prettyCode);

解密完成后,代码基本可读。
学新通
完整代码如下

<?php
use PhpParser\Error;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter;
use PhpParser\NodeFinder;

require 'vendor/autoload.php';
//1. 读取代码并解析
$code = file_get_contents('./test_enc.php');
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
try {
    $ast = $parser->parse($code);
} catch (Error $error) {
    echo "Parse error: {$error->getMessage()}\n";
    return;
}
//将乱码函数名,替换函数为func1,fun2
$nodeFinder = new NodeFinder;
$Funcs = $nodeFinder->findInstanceOf($ast, PhpParser\Node\Stmt\Function_::class);
$map = [];
$v = 0;

foreach ($Funcs as $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 ($tokens as $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 = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
try {
    $ast = $parser->parse($code);
} catch (Error $error) {
    echo "Parse error: {$error->getMessage()}\n";
    return;
}
$prettyPrinter = new PrettyPrinter\Standard;
$prettyCode = $prettyPrinter->prettyPrintFile($ast);
echo $prettyCode;
file_put_contents('./test_enc_format.php', $prettyCode);
学新通

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

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

function func0($v0)//反调试,解密代码
function func1(&$v1, $v2)//全局函数名恢复
function func2($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('当前文件名'));
return null;
?>加密压缩的代码 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. 异或解密字符串
    学新通
    手动实现类似的代码
function func2($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里面有什么地方进行设置,知道的大佬可以交流一下)**

function func1(&$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值的不可见字符会变化,需要手动获取)

function func2($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/初探PHP-Parser和PHP代码混淆/
  3. 《PHPJiaMi 免扩展加密分析及解密》

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgbhbkb
系列文章
更多 icon
同类精品
更多 icon
继续加载