声明
关于版权
请注意,文中相关商业名称均已消去,本文目的是学习分析反编译原理,目的不是为了破解商业产品。
替换并不是为了侵权,是为了保护商业产品,避免本文被搜索引擎直接搜索到。
如果您的确想知道被替换的名称,那么请在命令行中执行语句 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。
'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控制循环的变量$v10eval 前时间$v16eval 后时间$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 得到 $v28return $v24[0] === "\6" ? $v28 : eval("return {$v28};"); // 如果 24 字节中的第 1 个字节是 0x06 那就返回 $v28,否则返回 $v28 的执行结果返回值赋给 $v5
本段代码也没有风险,直接执行,得到执行结果 $v5 = array();。
比较奇怪,解码之后是个空数组。
第 4 段小结
变量说明$v22解码代码$v36指令总数$v12临时变量:分隔符位置$v2424 字节特殊数据$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单条指令$v38s 属性:指令的一些关键参数(字符串数组)$v39z 属性:指令类型(32 位整数)$v14a 属性:下一条指令的序号(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参数410x1a67__SHAREVM_FUNCTION__1设置寄存器的值为 __SHAREVM_FUNCTION__目标寄存器20x1d89ret1跳出循环,返回 get_value返回值 get_value30x930push1将 get_value 压入函数栈中最后一个函数的参数栈函数参数 get_value40x1384__CLASS__1设置寄存器的值为 __CLASS__目标寄存器50xb2amov2 或 4读取 get_value 到 1 或 2 个寄存器目标寄存器目标寄存器 2 或 get_valueget_value是否双重赋值60xfe6push1(指令重复)压入函数参数70xac3jnz2如果 get_value 为真则跳转到 get_value判断条件 get_value跳转目标指令序号 get_value80x189ecount2设置寄存器 get_value 为寄存器 get_value 的数组长度结果寄存器测试数组长度的寄存器90x14b9is_x4对寄存器 get_value 执行 is_ $v9[substr($v38[4], 2)] 结果储存在寄存器 get_value结果寄存器测试 is_x 的 get_value100x1783add3加法结果寄存器加数 1 get_value加数 2 get_value110x1b2eeval_or_include2 或 3eval include_once include结果寄存器或 eval 代码或 include 路径 get_valueeval 代码或 include 路径 get_value或类型类型120x602build_new2构造类构造语句TODO130x602lt3小于结果寄存器小于号左边 get_value小于号右边 get_value140x11afbuild_call1构造函数调用语句函数名 get_value150x1384__FUNCTION__1设置寄存器的值为 __FUNCTION__目标寄存器160x1783dec2减法被减数 get_value 和结果寄存器减数 get_value170x72csuppress_error_off0关闭抑制函数调用错误显示180x561nop0空指令190x16a8echo1输出输出内容 get_value200x614nop0(指令重复)空指令210x1a27build_member_call2对象,空 = $this,非空为 get_value方法名 get_value220x790exit1结果 get_value230xedcnop0(指令重复)空指令240x1ca8push1(指令重复)压入函数参数250x784build_call2构造函数调用语句函数名 get_value260x16fdunlink1解除连接目标寄存器270xb6fmul3280xce1call290x12baif3如果寄存器为真,则跳转到寄存器,否则跳转到寄存器条件寄存器条件为真指令位置条件为假指令位置300xa33equal3310x1250minus3320x1c37jz330x13c5identical340x12c2not_identical350x1110link连接360xfeacast_bool370x936suppress_error0开启抑制函数调用错误显示380x157dreflect_or_call390x1207not2取非结果寄存器被取非的寄存器 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)。
来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |