吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 41708|回复: 90
收起左侧

[Web逆向] PHP解密:反汇编某虚拟机加密(不进行反编译)

  [复制链接]
Ganlv 发表于 2019-7-24 04:10
本帖最后由 Ganlv 于 2019-7-28 20:02 编辑

声明

关于版权

请注意,文中相关商业名称均已消去,本文目的是学习分析反编译原理,目的不是为了破解商业产品。

替换并不是为了侵权,是为了保护商业产品,避免本文被搜索引擎直接搜索到。

如果您的确想知道被替换的名称,那么请在命令行中执行语句 php -r "echo gzinflate(base64_decode('qzJNzUsuqiwoAQA='));"

文章内容

本文的目的并不是破解,本文仅是学习其加密方法,了解其 VM 加密的强度、防反编译的能力。文末有评测报告。

本文不提供任何形式的反编译器或者其代码,文中所使用的代码均为通用的思路,您可以学习理解并根据自身需要修改和使用,不应该整段抄袭。

其网站上有一句话

破解是一门艺术,但是一旦破解的成本要远高于购买您的程序的成本,那么,您认为还有人想要破解您的程序吗?

但是我想说

很抱歉。有。破解是一门艺术,有人只是想要他的结果,而还有一些人则是品味这个过程,结果只是一个“附赠品”而已。

样本

样本为加密为免费体验版本,文件由网友提供,我没有该文件的原始文件。

格式化代码

我们需要先分析一下加密使用的 VM 的执行流程。一堆乱码肯定不方便分析,所以就先格式化,让代码看上去更舒服一些。

使用 nikic/PHP-Parser 进行格式化,这个解析器对乱码的处理很好。

[Info]

nikic 是 php 核心开发组成员。

PHP-Parser 只是一个语法分析器 (parser),不包含词法分析器 (lexer),其词法分析器使用的是 php 内置的 token_get_all 函数,所以基本上 php 能运行,解析结果就不会出错。

词法分析器是将代码打碎成片段,每个片段是一个关键的语法元素,通常称其为 word(单词)或者叫 token(标记符号)。语法分析器再将每一个单词组织成抽象语法树 (Abstract Syntax Tree, AST)

使用 Composer 安装 nikic/php-parser 依赖包。

composer require nikic/php-parser

创建 format.php

<?php

use PhpParser\Error;
use PhpParser\ParserFactory;

require 'vendor/autoload.php';

$code = file_get_contents('tests/samples/t2.php_');

$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
try {
    $ast = $parser->parse($code);
} catch (Error $error) {
    echo "Parse error: {$error->getMessage()}\n";
    return;
}

$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$newCode = $prettyPrinter->prettyPrintFile($ast);

file_put_contents('tests/samples/t2_formatted.php_', $newCode);

执行 format.php

php format.php

然后得到结果如下。

分析代码流程

注意:分析任何未知样本的时候,注意一定不要直接运行它,最好不要使用动态调试,有时候断点失效就会导致程序执行下去,如果有什么恶意代码(比如暗桩)就会被执行到。

在可以静态调试的时候先静态调试,分析代码执行流程,理解其算法,然后针对性解决问题。

版权信息

文件最开始是版权信息,这是免费版本。

<?php
$GANLVENCRYPTENHINT = "This encrypted file is for free trial with slow speed and time limitations. If your PHP works fine, please purchase at https://ganlvencrypt.com/ .";
$GANLVENCRYPTHINT = "您正在使用的加密为免费体验版本,运行速度极慢并有时间限制,如果您的程序运行正常,欢迎支付: https://ganlvencrypt.com/ .";

函数 1

下文中,乱码均被替换成 func1 $v0 $arg0 这些代号,防止文章中乱码影响阅读。

function ֒func1()
{
    static $a = NULL;
    if ($a === NULL) {
        $a = gzinflate(base64_decode("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"));
    }
    return $a;
}

我们可以在 php 中执行一下这句代码,看看其包含的内容是什么。

php -a

[Tips]

这里推荐使用 PsySH,一个交互式的 php shell,用着比 php -a 舒服得多。安装过程可以参考 PsySH 官网

$a = gzinflate(base64_decode('...'));
echo $a;

主程序

func(123);

这个 func 函数在下面一节介绍,这里传入了一个 123 不知道是干什么的。有可能是个入口点。

主函数

这个函数比较复杂,里面的乱码较多,我把刚才的 format.php 改了一下,自己额外写了一个 PHP Parser 的 NodeVisitor 自动进行名称替换,把乱码替换成 $v0 $arg0 这类名称。

代码为附件中的 format2.php

[Info]

上面这段代码来自 ganlvtech/php-enphp-decoder/src/NodeVisitors/FunctionLocalVariableRenameNodeVisitor.php

重新运行之后是这样的

可以看到乱码还是没有被完全去除。简单分析一下,原来是中间有一段是 eval 的代码,必须对可以执行的字符串也重命名变量。

修改后的代码为附件中的 format3.php,这样代码就几乎可读了。

[Tips]

执行时,如果出现 PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted,那么到 php.ini 中把 memory_limit 调大点,然后再试一次。

然后我们就要一步一步的“单步运行”分析这个 VM 到底是什么原理了。

第 1 段
$v0 = gzinflate(base64_decode("..."));
$v1 = eval(gzinflate(base64_decode("...")));
$v2 = '';
$v3 = '$v4 = $v1[36]($v2, $v5);
    if ($v4 === false) {
        $v6++;
        $v5[$v6] = $v2;
        $v7[$v6] = NULL;
        $v4 = $v6;
    }
    return $v4;';
$v8 = true;
$v9 = array($v1[4], $v1[18], $v1[9], $v1[13], $v1[6], $v1[12], $v1[2], $v1[35], $v1[8], $v1[1], $v1[14], $v1[16], null, $v1[27], $v1[17], $v1[41], null, $v1[33], $v1[29], $v1[43], $v1[31]);

$v0 那一句执行一下

不完全是乱码,有一些规律,我猜测是 VM 的字节码,还不确定。

$v1 那一句 eval 括号内的部分执行一下。未知的代码一定不要直接 eval,防止可能出现的问题。

执行

echo gzinflate(base64_decode("..."));

得到如下结果

return array (
  0 => 'openssl_decrypt',
  1 => 'resource',
  2 => 'string',
  3 => '__construct',
  4 => 'undef',
  5 => 'microtime',
  6 => 'int',
  7 => 'hi debugger~',
  8 => 'object',
  9 => 'false',
  10 => '1;',
  11 => 'Ganlv Encrypt VM Error: Unhandled',
  12 => 'double',
  13 => 'true',
  14 => 'reference',
  15 => 'func1',
  16 => 'constant',
  17 => 'callable',
  18 => 'null',
  19 => 'intval',
  20 => 'count',
  21 => 'strpos',
  22 => 'call_user_func_array',
  23 => 'gzinflate',
  24 => '#opcodeString',
  25 => 'new',
  26 => 'AES-128-ECB',
  27 => 'bool',
  28 => 'array_key_exists',
  29 => 'void',
  30 => 'is_',
  31 => 'error',
  32 => 'na/Nz',
  33 => 'ptr',
  34 => 'array_pop',
  35 => 'array',
  36 => 'array_search',
  37 => 'c/N*',
  38 => 'substr',
  39 => 'strlen',
  40 => 'usleep',
  41 => 'indirect',
  42 => 'base64_decode',
  43 => 'iterable',
  44 => 'unpack',
);

这个是常量池,里面有一些用到的函数名和字符串常量。这个和 EnPHP 比较类似。注意第 15 个 func1 为上面那个乱码函数的名称乱码,这里我写成了 func1 避免乱码。

$v2 = ''; 暂时不知道是做什么用的。

$v3 的代码必须等到 eval 的时候才能结合上下文分析。

$v8 看下文是循环终止条件。

$v9 执行一下可以得到以下结果。

$v9 = [
    "undef",
    "null",
    "false",
    "true",
    "int",
    "double",
    "string",
    "array",
    "object",
    "resource",
    "reference",
    "constant",
    null,
    "bool",
    "callable",
    "indirect",
    null,
    "ptr",
    "void",
    "iterable",
    "error",
];

也不知道 $v9 是做什么用的,似乎是 VM 用到的一些常量。

第 1 段小结
变量 说明
$v0 数据
$v1 常量池
$v3 一段代码
$v9 似乎是 VM 用到的一些常量
$v2,$v4, $v5,$v6, $v7,$v8 暂时未知
常量池替换

我又把 format.php 修改了一些,这样可以自动替换 $v1 的字符串。代码在附件 format4.php。完整结果如下。

function func($arg0)
{
    $v0 = gzinflate(base64_decode("ZVPNbtNAEP6cEIfWhSZ2gk1aCpSfQkrbJE1iFyg0jQTHRihFvXJo4YAEAoEECCEk7nDgHehTIPEMPAEXLrwCB2bHO6tdEynWzs7P983Mt+Ppo+zl3nSymr0+Oti8/3j36cbk6N7kxcO3o/Grg2cPPGwCCDAAmu/p5CkzwU2gtaLMHn1mAqDykQ4Rh7aBuV8S2kQNCCfK7KtPR31S+sziElD9gvwXYAQsHkvWWbSAOmWi1O1oBCqV/2sYU0Jb0L2cYCr5fTZXbBI+rmn6NSwTnaqQYG+EeWD+t4ap/6XDGa4R2DUidbmwrvl6h4b4DZt4DKJb/aSdPJivdGhwZMOODLAg5Xs80tO6Y28oDe9zQRW65DIJLb6VP5ytoih26YeKyvjqCjVecnZEiOqWKtyxKvg0Tgy5Qs9mmGBdKA2EUltTimlFbp9+WbsSXJf1ZJL2XFnsu/qfcJ6YvEWBk7zwnZ5djMtFuDd6TRF2rV7KAy2SACell5SL3yUzFZOLk4aRk64XioeHRgMld2ezYmZs3pZXkXKdrSLJln4Uaj/Fvnd0bwmq0rf4ws86J8ZOsaS4IpzSXDiDJKFETtertjg+qCfEhW4VdfldTypRDHP8LdnXyMy9W0z7aeR20Z1F053UjJi5un3B6AoGNH6MC0WMYz3+GJWia2peU+Li9e0nEmDNfTFDW+9rZrtl2V+HzW1X/qTYSsOWjBlngA0X7rwLd4IAzGq2zWrmrI353yhvn5PPSXI+yMyw+Ac="));
    $v1 = eval(gzinflate(base64_decode("TZOxThwxEIZ7nsISBRAFwS57BxdEpCS6MjQoFDSW1zt752Ds1XgNOYoUeZG8AE+DIhp6XoA6Eot/70E53+yO55uxmfrITihmtRLbG0Lsi5PPYst35EKwsiHNq67f+jhkipRhCj6ypoTKhELPxi0SOEhASu3dQKPGn1Wi0TXUpniS4iuj2ffmCqWmiRmHPw5TtDSioTouFsS/Ez5Cc/VPypVnCbTKBlQp0H5xjAgtX0zmLmmI8+9izuz5k/jhlso1lhp8CJHGx9rmQjAZHHJcZfuWmFzWLyDycP/v6fn/3ePfP6BQSSNQ2aeAkFbWqvURsHHRWsSzcQTXCqTcz5ViLlMW48A7H0DKdWEZA7Fso9MyrRN5eCxujWut6vPaILPpO+0bOntbXwkhRzcIYfJlfrZblEe7829fgSFTe5/bhEg6VF7SStIvE/rcH6SuvcGkD6BkgkQIIXpdCgB8nNo7vQWAQNfnfPXurM53gJM3CDB991UgxXoJntewd/oBMToPsQ5j/dk4YUsOlxcdx2CJcFxV5EU1hseLWKHvWgWaVq/PZhgsEujf9MTrzVfjg+iUvhzIzvEL")));
    $v2 = '';
    $v3 = '$v4 = array_search($v2, $v5);
if ($v4 === false) {
    $v6++;
    $v5[$v6] = $v2;
    $v7[$v6] = NULL;
    $v4 = $v6;
}
return $v4;';
    $v8 = true;
    $v9 = array('undef', 'null', 'false', 'true', 'int', 'double', 'string', 'array', 'object', 'resource', 'reference', 'constant', null, 'bool', 'callable', 'indirect', null, 'ptr', 'void', 'iterable', 'error');
    while ($v8) {
        $v8 = false;
        $v10 = microtime(true) * 1000;
        $v11 = array();
        $v12 = strpos($v0, "\1");
        $v13 = substr($v0, $v12 + 3);
        $v4 = unpack("n", substr($v0, $v12 + 1, 2));
        $v14 = $v4[1];
        unset($v4);
        $v4 = 0;
        eval('1;');
        $v15 = strlen($v13);
        unset($v12);
        $v16 = microtime(true) * 1000;
        if ($v16 - $v10 > 1000) {
            exit('hi debugger~');
        }
        while ($v4 < $v15) {
            $v12 = unpack("N", substr($v13, $v4, 4));
            $v17 = $v12[1];
            $v18 = unpack('na/Nz', substr($v13, $v4 + 4, 6));
            $v18['s'] = substr($v13, $v4 + 10, $v17 - 6);
            unset($v12);
            $v19 = 0;
            $v12 = strlen($v18['s']);
            $v20 = array('');
            while ($v19 < $v12) {
                $v21 = unpack("N", substr($v18['s'], $v19, 4));
                $v20[] = $v21[1] > 0 ? substr($v18['s'], $v19 + 4, $v21[1]) : '';
                $v19 += $v21[1] + 4;
            }
            unset($v12);
            $v18['s'] = $v20;
            $v11[] = $v18;
            $v4 += $v17 + 4;
            unset($v17);
        }
        $v22 = '$v23 = substr($v24, 1);
if ($v23[0] == 0) {
    return substr($v23, 1);
}
$v25 = intval($v23[0]);
$v26 = substr($v23, 0, $v25 + 1);
$v27 = substr($v23, $v25 + 1);
$v28 = openssl_decrypt($v27, \'AES-128-ECB\', $v26, OPENSSL_RAW_DATA);
return $v24[0] === "\\6" ? $v28 : eval("return {$v28};");';
        $v7 = array();
        $v29 = array();
        $v30 = array();
        $v31 = 0;
        $v32 = array();
        $v24 = '';
        $v33 = null;
        $v13 = null;
        $v34 = false;
        $v35 = null;
        $v36 = count($v11);
        $v12 = strpos($v0, "\1");
        $v24 = base64_decode(substr($v0, 0, $v12));
        $v5 = eval($v22);
        unset($v12);
        $v6 = $v36 * 3;
        while ($v14 < $v36) {
            $v37 = $v11[$v14];
            if (!$v37) {
                throw new Exception('GanlvEncrypt VM Error: Unhandled');
            }
            $v38 = $v37['s'];
            $v39 = $v37['z'];
            $v14 = $v37['a'];
            switch ($v39) {
                case 0x1a67:
                    $v7[$v38[1]] = __SHAREVM_FUNCTION__;
                    break;
                case 0x1d89:
                    $v35 = $v38[1] === "" ? "" : (array_key_exists($v38[1], $v7) ? $v7[$v38[1]] : (($v13 = unpack('c/N*', $v38[1])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL));
                    $v14 = $v36;
                    break;
                case 0x930:
                    $v30[$v31 - 1][1][] = $v38[1] === "" ? "" : (array_key_exists($v38[1], $v7) ? $v7[$v38[1]] : (($v13 = unpack('c/N*', $v38[1])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL));
                    break;
                case 0x1384:
                    $v7[$v38[1]] = __CLASS__;
                    break;
                case 0xb2a:
                    if (count($v38) > 4) {
                        $v7[$v38[1]] = $v7[$v38[2]] = $v38[3] === "" ? "" : (array_key_exists($v38[3], $v7) ? $v7[$v38[3]] : (($v13 = unpack('c/N*', $v38[3])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL));
                    } else {
                        $v7[$v38[1]] = $v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL));
                    }
                    break;
                case 0xfe6:
                    $v30[$v31 - 1][1][] = $v38[1] === "" ? "" : (array_key_exists($v38[1], $v7) ? $v7[$v38[1]] : (($v13 = unpack('c/N*', $v38[1])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL));
                    break;
                case 0xac3:
                    $v14 = (bool) ($v38[1] === "" ? "" : (array_key_exists($v38[1], $v7) ? $v7[$v38[1]] : (($v13 = unpack('c/N*', $v38[1])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL))) ? ($v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL))) - 1 : $v14;
                    break;
                case 0x189e:
                    $v33 =& $v7[$v38[2]];
                    $v7[$v38[1]] = count($v33);
                    unset($v33);
                    break;
                case 0x14b9:
                    $v40 = 'is_' . $v9[substr($v38[4], 2)];
                    $v7[$v38[1]] = $v40($v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL)));
                    break;
                case 0x1783:
                    $v7[$v38[1]] = ($v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL))) + ($v38[3] === "" ? "" : (array_key_exists($v38[3], $v7) ? $v7[$v38[3]] : (($v13 = unpack('c/N*', $v38[3])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL)));
                    break;
                case 0x1b2e:
                    if ($v38[3] !== '') {
                        $v40 = $v38[3];
                        $v12 = $v38[2];
                    } else {
                        $v40 = $v38[2];
                        $v12 = $v38[1];
                    }
                    $v12 = $v12 === "" ? "" : (array_key_exists($v12, $v7) ? $v7[$v12] : (($v13 = unpack('c/N*', $v12)) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL));
                    if ($v40 === 'e') {
                        $v40 = eval($v12);
                    } else {
                        if ($v40 === 'i') {
                            $v40 = (include_once $v12);
                        } else {
                            $v40 = (include $v12);
                        }
                    }
                    if ($v38[3] !== '') {
                        $v7[$v38[1]] = $v40;
                    }
                    break;
                case 0x602:
                    $v40 = $v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL));
                    if ($v40 === '') {
                        $v40 = __CLASS__;
                    }
                    $v30[] = array($v40, array(), array('new', $v38[1]));
                    $v31++;
                    break;
                case 0x6f9:
                    $v7[$v38[1]] = ($v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL))) < ($v38[3] === "" ? "" : (array_key_exists($v38[3], $v7) ? $v7[$v38[3]] : (($v13 = unpack('c/N*', $v38[3])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL)));
                    break;
                case 0x11af:
                    $v30[] = array($v38[1] === "" ? "" : (array_key_exists($v38[1], $v7) ? $v7[$v38[1]] : (($v13 = unpack('c/N*', $v38[1])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL)), array());
                    $v31++;
                    break;
                case 0x1ec1:
                    $v7[$v38[1]] = __FUNCTION__;
                    break;
                case 0x514:
                    $v7[$v38[1]] = ($v38[1] === "" ? "" : (array_key_exists($v38[1], $v7) ? $v7[$v38[1]] : (($v13 = unpack('c/N*', $v38[1])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL))) - ($v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL)));
                    break;
                case 0x72c:
                    $v34 = false;
                    break;
                case 0x561:
                    break;
                case 0x16a8:
                    echo $v38[1] === "" ? "" : (array_key_exists($v38[1], $v7) ? $v7[$v38[1]] : (($v13 = unpack('c/N*', $v38[1])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL));
                    break;
                case 0x614:
                    break;
                case 0x1a27:
                    if ($v38[1] === "") {
                        $v33 = $this;
                    } else {
                        $v33 = $v38[1] === "" ? "" : (array_key_exists($v38[1], $v7) ? $v7[$v38[1]] : (($v13 = unpack('c/N*', $v38[1])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL));
                    }
                    $v30[] = array(array($v33, $v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL))), array());
                    $v31++;
                    break;
                case 0x790:
                    exit($v38[1] === "" ? "" : (array_key_exists($v38[1], $v7) ? $v7[$v38[1]] : (($v13 = unpack('c/N*', $v38[1])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL)));
                    break;
                case 0xedc:
                    break;
                case 0x1ca8:
                    $v30[$v31 - 1][1][] = $v38[1] === "" ? "" : (array_key_exists($v38[1], $v7) ? $v7[$v38[1]] : (($v13 = unpack('c/N*', $v38[1])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL));
                    break;
                case 0x784:
                    $v30[] = array($v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL)), array());
                    $v31++;
                    break;
                case 0x16fd:
                    unset($v7[$v38[1]]);
                    break;
                case 0xb6f:
                    $v7[$v38[1]] = ($v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL))) * ($v38[3] === "" ? "" : (array_key_exists($v38[3], $v7) ? $v7[$v38[3]] : (($v13 = unpack('c/N*', $v38[3])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL)));
                    break;
                case 0xce1:
                    $v32 = array_pop($v30);
                    $v31--;
                    if ($v34) {
                        $v41 = @call_user_func_array($v32[0], $v32[1]);
                    } else {
                        $v41 = call_user_func_array($v32[0], $v32[1]);
                    }
                    if ($v38[1] !== '') {
                        $v7[$v38[1]] = $v41;
                    }
                    break;
                case 0x12ba:
                    $v40 = $v7[$v38[1]];
                    $v14 = ($v40 ? $v7[$v38[2]] : $v7[$v38[3]]) - 1;
                    break;
                case 0xa33:
                    $v7[$v38[1]] = ($v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL))) == ($v38[3] === "" ? "" : (array_key_exists($v38[3], $v7) ? $v7[$v38[3]] : (($v13 = unpack('c/N*', $v38[3])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL)));
                    break;
                case 0x1250:
                    $v7[$v38[1]] = ($v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL))) - ($v38[3] === "" ? "" : (array_key_exists($v38[3], $v7) ? $v7[$v38[3]] : (($v13 = unpack('c/N*', $v38[3])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL)));
                    break;
                case 0x1c37:
                    $v14 = (bool) ($v38[1] === "" ? "" : (array_key_exists($v38[1], $v7) ? $v7[$v38[1]] : (($v13 = unpack('c/N*', $v38[1])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL))) ? $v14 : ($v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL))) - 1;
                    break;
                case 0x13c5:
                    $v7[$v38[1]] = ($v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL))) === ($v38[3] === "" ? "" : (array_key_exists($v38[3], $v7) ? $v7[$v38[3]] : (($v13 = unpack('c/N*', $v38[3])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL)));
                    break;
                case 0x12c2:
                    $v7[$v38[1]] = ($v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL))) !== ($v38[3] === "" ? "" : (array_key_exists($v38[3], $v7) ? $v7[$v38[3]] : (($v13 = unpack('c/N*', $v38[3])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL)));
                    break;
                case 0x1110:
                    $v40 = $v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL));
                    $v7[$v38[1]] =& ${$v40};
                    break;
                case 0xfea:
                    $v7[$v38[1]] = (bool) ($v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL)));
                    break;
                case 0x936:
                    $v34 = true;
                    break;
                case 0x157d:
                    $v32 = array_pop($v30);
                    $v31--;
                    if (isset($v32[2])) {
                        if ($v32[2][0] === 'new') {
                            $v42 = new ReflectionClass($v32[0]);
                            if ($v42->hasMethod('__construct')) {
                                if ($v34) {
                                    $v43 = @$v42->newInstanceArgs($v32[1]);
                                } else {
                                    $v43 = $v42->newInstanceArgs($v32[1]);
                                }
                            } else {
                                if ($v34) {
                                    $v43 = @$v42->newInstance();
                                } else {
                                    $v43 = $v42->newInstance();
                                }
                            }
                            $v7[$v32[2][1]] = $v43;
                        }
                    } else {
                        if ($v34) {
                            $v41 = @call_user_func_array($v32[0], $v32[1]);
                        } else {
                            $v41 = call_user_func_array($v32[0], $v32[1]);
                        }
                        if ($v38[1] !== '') {
                            $v7[$v38[1]] = $v41;
                        }
                    }
                    break;
                case 0x1207:
                    $v7[$v38[1]] = !(bool) ($v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL)));
                    break;
            }
            usleep(2000);
        }
        foreach ($v7 as $v44 => &$v29) {
            unset($v7[$v44]);
        }
        unset($v11);
        unset($v7);
        unset($v29);
        unset($v30);
        unset($v32);
        unset($v40);
        unset($v33);
    }
    return $v35;
}
第 2 段
while ($v8) {
    $v8 = false;
    $v10 = microtime(true) * 1000;
    $v11 = array();
    $v12 = strpos($v0, "\1");
    $v13 = substr($v0, $v12 + 3);
    $v4 = unpack("n", substr($v0, $v12 + 1, 2));
    $v14 = $v4[1];
    unset($v4);
    $v4 = 0;
    eval('1;');
    $v15 = strlen($v13);
    unset($v12);
    $v16 = microtime(true) * 1000;
    if ($v16 - $v10 > 1000) {
        exit('hi debugger~');
    }

这段代码有很多语句似乎暂时没有用到。

while

这个 while 应该是虚拟机的主循环。

microtime

根据变量的赋值与调用关系,这两个 microtime 应该是在一起的,调整一下代码顺序。

$v10 = microtime(true) * 1000;
eval('1;');
$v16 = microtime(true) * 1000;
if ($v16 - $v10 > 1000) {
    exit('hi debugger~');
}

这段是简单地防止 eval hook。如果在 eval('1;'); 那一句断点超过 1 秒钟就会直接输出 hi debugger~ 然后退出。

作者可能想得太多了,我根本就不想用 eval hook 这种方法。而且,都有 VM 加密了,防止 eval 这种低级加密,就显得有点多余。

能想出 eval hook 的人,基本上都能绕过这个防御。原理很简单,不在这里设断点,或者断点时间小于 1 秒,或者把 eval 删掉就行了。

VM 初始化
$v11 = array();
$v12 = strpos($v0, "\1"); // 分隔符位置
$v13 = substr($v0, $v12 + 3);
$v4 = unpack("n", substr($v0, $v12 + 1, 2));
$v14 = $v4[1];
unset($v4);
$v4 = 0;
$v15 = strlen($v13);
unset($v12);

[Info]

unpack 函数中的 "n" 是指按大端序读取一个 16 位有符号整数

$v0 的前 32 字节似乎是校验,然后有一个 0x01 用于分割,然后的两字节是那个 51 暂时不知道是做什么的,有可能是入口点。后面的全部应该是字节码,赋值给 $v13 了,$v15 是后面部分的长度。

最后结果就是

$v11 = array();
$v13 = '...'; // 猜测是字节码
$v14 = 51; // 猜测是入口点指针位置
$v4 = 0; // 循环变量或者指针
$v15 = 1402; // 字节码总长度
第 2 段小结
变量 说明
$v8 控制循环的变量
$v10 eval 前时间
$v16 eval 后时间
$v11 暂时未知
$v4 临时变量
$v12 临时变量:分隔符位置
$v13 猜测是字节码
$v14 猜测是入口点指针位置
$v15 字节码总长度
第 3 段

与上一段的结果连起来,配上注释如下。

$v11 = array(); // 结构化指令数组
$v13 = '...'; // 字节码
$v4 = 0; // 临时的循环变量
$v15 = 1402; // 字节码总长度
while ($v4 < $v15) {
    $v12 = unpack("N", substr($v13, $v4, 4)); // 读取 4 字节整数
    $v17 = $v12[1]; // 本条指令的字节码长度
    $v18 = unpack('na/Nz', substr($v13, $v4 + 4, 6)); // 读取 2 字节整数,赋值给 $v18['a']。读取 4 字节整数,命名为 $v18['z']。
    $v18['s'] = substr($v13, $v4 + 10, $v17 - 6); // 读取本条指令剩余部分字节码
    unset($v12);
    $v19 = 0; // 剩余部分指针
    $v12 = strlen($v18['s']); // 长度
    $v20 = array('');
    while ($v19 < $v12) {
        $v21 = unpack("N", substr($v18['s'], $v19, 4)); // 读取 4 字节整数
        $v20[] = $v21[1] > 0 ? substr($v18['s'], $v19 + 4, $v21[1]) : ''; // 大于零则添加一个参数,参数长度为刚刚读取的 4 字节整数
        $v19 += $v21[1] + 4; // 移动属性部分指针
    }
    unset($v12);
    $v18['s'] = $v20;
    $v11[] = $v18; // 把指令添加到结构化指令数组中
    $v4 += $v17 + 4; // 移动指针
    unset($v17);
}

直接执行完这段都不会出现什么安全问题。因为 eval 必须明文写出来,不能替换函数名,而这段没有 eval,所以可以安全地在 PsySH 中执行。

得到的 $v11 是最关键的。

部分 $v11 如下

$v11 = [
    [
        "a" => 53,
        "z" => 5501,
        "s" => [
            "",
            "3",
        ],
    ],
    [
        "a" => 58,
        "z" => 6695,
        "s" => [
            "",
            "2",
            b"\v\0\0\x05ü\0\0\0\x13",
        ],
    ],
    [
        "a" => 42,
        "z" => 3297,
        "s" => [
            "",
            "3",
        ],
    ],
    [
        "a" => 16,
        "z" => 4688,
        "s" => [
            "",
            "4",
            "0",
            "7",
        ],
    ],
    [
        "a" => 35,
        "z" => 1936,
        "s" => [
            "",
            "",
        ],
    ],
    [
        "a" => 65,
        "z" => 7336,
        "s" => [
            "",
            "3",
        ],
    ],
    [
        "a" => 26,
        "z" => 4368,
        "s" => [
            "",
            "10",
            "\v\0\0\0\x15\0\0\0\x15",
        ],
    ],
    [
        "a" => 67,
        "z" => 2858,
        "s" => [
            "",
            "2",
            "3",
        ],
    ],
];

可以看到,$v11 是由大量指令构成数组,每条指令有 a z s 三个属性,a 是一个 16 字节整数,z 是一个 32 字节整数,s 是一个数组,第一项是空字符串 “”

第 3 段小结
变量 说明
$v11 结构化指令数组
$v13 字节码
$v4 临时变量:字节码读取指针位置
$v12 临时变量:用于 unpack 和参数部分字节码长度
$v17 本条指令的字节码长度
$v18 单条指令,有 a, z, s 三个属性
$v19 本条指令剩余部分字节码的指针位置
$v20 指令 s 参数的临时列表
$v21 临时变量:用于 unpack
第 4 段
$v22 = '$v23 = substr($v24, 1);
    if ($v23[0] == 0) {
        return substr($v23, 1);
    }
    $v25 = intval($v23[0]);
    $v26 = substr($v23, 0, $v25 + 1);
    $v27 = substr($v23, $v25 + 1);
    $v28 = openssl_decrypt($v27, \'AES-128-ECB\', $v26, OPENSSL_RAW_DATA);
    return $v24[0] === "\\6" ? $v28 : eval("return {$v28};");';
$v7 = array();
$v29 = array();
$v30 = array();
$v31 = 0;
$v32 = array();
$v24 = '';
$v33 = null;
$v13 = null;
$v34 = false;
$v35 = null;
$v36 = count($v11); // 指令总数
$v12 = strpos($v0, "\1"); // 分隔符位置
$v24 = base64_decode(substr($v0, 0, $v12)); // 24 字节校验值
$v5 = eval($v22); // 解码关键数据
unset($v12);
$v6 = $v36 * 3;
解码分析
$v23 = substr($v24, 1); // 去除最前面的 1 个字节剩下 23 字节
if ($v23[0] == 0) {
    return substr($v23, 1); // 如果 23 字节中的第 1 字节是 '0' 或 "\0"(注意 == 仅值相等的等于号),则直接返回后面 22 个字节
}
$v25 = intval($v23[0]); // 23 字节中的第 1 字节字符转换成数字
$v26 = substr($v23, 0, $v25 + 1); // 前 $v25 + 1 字节
$v27 = substr($v23, $v25 + 1); // 剩下的全部字节
$v28 = openssl_decrypt($v27, 'AES-128-ECB', $v26, OPENSSL_RAW_DATA); // 用 $v26 解码 $v27 得到 $v28
return $v24[0] === "\6" ? $v28 : eval("return {$v28};"); // 如果 24 字节中的第 1 个字节是 0x06 那就返回 $v28,否则返回 $v28 的执行结果

返回值赋给 $v5

本段代码也没有风险,直接执行,得到执行结果 $v5 = array();

比较奇怪,解码之后是个空数组。

第 4 段小结
变量 说明
$v22 解码代码
$v36 指令总数
$v12 临时变量:分隔符位置
$v24 24 字节特殊数据
$v5 某关键数据解码之后结果
$v6 指令长度的 3 倍
$v7,$v29, $v30,$v31, $v32,$v13, $v34,$v35 未知
第 5 段
while ($v14 < $v36) {
    $v37 = $v11[$v14];
    if (!$v37) {
        throw new Exception('Ganlv Encrypt VM Error: Unhandled');
    }
    $v38 = $v37['s'];
    $v39 = $v37['z'];
    $v14 = $v37['a'];
    switch ($v39) {
        case 0x1a67:
            $v7[$v38[1]] = __SHAREVM_FUNCTION__;
            break;
        case 0x1d89:
            $v35 = $v38[1] === "" ? "" : (array_key_exists($v38[1], $v7) ? $v7[$v38[1]] : (($v13 = unpack('c/N*', $v38[1])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL));
            $v14 = $v36;
            break;
        case 0xac3:
            $v14 = (bool) ($v38[1] === "" ? "" : (array_key_exists($v38[1], $v7) ? $v7[$v38[1]] : (($v13 = unpack('c/N*', $v38[1])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL))) ? ($v38[2] === "" ? "" : (array_key_exists($v38[2], $v7) ? $v7[$v38[2]] : (($v13 = unpack('c/N*', $v38[2])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL))) - 1 : $v14;
            break;
        // 此处省略大量指令
    }
    usleep(2000);
}
读取指令部分
while ($v14 < $v36) { // $v14 是循环变量 $v36 是指令总数
    $v37 = $v11[$v14]; // 读取一条指令
    if (!$v37) {
        throw new Exception('Ganlv Encrypt VM Error: Unhandled');
    }
    $v38 = $v37['s']; // s 属性是指令的一些关键参数,具体不同指令可能存在差别(字符串数组)
    $v39 = $v37['z']; // z 属性是指令类型(32 位整数)
    $v14 = $v37['a']; // a 属性还不太清楚(16 位整数)
指令分支部分

根据 z 属性的不同数值,按照不同指令分支去执行。

有一段代码似乎经常出现

$v38[1] === "" ? "" : (array_key_exists($v38[1], $v7) ? $v7[$v38[1]] : (($v13 = unpack('c/N*', $v38[1])) || 1 ? ($v18 = substr(func1(), $v13[1], $v13[2])) || 1 ? ($v13 = $v18[0]) && ($v13 === "\6" || $v13 === "\t") ? ($v24 = $v18) || 1 ? eval($v22) : "" : ($v13 === "\4" ? (int) substr($v18, 1) : ($v13 === "\5" ? (double) substr($v18, 1) : ($v13 === "\7" ? true : ($v13 === "\10" ? false : NULL)))) : NULL : NULL))

翻译一下

if ($v38[1] === "") {
    return ""; // 为空则直接返回空字符串
} else {
    if (array_key_exists($v38[1], $v7)) {
        return $v7[$v38[1]]; // 在 $v7 中存在则返回这一项,暂时不清楚 $v7 是什么东西,可能是堆、栈或者全局内存区
    } else {
        $v13 = unpack('c/N*', $v38[1]); // 读取 1 个字符和 2 个 32 位整数
        $v18 = substr(func1(), $v13[1], $v13[2]); // 在 func1() 的数据中找到数据
        $v13 = $v18[0]; // 第 1 字节是该指令类型
        if ($v13 === "\x06" || $v13 === "\x09") {
            $v24 = $v18;
            return eval($v22); // 读取特殊常量,例如 array()
        } elseif ($v13 === "\x04") {
            return (int)substr($v18, 1); // 读取整数常量
        } elseif ($v13 === "\x05") {
            return (double)substr($v18, 1); // 读取实数常量
        } elseif ($v13 === "\x07") {
            return true;  // 读取 true
        } elseif ($v13 === "\x08") {
            return false; // 读取 false
        } else {
            return null; // 读取 null
        }
    }
}
延时部分
usleep(2000);

试用版运行缓慢。每条指令直接都延时 2 毫秒能不慢吗!

第 5 段小结
变量 说明
$v14 临时变量:当前循环运行指令的序号
$v37 单条指令
$v38 s 属性:指令的一些关键参数(字符串数组)
$v39 z 属性:指令类型(32 位整数)
$v14 a 属性:下一条指令的序号(16 位整数)
$v13,$v18, $v24 指令分支中解码函数中的临时变量
第 6 段
        foreach ($v7 as $v44 => &$v29) {
            unset($v7[$v44]);
        }
        unset($v11);
        unset($v7);
        unset($v29);
        unset($v30);
        unset($v32);
        unset($v40);
        unset($v33);
    }
    return $v35;
}

重置虚拟机。从这几个 unset 可以看出来这些变量是真正要存数据的变量,其他不需要 unset 的变量只是临时变量。

变量 说明
$v7
$v11 指令集
$v29,$v30, $v32,$v40, $v33

分析指令

每条指令都有那一坨代码,为了方便之后理解,我又修改了一个 format5.php,让其把那一坨东西变成 get_value($...),这样就方便阅读了。

switch ($v39) {
    case 0x1a67:
        $v7[$v38[1]] = __SHAREVM_FUNCTION__;
        break;
    case 0x1d89:
        $v35 = get_value($v38[1]);
        $v14 = $v36;
        break;
    case 0x930:
        $v30[$v31 - 1][1][] = get_value($v38[1]);
        break;
    case 0x1384:
        $v7[$v38[1]] = __CLASS__;
        break;
    case 0xb2a:
        if (count($v38) > 4) {
            $v7[$v38[1]] = $v7[$v38[2]] = get_value($v38[3]);
        } else {
            $v7[$v38[1]] = get_value($v38[2]);
        }
        break;
    case 0xfe6:
        $v30[$v31 - 1][1][] = get_value($v38[1]);
        break;
    case 0xac3:
        $v14 = (bool) get_value($v38[1]) ? get_value($v38[2]) - 1 : $v14;
        break;
    case 0x189e:
        $v33 =& $v7[$v38[2]];
        $v7[$v38[1]] = count($v33);
        unset($v33);
        break;
    case 0x14b9:
        $v40 = 'is_' . $v9[substr($v38[4], 2)];
        $v7[$v38[1]] = $v40(get_value($v38[2]));
        break;
    case 0x1783:
        $v7[$v38[1]] = get_value($v38[2]) + get_value($v38[3]);
        break;
    case 0x1b2e:
        if ($v38[3] !== '') {
            $v40 = $v38[3];
            $v12 = $v38[2];
        } else {
            $v40 = $v38[2];
            $v12 = $v38[1];
        }
        $v12 = get_value($v12);
        if ($v40 === 'e') {
            $v40 = eval($v12);
        } else {
            if ($v40 === 'i') {
                $v40 = (include_once $v12);
            } else {
                $v40 = (include $v12);
            }
        }
        if ($v38[3] !== '') {
            $v7[$v38[1]] = $v40;
        }
        break;
    case 0x602:
        $v40 = get_value($v38[2]);
        if ($v40 === '') {
            $v40 = __CLASS__;
        }
        $v30[] = array($v40, array(), array('new', $v38[1]));
        $v31++;
        break;
    case 0x6f9:
        $v7[$v38[1]] = get_value($v38[2]) < get_value($v38[3]);
        break;
    case 0x11af:
        $v30[] = array(get_value($v38[1]), array());
        $v31++;
        break;
    case 0x1ec1:
        $v7[$v38[1]] = __FUNCTION__;
        break;
    case 0x514:
        $v7[$v38[1]] = get_value($v38[1]) - get_value($v38[2]);
        break;
    case 0x72c:
        $v34 = false;
        break;
    case 0x561:
        break;
    case 0x16a8:
        echo get_value($v38[1]);
        break;
    case 0x614:
        break;
    case 0x1a27:
        if ($v38[1] === "") {
            $v33 = $this;
        } else {
            $v33 = get_value($v38[1]);
        }
        $v30[] = array(array($v33, get_value($v38[2])), array());
        $v31++;
        break;
    case 0x790:
        exit(get_value($v38[1]));
        break;
    case 0xedc:
        break;
    case 0x1ca8:
        $v30[$v31 - 1][1][] = get_value($v38[1]);
        break;
    case 0x784:
        $v30[] = array(get_value($v38[2]), array());
        $v31++;
        break;
    case 0x16fd:
        unset($v7[$v38[1]]);
        break;
    case 0xb6f:
        $v7[$v38[1]] = get_value($v38[2]) * get_value($v38[3]);
        break;
    case 0xce1:
        $v32 = array_pop($v30);
        $v31--;
        if ($v34) {
            $v41 = @call_user_func_array($v32[0], $v32[1]);
        } else {
            $v41 = call_user_func_array($v32[0], $v32[1]);
        }
        if ($v38[1] !== '') {
            $v7[$v38[1]] = $v41;
        }
        break;
    case 0x12ba:
        $v40 = $v7[$v38[1]];
        $v14 = ($v40 ? $v7[$v38[2]] : $v7[$v38[3]]) - 1;
        break;
    case 0xa33:
        $v7[$v38[1]] = get_value($v38[2]) == get_value($v38[3]);
        break;
    case 0x1250:
        $v7[$v38[1]] = get_value($v38[2]) - get_value($v38[3]);
        break;
    case 0x1c37:
        $v14 = (bool) get_value($v38[1]) ? $v14 : get_value($v38[2]) - 1;
        break;
    case 0x13c5:
        $v7[$v38[1]] = get_value($v38[2]) === get_value($v38[3]);
        break;
    case 0x12c2:
        $v7[$v38[1]] = get_value($v38[2]) !== get_value($v38[3]);
        break;
    case 0x1110:
        $v40 = get_value($v38[2]);
        $v7[$v38[1]] =& ${$v40};
        break;
    case 0xfea:
        $v7[$v38[1]] = (bool) get_value($v38[2]);
        break;
    case 0x936:
        $v34 = true;
        break;
    case 0x157d:
        $v32 = array_pop($v30);
        $v31--;
        if (isset($v32[2])) {
            if ($v32[2][0] === 'new') {
                $v42 = new ReflectionClass($v32[0]);
                if ($v42->hasMethod('__construct')) {
                    if ($v34) {
                        $v43 = @$v42->newInstanceArgs($v32[1]);
                    } else {
                        $v43 = $v42->newInstanceArgs($v32[1]);
                    }
                } else {
                    if ($v34) {
                        $v43 = @$v42->newInstance();
                    } else {
                        $v43 = $v42->newInstance();
                    }
                }
                $v7[$v32[2][1]] = $v43;
            }
        } else {
            if ($v34) {
                $v41 = @call_user_func_array($v32[0], $v32[1]);
            } else {
                $v41 = call_user_func_array($v32[0], $v32[1]);
            }
            if ($v38[1] !== '') {
                $v7[$v38[1]] = $v41;
            }
        }
        break;
    case 0x1207:
        $v7[$v38[1]] = !(bool) get_value($v38[2]);
        break;
}

然后就是漫长的调试单步运行过程,检查每条指令的用途。

最后竟然发现,这是一个基于寄存器的虚拟机。这个基于栈的虚拟机设计的很有趣,不过我感觉它并没有之前分析过的 mfenc 的虚拟机效率高。

[Info]

基于栈的虚拟机比较简单,栈可以无限增长,参数直接 push 上去,调用时只能调用栈顶附近的几个值,调用结束后直接移动指针出栈。编译器不需要考虑任何运行时的问题。

基于寄存器的虚拟机比较麻烦,寄存器数量有限,但是每个寄存器都可以随意存取,而不是像栈只能存取栈顶附近的值,指令的数量会变少。编译过程中需要分配寄存器,比较麻烦。寄存器没有“出栈”过程,不知道变量的生命周期,反编译会稍微麻烦。

虚拟机执行的最开始会通过 ReflectFunction getFileName getStartLine 等函数截取虚拟机的代码部分,使用 hash_hmac 计算 sha1 哈希值,将哈希值 str_rot13,然后使用这个值作为秘钥使用 AES-256-CBC 算法进行 openssl_decrypt 二次解密字节码,如果无法解密,返回 false,则表明虚拟机被修改过,下一条指令输出 Hi you made me sad

这中间我用了一些技巧,因为 XDebug 支持在运行过程中改变变量的值,我自己额外写了一些代码用正确的结果替换了这个 false

$a = file_get_content('t2.php_'); // 整个文件
$b = trim(substr($b, strpos($b, '));'))); // 虚拟机部分代码
$c = str_rot13(hash_hmac('sha1', $b, base64_decode('xpi4cvvjX6i0p74cIhSgyoV1npiE1yo15cb9RY91g1I='))); // 解密 key,后面的 base64_decode 是来自调试过程中的相对应的函数参数

这样可以得到 $c = '33506rs803241o212511992r97opqr7sq509orsr';,然后在 XDebug 中执行下面代码替换掉 $v41

$v41 = openssl_decrypt($v32[1][0], $v32[1][1], '33506rs803241o212511992r97opqr7sq509orsr', $v32[1][3], $v32[1][4]);

解码的结果继续经过 gzinflate 解压缩,然后替换 $v0,当前虚拟机的字节码就被替换成脱壳后的字节码了,类似于一个自解压壳。在替换字节码之前,它会将 $v8 设置成 true 保证外层循环能重新开始。替换字节码后,当前指令的后续指令指针会设置在数组长度外,从而结束当前虚拟机循环。

结束内层虚拟机循环之后,外层循环重新开始,重新解码字节码,转换成指令序列,然后再进入内层循环,执行新的函数。

变量 说明
$v14 指令指针
$v7 寄存器
$v30 函数调用栈
$v31 函数调用栈指针
$v32 函数调用临时存放函数名和参数值
$v34 函数调用错误抑制
$v35 函数最终返回值
$v29,$v13 未知

不过这个虚拟机的指令集很精简。

序号 指令类型值 指令 参数数量 说明 参数1 参数2 参数3 参数4
1 0x1a67 __SHAREVM_FUNCTION__ 1 设置寄存器的值为 __SHAREVM_FUNCTION__ 目标寄存器
2 0x1d89 ret 1 跳出循环,返回 get_value 返回值 get_value
3 0x930 push 1 get_value 压入函数栈中最后一个函数的参数栈 函数参数 get_value
4 0x1384 __CLASS__ 1 设置寄存器的值为 __CLASS__ 目标寄存器
5 0xb2a mov 2 或 4 读取 get_value 到 1 或 2 个寄存器 目标寄存器 目标寄存器 2 或 get_value get_value 是否双重赋值
6 0xfe6 push 1 (指令重复)压入函数参数
7 0xac3 jnz 2 如果 get_value 为真则跳转到 get_value 判断条件 get_value 跳转目标指令序号 get_value
8 0x189e count 2 设置寄存器 get_value 为寄存器 get_value 的数组长度 结果寄存器 测试数组长度的寄存器
9 0x14b9 is_x 4 对寄存器 get_value 执行 is_ $v9[substr($v38[4], 2)] 结果储存在寄存器 get_value 结果寄存器 测试 is_xget_value
10 0x1783 add 3 加法 结果寄存器 加数 1 get_value 加数 2 get_value
11 0x1b2e eval_or_include 2 或 3 eval include_once include 结果寄存器或 eval 代码或 include 路径 get_value eval 代码或 include 路径 get_value或类型 类型
12 0x602 build_new 2 构造类构造语句 TODO
13 0x602 lt 3 小于 结果寄存器 小于号左边 get_value 小于号右边 get_value
14 0x11af build_call 1 构造函数调用语句 函数名 get_value
15 0x1384 __FUNCTION__ 1 设置寄存器的值为 __FUNCTION__ 目标寄存器
16 0x1783 dec 2 减法 被减数 get_value 和结果寄存器 减数 get_value
17 0x72c suppress_error_off 0 关闭抑制函数调用错误显示
18 0x561 nop 0 空指令
19 0x16a8 echo 1 输出 输出内容 get_value
20 0x614 nop 0 (指令重复)空指令
21 0x1a27 build_member_call 2 对象,空 = $this,非空为 get_value 方法名 get_value
22 0x790 exit 1 结果 get_value
23 0xedc nop 0 (指令重复)空指令
24 0x1ca8 push 1 (指令重复)压入函数参数
25 0x784 build_call 2 构造函数调用语句 函数名 get_value
26 0x16fd unlink 1 解除连接 目标寄存器
27 0xb6f mul 3
28 0xce1 call
29 0x12ba if 3 如果寄存器为真,则跳转到寄存器,否则跳转到寄存器 条件寄存器 条件为真指令位置 条件为假指令位置
30 0xa33 equal 3
31 0x1250 minus 3
32 0x1c37 jz
33 0x13c5 identical
34 0x12c2 not_identical
35 0x1110 link 连接
36 0xfea cast_bool
37 0x936 suppress_error 0 开启抑制函数调用错误显示
38 0x157d reflect_or_call
39 0x1207 not 2 取非 结果寄存器 被取非的寄存器 get_value

注:

  1. 指令类型值是随机的,这里只是我这个文件的值。
  2. 指令名称是我自己按照大部分 ASM 常用名称起的。
  3. get_value 函数就是前面那一坨代码。

翻译指令

因为他是基于寄存器的虚拟机,那么我就把所有的寄存器都写成类似 $reg1 这样的变量名,把所有的指令翻译成正常的 php 代码。

编写反汇编脚本

读懂虚拟机指令之后就编写反汇编脚本了,这个过程很复杂,没有耐心的人就不要尝试了。这个加密的说明文档中也提到了,只能增加破解成本,不能避免破解。前前后后大概需要一周的业余时间才能编写完。

代码中的各种技术

我上面编写的反汇编脚本采用静态反编译的方法直接绕过了所有反调试技术,不过我们也要欣赏一下这份代码。

花指令

代码中会出现乱跳转的现象,理论上可以通过静态分析,消除掉部分条件分支。

  1. 如果每次跳转的条件如果能直接解出来(恒成立或恒不成立),那这个跳转就可被移除掉。
  2. 如果两个跳转跳向同一个地点,那么也可以被移除掉。

版权信息

前面添加了 Welcome+to+GanlvEncrypt%3A+https%3A%2F%2Fganlvencrypt.com%2F GanlvEncrypted at 15594841957873 这两个版权信息。

phpdbg

if (function_exists('phpdbg_clear')) {
    phpdbg_clear();
}
if (function_exists('phpdbg_prompt')) {
    phpdbg_prompt(urldecode('Welcome+to+GanlvEncrypt%3A+https%3A%2F%2Fganlvencrypt.com%2F'));
}
if (php_sapi_name() === 'phpdbg') {
    return;
}

时间检测

if ('1559737928' < time()) {
    eval('throw new "Time Expired!";');
}
$t0 = microtime() * 1000;
eval('');
$t1 = microtime() * 1000;
if ($t1 - $t0 > 100) {
    return;
}

运行时间

@set_time_limit(ini_get('max_execution_time'));

xdebug

if (function_exists('xdebug_is_debugger_active')) {
    if (xdebug_is_debugger_active() && function_exists('xdebug_break')) {
        while (true) {
            eval('while(true){xdebug_break();}');
            xdebug_break();
        }
    }
}
if (function_exists('xdebug_disable')) {
    xdebug_disable();
}
if (function_exists('xdebug_is_enabled')) {
    echo 'dontdebugme';
    die(114);
}
$filename = xdebug_get_tracefile_name();
if ($filename !== false) {
    file_put_contents($xdebug_stop_trace(), 'dontdebugme');
}

原始文件的内容

if ($arg0 == 123) {
    echo 'hello';
} else {
    echo 'world';
}

评测报告

  1. 加密后的函数名、变量名均混淆,使用 0x80-0xff 的不可读字符干扰破解者。(破解过程:分析原理、编写反混淆代码。破解用时:约 1 小时)
  2. 数据均经过 gzdeflate base64 方式编码。(破解过程:编写反混淆代码。破解用时:约 10 分钟)
  3. 代码中的函数以及字符串常量使用常量池方式干扰破解过程。(破解过程:编写反混淆代码。破解用时:约 1 小时)
  4. 简单地防 eval hook。(破解过程:在这些代码出不下断点。破解用时:约 1 分钟)
  5. 指令使用 pack 方式存储成字节码。(破解过程:分析原理,单步调试。破解用时:约 1 小时)
  6. 每条指令的参数都使用 pack 方式存储在额外的另外一组字节码中。(破解过程:分析原理,单步调试。破解用时:约 1 小时)
  7. 每条指令存储前,会按不同类型编码,如果是特殊值则使用 AES 编码。(破解过程:分析原理,单步调试。破解用时:约 1 小时)
  8. 分析每条指令的用途(破解过程:分析原理,单步调试。破解用时:每条指令约 10 分钟)
  9. 完全相同的指令有不同的指令编码值(破解过程:观察,找规律。破解用时:每条指令约 10 分钟)
  10. 基于寄存器的虚拟机(破解过程:分析原理,单步调试。破解时间:约 20 分钟)
  11. 校验虚拟机代码(破解过程:分析原理,单步调试,通过 XDebug 修改值。破解用时:约 10 分钟)
  12. 编写反编译脚本(破解用时:大于 1 周)
  13. 花指令(破解过程:分析原理,单步调试。破解用时:约 20 分钟)
  14. phpdbg xdebug eval hook 反调试技术(破解用时:约 1 小时)

免费版每执行一条指令会暂停 2 毫秒,这个有些让人想不明白。同时他还设置了 @set_time_limit(ini_get('max_execution_time')); 避免运行超时。

虚拟机指令中没有添加无关代码,但不排除以后添加的可能性。

总结

这款加密的免费版本的加密强度很高,很难轻易破解,破解者可能需要花费一周以上的时间才能完整破解。

其实破解这个东西的确很有趣,加密制造了什么麻烦,我就一层一层将麻烦剥开,最后就会露出破解结果。

PHP 这些加密都还算比较简单的,因为代码只有这么少,没有大量的花指令,虚拟机也是用 PHP 写的,我可以毫不费力地改虚拟机代码,让其便于我注入代码或者导出数据。

附件

仅包含文中代码,没有成品。代码内容可以在 GitHub 上查看,代码不会继续更新(除非您提交 Pull Request)。

formatted-encrypted-file.png
encrypted-file.png
partial-renamed-encrypted-file.png
decoded-bytecode.png
decoded-bytecode-2.png
translated-code-1.png
translated-code-2.png
disassembled-code-1.png
disassembled-code-2.png

免费评分

参与人数 32吾爱币 +31 热心值 +29 收起 理由
nyf + 1 + 1 谢谢@Thanks!
幼稚鬼 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
bj2018 + 1 + 1 好(不提供破解脚本,不为破解而破解!)
清炒藕片丶 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
RoB1n_Ho0d + 1 + 1 我很赞同!
mm498110811 + 1 + 1 用心讨论,共获提升!
test0001 + 1 + 1 用心讨论,共获提升!
meetzhang + 1 + 1 我很赞同!
模具人 + 1 + 1 谢谢@Thanks!
laughingsir38 + 1 + 1 我很赞同!
cibehzf + 1 我很赞同!
留青梳 + 1 用心讨论,共获提升!
Vcat + 1 + 1 我很赞同!
tongdaren + 1 + 1 我很赞同!
雷爵大大 + 1 + 1 用心讨论,共获提升!
tchivs + 3 + 1 用心讨论,共获提升!
sinoers + 1 + 1 谢谢@Thanks!
LOLQAQ + 1 + 1 我很赞同!
laohucai + 1 + 1 谢谢@Thanks!
秋田先生 + 1 + 1 用心讨论,共获提升!
塞北的雪 + 1 + 1 感谢大神的分析
抄经大弟子 + 1 + 1 热心回复!
大师兄啊 + 1 + 1 我很赞同!
easy123456 + 1 + 1 又见到大神你了。。。
XhyEax + 2 + 1 我很赞同!
5omggx + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
majia4075669072 + 1 我很赞同!
lm180180 + 2 + 1 用心讨论,共获提升!
GCM + 1 帖子被盗https://www.y4f.net/45117.html
阿狸主题曲 + 1 我很赞同!
zx575645128 + 1 + 1 我很赞同!
zsx + 1 + 1 用心讨论,共获提升!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

bj2018 发表于 2019-9-19 19:18
@Ganlv ,给楼主二个大大的赞!!
第一个赞献是给楼主的技术,真的让人佩服,台上一分钟,台下十年功!
另一个赞献给楼主发帖讲解形式,内容既进行了技术交流(让我们多学习了一种代码保护的思路),又保护了该加密产品的商业版权和无数加密使用者的权益(不再提供解密脚本,防止了伸手党对该商业产品的二次侵权,而这种伸手党再次提供在线破解的伤害是最致命的,也是最可耻可恨的。另外内容中还细心地消去了相关商业名称,保护了商业产品避免被搜索引擎直接搜索到)。
这才是真的学习贴!
最后送上美好的祝福:楼主一定会:生活如意,事业高升,健康长寿,合家幸福!
Hmily 发表于 2019-7-24 17:03
@Ganlv MD代码和论坛的代码插件貌似不能混用,另外好像升级到php7.3,md插件有兼容问题,我还在查。

免费评分

参与人数 1热心值 +1 收起 理由
Ganlv + 1 OK

查看全部评分

chenrubai 发表于 2019-7-24 18:42
sakura521 发表于 2019-7-24 19:55
前排膜拜大佬。。。。
Hmily 发表于 2019-7-25 09:45
@Ganlv MD的代码兼容问题修改好了,可以用md的代码格式了。
zsx 发表于 2019-7-27 23:18
楼主厉害,点个赞,挺想和楼主继续探讨一下。
看了一下楼主提出的样本,免费加密的强度不算高,而且应该是比较早期的版本。单个样本其实不难破解,这个加密的麻烦之处在更为神奇的地方。

解密方式倒是挺有趣的,学习了。
 楼主| Ganlv 发表于 2019-7-28 20:15
zsx 发表于 2019-7-27 23:18
楼主厉害,点个赞,挺想和楼主继续探讨一下。
看了一下楼主提出的样本,免费加密的强度不算高,而且应该是 ...

其实就是互相比耐心呗,你花大量时间设计虚拟机指令,破解者就花一些时间去分析虚拟机指令,如果单纯想破解的话也没有必要反编译,反汇编之后改几个跳转,或者改几个常量就行了。反汇编之后就和 We7 加密差不多,正常的语句用 goto 乱跳。
zx575645128 发表于 2019-7-29 12:02
学习了,感谢分享
icy8 发表于 2019-7-29 13:52
前排支持大佬
GCM 发表于 2019-7-29 14:31
帖子被盗啦:https://www.y4f.net/45117.html
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-15 13:23

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表