声明
关于版权
请注意,文中相关商业名称均已消去,本文目的是学习分析反编译原理,目的不是为了破解商业产品。
替换并不是为了侵权,是为了保护商业产品,避免本文被搜索引擎直接搜索到。
如果您的确想知道被替换的名称,那么请在命令行中执行语句 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("bVd1UNRP3Abu+AIijQKK9IGS1xxIKiAIiJQginTHSSmCdB4gXRIS0ikhKXlIKQ3SHRLS3e/95p15/3p3dmc+O7ufzz77PM/szAKIoLYN10WZ4SmBJMVp6xAm5jSaCQCxml4db/DTO1Lpa7YGPl3a89EyBECVdl6FnfGeuc70Vt+9ktX1Loig6e4C4OlpAVp5KEH+vBI26aPLHFcXBQBV31t19c/No1QnPZBiWATyyczRENgHI6AYMjAUDEdCyQkxCkBGnAxdMl5f0XLyf0c1aoCxn81buAGojpmtCdbOTMAZK6CHkrc3cfzw1hmCkBWwdHZ+60QIIHAFQndDmf3vkjBhM2EOQP9vs5kph5EzBwyFEkNikDAxlChGFEH0/zUAFhf3Ax992WPZxhNVRNmtI4QHkDuLfXibt392z++Pq8Zb+jTiSbKtwAgYGoxAwwH42Yna8sL4V84ftwVvpBkWV96+AYZB0WAYXBQMxyDAMLQYYRDmoggAViDH7zMWzNcxeGXnF4ysekoMILNqTtsGxI+owrsTJTN+cwQUbzBIAKipkcrzKmtLcknBQPWdA5wJk+uG5BGYUA2FBqNQYJgYGkBMti2lI2Urp9l6JATFU1hdjp4fArDkLhn5OBwXU95C5QAv4ylknwAGCgXgpXHsOVpp6o85LXgFrkdyKAfswQgUHIxEAfCMOX7qPY2jBwr+Q/qmnHzeR7QRuYM5S1lCpTY0sz8qKQ48wHAEAoxEAsi6kbm8HcZ7CaJso1FaqQZoPYtw/TdkPrlqWlNbFM/Eex88EF4jsAIlsIMkgCTgRcIA1Je6/PYZ9Cd3lzlazw/+mxPBUgXntqz8LVyugaAwVqy2jNdaSQUAy14T8lMoS1fl9nIhtr+W0NwDY9BgNApANGUsBC7gXqlJGrIaZy/wmobCx7yrJSPbyTuL8EzkkU3x3KFgwnEYKIFkDIBqX1jOKjT0UEsiYhCOMYJGvc6tzaJLvva51kvgF1DzHyvPBK+yAMjA0spxfakedp8qO79+bhAYGI4UBsMIuBFQMcKFRQF0zXLJUFnuxaxlQ7weuywtU23T3RV5kbPqyef7jbJe3RY0FAtGvu4E98IBxOzgX3lmT2MzwYP1m2WF8bdIKYgA+NRlJ99xd9g6PZNkH4hT5E04QaDhi59dRyAKX60KTT6mk+dS0wC6NuRfwbfzrww7x633dt7mlslD6rfDc10tGhZac8a0IInNgqKNGwVgNByM+M9UUCQYDRYVJTgMARaDEeiGEUKCyaAEX8AIHEAJppzDUVauHW4apWHFjsRJJLo+n4BhCEIuHEywI4IgCiyfU13w03Mz1+a+8a+V6vqaq4RkBBiB+c8vYAyKcAwKLIoBw1FoAP7v7zfTKbIbQYuOd8S47nzfYR0iiEtAQ7A5HEMARSD/v0Q0ARPsP1xwgu5iBN1Pq37j16GxIlSD1OJvZwyaDOyT/T9uaBgTa8vSBI6/fujfq+xAoBoJIM79U3XTKHUiVXhLLTmqrdy67EP8j4qTTDG7vA4VW27vcm51E2pCAdjg0w/CJr76LfI1I+flxaCbulxcCQF4Gh4dtbaz65fH7y8ARGZWqDADMGl0OnntwzvfcfukasBuECmN7k/3XKyt/dcCwRcSiCDgRCIAZMu/3EiN0o3Y40SZ5Mc3fmE1yrLegFFiADxly1syD3IyybjVOGiq1JU79wsEIMrXakcgHZiih5c556c1HX/TGJq1JuZf4Bvrvw9tiqr0fFfBAvDl42Her47Hmn4Qs7C5Ty1pBz4A3D/li+hnM1fi0FasthRmQVzWG0Bdt8/6TljjSm2htcxtZec695NDYzzBBGkCyLhesJSs0jNzuNqySx023wbgde0SH19SKn7Vu64yHCQXsf83AiBxG6PTyaRmZLmvujk6L/belPb4+QGI4fRmUpjlp1ZsguX7Lz1ACBlwAaAzxnCtuNN3zh01/U9aYpBDaw2z9feSAfR8W9h067dq406YjM6kwzr8hZMvMOxHeNg3c0arL3EGlS2A2jTpQ/MRqtfaIQCsY8IOgf6DvL5xPw4RTv5wlAhA1p8OJGRya7pmZ+esiXUtDnCFMJ8ByNjRk2pf893+OD3WQ/Nizv6rSSS7jfk5pKT82rgDQ/ZsSyWkxmoRB61mMZBnCEyyFrIPJNRqjClekds3tVTZ4cttMvvBtjtSwAmg26OCDtqL7vTv16DPX4V5Eafmlpkr9QGIEZ+u/lsfNRBM8YkMDw5pOZTP8jVVaB8U4p3ERBzW6sI6vXilnvvB0l3/IA2vYSaLdHnubTE5UzqrEuFCqN4UVpp4iEhJEc99x+/l8zFhYakHoU2pPjsbwrkfC8141IHrf8u4WfahnRHOib7v5fEtjpjMHTaHcApHGyq/1ZeITWbczi0SXPby1jAdo1umKF+5DTvdHVMOvzDPPE7HI8UzvdmE+Oz1og2FYDqHxBBIwckyK0gonjvo4whqfsY2y+02PfmbmRyhBUxEShq8v6vmyxkVsRrXi9Px3/ybdWaztcs37poU9nDZVQXRIVPJGecL3n/Ya4Hq7Gn1ZBhy5KGmQ529fqgyusdO0bZX0db1iCgWfMQ+tqTnvmsIJsdF3BdFZD2yNeB0ZBu+SVz0QwprrByvn7SLJBPP3PVmAAkN7s4rovM9Oreexxzz0LHq4RMqIX+WKf3uvx78LmSxt2zQBOfPqVke6995sLB/adE6o0Ae4pXohzj3kYrKWM2d9b+s4hEF1WmOwHJHcySDYJh80w8rw5Z6fJHgLa+51T26oAXdKM7Sl3Tjj/f1HBMX0p7a1vQadnGTbfF5Kgm1yaLxECMSTlsGh31Rl0/cGw4+DrwZnuVz+9a1jPahj2/LZORzOXdXKv+ccOV9P/Lm4mJPKCBv6HjBlh3ByWyPtr7Zt8S/0cQqHMnsGWHEg23Fm+nlozNAn81B8mt98uMFX+ImT/ELi+2WTi8XNqusvT55VpkIBqc/jrY0Tc050Tv5/Ljd8g6Pavr+K0DDswI976aeFBdyzy3Vxy1jebzmuH7FwIbLzUtR0dNmZiX1PVP4sUewKPZkI/Q4L9VejTMqwmqoI755o3myM18j9IuvXnI0tjkEFdVuEUAvNyTxdxOfEU1lwflnvMCT7JtZtN67Cs+G7Ly7kuxF2OV8x3qpR/0Pj0QFxGHdfaRRyfts5Tbvw2hYQ3REVH67U1JhF/G2Pny7Vs8n8x6EqH/WI53f7LluNJU9mNZE5+Vlsg7zV5hF9VEzS4mGSKykbdpYSyf/rFBpIdrcmxeXTVKgSu9IGf/uZ8VGb0T5KzHmqUwvCVF9bGIkDuqFFnAkudSxWHUo57sdeBFTaqWLnEms9pf64y5Nd59ip1TuCvd6QkXCIzlUX5voGurl+qqLbbljNGmBBhkL/ReRkqG+cEiDMdywDaIBBbWZZu5sl3KZcVEHaHLeB4ceWWy7KCU3PQsstqUfth1P5sW2wDp5EJ0Ry7Rvv3kp1Udorz+rAQvwL92uG5DumP89Vu3WySoGwsyuf6DDpMrkm8wee0ocZxJehFXrhlubLqAZczHa6USD+2FUxC4DGUyHMxWoiH9rVNvULhtLzKtT98UnUqmxX/+tNffPt7H3efxCqHPBTk+9J74rydEHd/qY/1kKH8C/9BllOCTqyDLEErG4J3fPBVymDZtZrHS1I03u/i6Q69VZh0MEtSbQDJxbOQNtky/kErpG5Po3aaIifwyVq8ZyF1LpG/h4FVw+qSPGHxTbPh1CPEfzuTdkfExVYVWzo1UpgXnSCozMXU5OSQRQ/Z0StnFqImIiB4lGBibfc7BxLVJVuaAUX5k0pD/6ulopJ+vyT0DjPFm5h1TZ4W0RW7uaixZFyAq7/uOlT46fdzxt4uCrpvId20Q3rT4eF8aKDfLv7skYpxzeWj8joULFL/1kHGLhcWkWfELKeDUwTVJiG0iCLiY2+QtkqO10bz0mp5iluJdyw9TgPY0TX8kY2aXJSzmHORxts0fr3fDCJVW19ZwPBiDZg6nwQN3oRlk+g4/vdg20ZokXaMw27oxHMeJrz8WBhZvK+/5+cMWQIsr4eQN3pfIu9aYFVcQseArzxo7FxzmG+jS3TK3PrVo3sETBpVDLQ2X7lPcFx/Jt9rqQ8u9pKS37z3wOU/PtOjTCnc0fHE4dvOPzZ169QGYKdIt9keUdYNF+WPjN5CZH1+w8ebsAzQum3+m2EvqIsDha2GyGiA7oNwRz9OhiVgnik2T/2ePlO6t5vpnbmHtfDQzJk59Ix1tHSLHY2dK/28BFmFRtW7HEazScWu5AGGRHM2/0ivhWWjw+2bc2CxwuNozwOB8/Ni9pq4ROsZPSh7tHk0WI7Tjgf8Y0/5gqxFakz06caRanVTn+tK/jZTKYv5dZkWZqx8Q0KkT5CTLi92gGTM6R+1caamTjkDfWm10nkMa0d0Elqd/EM5Ne1txwcZbHapn/DQRbeJ0oZkUlcto9tM9gqZsfud8gm637FAZu97B9IwnTsor4Tl9LpXs1EfRiaUiZHCvja2XNfd2nZ42//UgXdwvSMRWGvsM2O2K7+MnLqfU0KnnQglamAU8xseO+3MeODlkxs41Q97oopMgYzEeaNAzjS5STtluvqMaSvlh5I7spn9w92BY8xCMH/IPiVX98ng4oHFxEUkOdYD2F14liZ8xPgu3s73nXZAEXAysKsAlnI/MZn9Qed7VDO5rSV+qPbagpzStFfMeuXZRNkEM8K7679EtBngf8Y04GvM8nLWjn6uzfq6npFGkeMu/pqN412HSrMbQmVu5ciq6XsEbBsHfUN2burLkJWZHpnI+UC732+aalFGIQJ2TFU3bDM1LdzWDUYqY3dMjWn8ynct5xiOhDo6C1sCqp9Jer11yb4eehyQ9dDU0YjmeUqaUEcMEtlYrS2n4J97TDkSDU4qIwa+6aQsI3I6vdWZNlv2KqlhYJ/kQFxQ2nEywqIK5hcB2S7+tpF14z0uMo8bGlIq6rdyIgeEzRHsu5ZgV3+Wiv3fUhlOERV9/1IwWJlMw2nub87Kbp0DleM63pMVn7XxhqdFnR5MtfccRz28TP2DDv5e7qX6n8PjAlT5rz+EHqTUKVctrZoO3z4E1L7oh8hp162lx1wY5c70UCcsXYlWny1wW9PLH8Oz26qvnYZ8SwyPyn+46HliXMBwcXyxMX77VfqKaGx5BaMey6Y5VEBLi5rrzancbXmZX+bF0ggpKZNN8hLg0dZA8/SI5u6D645Mruj+078Y6fVro5HJAr8+TP9fTTctw1YwW5UXpntKFg6nDe1PsRqljuZyUaX5w0lYnlyxmSKevIOFcVm3ypWTL1/xDLJIMaaTjFF6OzMrTjY9SZynoTmZG4qz3V8DlhNV0733nzpRfdng6S+bGBOKwxLFafAy9u8pY/a/1OEKzG1/5ebFWrb93E21+sf1eqqVnFJpuotdybZr8fhVcVQxdfPcgYKuutjhXxNCZ5rt/WRUWzp7jVeFLesfGTH1q4YHLPMV53IxbUVa9fOzu3PYV4trjNnZ6xuXJuOV1AvqFYspKfZTXWIO/RpVi/Q8sEoiBN8A+/+yhYyUnI5KFbsFBJ41dfkvC/KpS6pyl4aJfdjVVlqUfftkCx27kj9ArRngLCbu0nsVYiHf1ZQdI1FJmYpdwPmiYilXd72pbXbmqOPXuaNJmlFk05+9ZRyniYIiu2DhzxMxx6Tes6UlyeClY19kuLEu9pX5J7aN5sCCumt9F8mpNsxqYwSMwYK+Z8gTYg6vHe9z4WYMpslMOIZzmrSB8wq4ckvux/BOvTPkgo36Mcb+2Sj6w6RlrDq9t6+6owQy80HRoSUm5OQhxORYagDpRsZrz4MTSJ9A6op9GyTff1F84oEuM2f/yK8+ncRkcg85RNWeMJuFHH7cGrpSIQ+CyEWFxnhjKHrZ2D2apLDEr75dubiZe3lAuo201cdmq5228qiexMoioyTCdKSKJao9r5HYtN1OU7PqpU60+mpFfQTqwyf0oil8Out6HXrn8VyO8lnFgeSmjjJotuSaFO41d1IEu6HzRkPzvU3bCVVFtgbXFdnBiej2G6Tg1Yu2dYd86vak5tnFaFe3Yp5crSZshX0qAXAyBnx779fBfJn1pkz39D4zDY5tnbum4AnRf8b73o6qmwyqTUfoHscY7ISzRpBYtfrdsg/54E5E8SQ8EyS/YE4ScKoAfja+ZTz54s//SidaU3klD00Je/Wc7Y5smnJr67dF9wdL1XJDPgB4AcLUueVmCSeRW9gVOvZZfDwK25ggF4VcRlioVwmf97HuO3ss6shSg6AB24EBha1w+qZ33+UEWZrsZkcuCroj74fwA="));
}
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_x 的 get_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 |
|
|
注:
- 指令类型值是随机的,这里只是我这个文件的值。
- 指令名称是我自己按照大部分 ASM 常用名称起的。
get_value
函数就是前面那一坨代码。
翻译指令
因为他是基于寄存器的虚拟机,那么我就把所有的寄存器都写成类似 $reg1
这样的变量名,把所有的指令翻译成正常的 php 代码。
编写反汇编脚本
读懂虚拟机指令之后就编写反汇编脚本了,这个过程很复杂,没有耐心的人就不要尝试了。这个加密的说明文档中也提到了,只能增加破解成本,不能避免破解。前前后后大概需要一周的业余时间才能编写完。
代码中的各种技术
我上面编写的反汇编脚本采用静态反编译的方法直接绕过了所有反调试技术,不过我们也要欣赏一下这份代码。
花指令
代码中会出现乱跳转的现象,理论上可以通过静态分析,消除掉部分条件分支。
- 如果每次跳转的条件如果能直接解出来(恒成立或恒不成立),那这个跳转就可被移除掉。
- 如果两个跳转跳向同一个地点,那么也可以被移除掉。
版权信息
前面添加了 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';
}
评测报告
- 加密后的函数名、变量名均混淆,使用
0x80-0xff
的不可读字符干扰破解者。(破解过程:分析原理、编写反混淆代码。破解用时:约 1 小时)
- 数据均经过
gzdeflate
base64
方式编码。(破解过程:编写反混淆代码。破解用时:约 10 分钟)
- 代码中的函数以及字符串常量使用常量池方式干扰破解过程。(破解过程:编写反混淆代码。破解用时:约 1 小时)
- 简单地防 eval hook。(破解过程:在这些代码出不下断点。破解用时:约 1 分钟)
- 指令使用
pack
方式存储成字节码。(破解过程:分析原理,单步调试。破解用时:约 1 小时)
- 每条指令的参数都使用
pack
方式存储在额外的另外一组字节码中。(破解过程:分析原理,单步调试。破解用时:约 1 小时)
- 每条指令存储前,会按不同类型编码,如果是特殊值则使用 AES 编码。(破解过程:分析原理,单步调试。破解用时:约 1 小时)
- 分析每条指令的用途(破解过程:分析原理,单步调试。破解用时:每条指令约 10 分钟)
- 完全相同的指令有不同的指令编码值(破解过程:观察,找规律。破解用时:每条指令约 10 分钟)
- 基于寄存器的虚拟机(破解过程:分析原理,单步调试。破解时间:约 20 分钟)
- 校验虚拟机代码(破解过程:分析原理,单步调试,通过 XDebug 修改值。破解用时:约 10 分钟)
- 编写反编译脚本(破解用时:大于 1 周)
- 花指令(破解过程:分析原理,单步调试。破解用时:约 20 分钟)
phpdbg
xdebug
eval hook
反调试技术(破解用时:约 1 小时)
免费版每执行一条指令会暂停 2 毫秒,这个有些让人想不明白。同时他还设置了 @set_time_limit(ini_get('max_execution_time'));
避免运行超时。
虚拟机指令中没有添加无关代码,但不排除以后添加的可能性。
总结
这款加密的免费版本的加密强度很高,很难轻易破解,破解者可能需要花费一周以上的时间才能完整破解。
其实破解这个东西的确很有趣,加密制造了什么麻烦,我就一层一层将麻烦剥开,最后就会露出破解结果。
PHP 这些加密都还算比较简单的,因为代码只有这么少,没有大量的花指令,虚拟机也是用 PHP 写的,我可以毫不费力地改虚拟机代码,让其便于我注入代码或者导出数据。
附件
仅包含文中代码,没有成品。代码内容可以在 GitHub 上查看,代码不会继续更新(除非您提交 Pull Request)。