基于vm加密的php逆向分析
前言
对于 php 主流的加密方式有两种:
1、基于扩展的
2、本地加密,不涉及扩展
一些在 php 端通过 zend_compile_file 等函数就可以直接 dump 出原代码的,称之为加密实在是抬举了
之前有写过一篇直接 dump 出源码的分析 https://blog.qc7.org/archives/php-decode
基于本人有限的了解,私以为 z5 和 swoole 等类似的才能称得上加密,这里的分析仅限于技术交流
代码可读
之前已经有大佬写过 z5 的逆向分析 https://www.52pojie.cn/thread-995682-1-1.html
最近拿到一份样本,从技术的角度简单了解基于 vm 加密的实现思路,由于逆向难度太大,这里的只是半成品
代码分为两大部分,前面两个函数为 vm 相关的逻辑,函数名是不可读的,后面的为业务函数实现,两个 vm 函数姑且命名为 func_a、 func_b
业务函数部分,函数签名以及最后面的部分代码都是可读的
两个 vm 相关的函数都是定义了一个 $a 变量,经过 gzinflate 解压然后 return $a,解压出来的 $a 也是加密的
业务函数的最后 eval 调用了 func_b,将其函数执行一遍,func_b 函数 gzinflate 出来的密文如下图
代码显示前面有一个 eval(gzinflate(…)),后面还有大量的 eval 调用(截图没截出来)
在 Ganlv 大佬的基础上,实现代码可读显示这个问题不大
调用 func_b 的时候,将第一个 eval(gzinflate(…)) 改为 print(gzinflate(…)),得到的内容为一个常量数组
return array (0 => '1;',1 => '屡爷伅谍夻',2 => 'fgetc',3 => 'PHP_VERSION_ID',4 => 'is_array',5 => 'function_exists',6 => 'current',7 => 'valid',8 => 'r+',9 => 'count',10 => 'fopen',11 => 'Z5Encrypt VM Error: Unhandled',12 => '$藰傰绢撥垇=array_search($幍娷柶牍聊,$瑡吐€);if($藰傰绢撥垇===false){$拐陡湌灭莫++;$瑡吐€[$拐陡湌灭莫]=$幍娷柶牍聊;$溤鋄$拐陡湌灭莫]=NULL;$藰傰绢撥垇=$拐陡湌灭莫;}return $藰傰绢撥垇;',13 => 'get_object_vars',14 => 'newInstance',15 => 'parent::',16 => 'is_string',17 => 'array_keys',18 => 'n*',19 => 'php://filter/read=zlib.inflate/resource=php://memory',20 => ' if($柪0]==0)return substr($柪?1);$膊=intval($柪0]);$宕∩哼膨?substr($柪?0,$膊+1);$胤热劓煢麠=substr($柪?$膊+1);$溆侚娆儫潄气=openssl_decrypt($胤热劓煢麠,"AES-128-ECB",$宕∩哼膨?1);return $溆侚娆儫潄气;',21 => 'call_user_func_array',22 => 'fread',23 => 'ob_start',24 => 'substr',25 => 'constant',26 => 'fclose',27 => 'c/N*',28 => 'microtime',29 => 'fputs',30 => 'strlen',31 => 'is_numeric',32 => 'ord',33 => '#opcodeString',34 => 'getIterator',35 => 'next',36 => 'hasMethod',37 => 'is_object',38 => 'defined',39 => 'newInstanceArgs',40 => '__construct',41 => 'key',42 => 'hi debugger~',43 => 'unpack',44 => 'rewind',45 => 'Nk/na/Nz',46 => 'str_replace',
);
将展开后的常量数组以及后半部分代码,更新回业务函数中,使用 php-parser 格式化后显示如下
之后需要解决代码可读的问题,在 php-parser 基础上进行变量重命名,替换为 $var__ 开始的变量名
变量 $var__7 中就是前面的一个常量数组,基于 $var__7 替换掉函数中部分其他的 $var__ 变量后显示如下
到这里大部分代码已经可读,$var__3 猜测应该是业务函数主体,另外 $var__7 中还有部分代码不可读,不过影响不大
代码展开后,虚拟机的部分代码显示如下
$var__31 = ('count')($var__11);$var__27 = $var__16;$var__32 = eval($var__25);$var__32 = eval("return {$var__32};");unset($var__13);$var__33 = $var__31 * 3;while ($var__15 < $var__31) {$var__34 = $var__11[$var__15];if (!$var__34) {throw new Exception('Z5Encrypt VM Error: Unhandled');}$var__35 = $var__34['s'];$var__36 = $var__34['z'];$var__15 = $var__34['a'];switch ($var__36) {case 0x1f13:$var__26[$var__35[1][1]] = null;break;case 0xdb7:$var__15 = (bool) ($var__35[1] === "" ? "" : ($var__35[1][0] === "\x00" ? isset($var__26[$var__35[1][1]]) ? $var__26[$var__35[1][1]] : null : (($var__19 = $var__35[1][0]) && $var__19 === "\x04" || $var__19 === "\x05" ? ($var__27 = $var__35[1][1]) && ($var__27 = eval($var__25)) || 1 ? $var__19 === "\x04" ? $var__27 : eval("return {$var__27};") : "" : ($var__19 === "\x01" ? $var__35[1][1] : NULL)))) ? ($var__35[2] === "" ? "" : ($var__35[2][0] === "\x00" ? isset($var__26[$var__35[2][1]]) ? $var__26[$var__35[2][1]] : null : (($var__19 = $var__35[2][0]) && $var__19 === "\x04" || $var__19 === "\x05" ? ($var__27 = $var__35[2][1]) && ($var__27 = eval($var__25)) || 1 ? $var__19 === "\x04" ? $var__27 : eval("return {$var__27};") : "" : ($var__19 === "\x01" ? $var__35[2][1] : NULL)))) - 1 : $var__15;break;case 0x19d4:$var__15 = (bool) ($var__35[1] === "" ? "" : ($var__35[1][0] === "\x00" ? isset($var__26[$var__35[1][1]]) ? $var__26[$var__35[1][1]] : null : (($var__19 = $var__35[1][0]) && $var__19 === "\x04" || $var__19 === "\x05" ? ($var__27 = $var__35[1][1]) && ($var__27 = eval($var__25)) || 1 ? $var__19 === "\x04" ? $var__27 : eval("return {$var__27};") : "" : ($var__19 === "\x01" ? $var__35[1][1] : NULL)))) ? $var__15 : ($var__35[2] === "" ? "" : ($var__35[2][0] === "\x00" ? isset($var__26[$var__35[2][1]]) ? $var__26[$var__35[2][1]] : null : (($var__19 = $var__35[2][0]) && $var__19 === "\x04" || $var__19 === "\x05" ? ($var__27 = $var__35[2][1]) && ($var__27 = eval($var__25)) || 1 ? $var__19 === "\x04" ? $var__27 : eval("return {$var__27};") : "" : ($var__19 === "\x01" ? $var__35[2][1] : NULL)))) - 1;break;case 0x1461:eval($var__35[1] === "" ? "" : ($var__35[1][0] === "\x00" ? isset($var__26[$var__35[1][1]]) ? $var__26[$var__35[1][1]] : null : (($var__19 = $var__35[1][0]) && $var__19 === "\x04" || $var__19 === "\x05" ? ($var__27 = $var__35[1][1]) && ($var__27 = eval($var__25)) || 1 ? $var__19 === "\x04" ? $var__27 : eval("return {$var__27};") : "" : ($var__19 === "\x01" ? $var__35[1][1] : NULL))));break;case 0x411:$var__26[$var__35[2][1]] = $var__35[1] === "" ? "" : ($var__35[1][0] === "\x00" ? isset($var__26[$var__35[1][1]]) ? $var__26[$var__35[1][1]] : null : (($var__19 = $var__35[1][0]) && $var__19 === "\x04" || $var__19 === "\x05" ? ($var__27 = $var__35[1][1]) && ($var__27 = eval($var__25)) || 1 ? $var__19 === "\x04" ? $var__27 : eval("return {$var__27};") : "" : ($var__19 === "\x01" ? $var__35[1][1] : NULL)));break;case 0xbd0:$var__37 = $var__26[$var__35[1][1]];$var__15 = ($var__37 ? $var__26[$var__35[2][1]] : $var__26[$var__35[3][1]]) - 1;break;case 0x18b2:$var__26[$var__35[1][1]] = $var__5[1];break;
这里大量的分支都是相同代码,基于 Ganlv 的基础上进行 get_value 收缩掉这部分代码
$var__35[3] === "" ? "" : ($var__35[3][0] === "\x00" ? isset($var__26[$var__35[3][1]]) ? $var__26[$var__35[3][1]] : null : (($var__19 = $var__35[3][0]) && $var__19 === "\x04" || $var__19 === "\x05" ? ($var__27 = $var__35[3][1]) && ($var__27 = eval($var__25)) || 1 ? $var__19 === "\x04" ? $var__27 : eval("return {$var__27};") : "" : ($var__19 === "\x01" ? $var__35[3][1] : NULL)))
收缩代码 get_value 函数如下,这里的 varMap.php 文件是在使用 php-parser 进行重命名时 dump 出来的,将原变量名映射到 $var__ 变量
function get_value($key)
{static $varMap = [];if (empty($varMap)) {$varMap = include 'varMap.php';}$value = _get_value($key);if (is_string($value)) {if (array_key_exists($value, $varMap)) {$value = $varMap[$value];}}// echo $value;return $value;
}function _get_value($key) {global $var__19, $var__25, $var__26, $var__27, $var__35;if ($key === "") {return "";} else {if ($key[0] === "\x00") {if (isset($var__26[$key[1]])) {return $var__26[$key[1]];} else {return null;}} else {$var__19 = $key[0];if (($var__19 && $var__19 === "\x04") || $var__19 === "\x05") {$var__27 = $key[1];if (($var__27 && ($var__27 = eval($var__25))) || 1) {if ($var__19 === "\x04") {return $var__27;} else {return eval("return {$var__27};");}} else {return "";}} else {if ($var__19 === "\x01") {return $key[1];} else {return null;}}}}
}
收缩后的部分代码显示如下
$var__7 中有一个 openssl_decrypt 的代码段,使用时提取到 $var__25 变量
该代码段有几个临时变量名为乱码,手工修改可读显示如下
至此 vm 代码已经全部可读,代码展开后的完整显示如下,其中 get_value 就是前面的定义,$var__3 为密文由于太长这里删掉了
下来分析 vm 的整体逻辑
function user_function($var__1 = false, $var__2 = 'danger')
{global $var__19, $var__25, $var__26, $var__27, $var__35, $callfunc;$var__3 = '......';true;$var__4 = func_get_args();$var__5 = array(&$var__3, __FILE__, __FUNCTION__, __CLASS__, version_compare(PHP_VERSION, '5.3') === -1 ? '' : __NAMESPACE__);$var__6 = $var__5[0];true;$var__7 = array(0 => '1;', 1 => 'ÂĹŇŻŻľý‰ţ', 2 => 'fgetc', 3 => 'PHP_VERSION_ID', 4 => 'is_array', 5 => 'function_exists', 6 => 'current', 7 => 'valid', 8 => 'r+', 9 => 'count', 10 => 'fopen', 11 => 'Z5Encrypt VM Error: Unhandled', 12 => '$var__17=array_search($var__8,$var__32);if($var__17===false){$var__33++;$var__32[$var__33]=$var__8;$var__26[$var__33]=NULL;$var__17=$var__33;}return $var__17;', 13 => 'get_object_vars', 14 => 'newInstance', 15 => 'parent::', 16 => 'is_string', 17 => 'array_keys', 18 => 'n*', 19 => 'php://filter/read=zlib.inflate/resource=php://memory', 20 => ' if($var__27[0]==0)return substr($var__27,1);$ŞĄĄ ˛˛=intval($var__27[0]);$ĺ´ĄÉşßĹňŁ=substr($var__27,0,$ŞĄĄ ˛˛+1);$ءČČŘćŸŚű—=substr($var__27,$ŞĄĄ ˛˛+1);$äÓůćŹƒŸĆř=openssl_decrypt($ءČČŘćŸŚű—,"AES-128-ECB",$ĺ´ĄÉşßĹňŁ,1);return $äÓůćŹƒŸĆř;', 21 => 'call_user_func_array', 22 => 'fread', 23 => 'ob_start', 24 => 'substr', 25 => 'constant', 26 => 'fclose', 27 => 'c/N*', 28 => 'microtime', 29 => 'fputs', 30 => 'strlen', 31 => 'is_numeric', 32 => 'ord', 33 => '#opcodeString', 34 => 'getIterator', 35 => 'next', 36 => 'hasMethod', 37 => 'is_object', 38 => 'defined', 39 => 'newInstanceArgs', 40 => '__construct', 41 => 'key', 42 => 'hi debugger~', 43 => 'unpack', 44 => 'rewind', 45 => 'Nk/na/Nz', 46 => 'str_replace');$var__8 = '';$var__9 = '$var__17=array_search($var__8,$var__32);if($var__17===false){$var__33++;$var__32[$var__33]=$var__8;$var__26[$var__33]=NULL;$var__17=$var__33;}return $var__17;';$var__10 = true;while ($var__10) {$var__10 = false;$var__11 = array();$var__12 = ('fopen')('php://filter/read=zlib.inflate/resource=php://memory', 'r+');('fputs')($var__12, $var__6);('rewind')($var__12);$var__13 = ('unpack')('n*', ('fread')($var__12, 4));$var__14 = ('microtime')(true) * 1000;$var__15 = $var__13[2];('fgetc')($var__12);$var__16 = ('fread')($var__12, $var__13[1] - 1);$var__17 = 0;eval('1;');unset($var__13);$var__18 = ('microtime')(true) * 1000;if ($var__18 - $var__14 > 1000) {exit('hi debugger~');}while (true) {$var__19 = ('fread')($var__12, 10);if ($var__19 === '') {break;}$var__20 = ('unpack')('Nk/na/Nz', $var__19);$var__21 = $var__20['k'];unset($var__20['k']);$var__22 = array('');$var__17 = 6;while ($var__17 < $var__21) {$var__23 = ('unpack')("N", ('fread')($var__12, 4));$var__24 = $var__23[1] > 0 ? ('fread')($var__12, $var__23[1]) : '';if ($var__24 !== '') {if ($var__24[0] === "\v") {$var__24 = ('unpack')('c/N*', $var__24);$var__24 = ('substr')(func_a(), $var__24[1], $var__24[2]);}if ($var__24[0] === "\x03") {$var__24 = array("\x01", (int) ('substr')($var__24, 1));} elseif ($var__24[0] === "\x06") {$var__24 = array("\x01", true);} elseif ($var__24[0] === "\x07") {$var__24 = array("\x01", false);} elseif ($var__24[0] === "\x08") {$var__24 = array("\x01", (double) ('substr')($var__24, 1));} elseif (('ord')($var__24) <= 12) {$var__24 = array($var__24[0], ('substr')($var__24, 1));} elseif (('is_numeric')($var__24)) {$var__24 = array("\x00", (int) $var__24);} else {$var__24 = array("\x00", $var__24);}}$var__17 += $var__23[1] + 4;$var__22[] = $var__24;}unset($var__13);$var__20['s'] = $var__22;$var__11[] = $var__20;unset($var__21);}('fclose')($var__12);$var__25 = ' if($var__27[0]==0)return substr($var__27,1);$n1=intval($var__27[0]);$s1=substr($var__27,0,$n1+1);$s2=substr($var__27,$n1+1);$r1=openssl_decrypt($s2,"AES-128-ECB",$s1,1);return $r1;';$var__26 = array();$var__27 = '';$var__28 = null;$var__19 = null;$var__29 = false;$var__30 = null;$var__31 = ('count')($var__11);$var__27 = $var__16;$var__32 = eval($var__25);$var__32 = eval("return {$var__32};");unset($var__13);$var__33 = $var__31 * 3;while ($var__15 < $var__31) {$var__34 = $var__11[$var__15];if (!$var__34) {throw new Exception('Z5Encrypt VM Error: Unhandled');}$var__35 = $var__34['s'];$var__36 = $var__34['z'];$var__15 = $var__34['a'];switch ($var__36) {case 0x1f13:$var__26[$var__35[1][1]] = null;break;case 0xdb7:$var__15 = (bool) get_value($var__35[1]) ? get_value($var__35[2]) - 1 : $var__15;break;case 0x19d4:$var__15 = (bool) get_value($var__35[1]) ? $var__15 : get_value($var__35[2]) - 1;break;case 0x1461:$dynamic = get_value($var__35[1]);eval($dynamic);break;case 0x411:$var__26[$var__35[2][1]] = get_value($var__35[1]);break;case 0xbd0:$var__37 = $var__26[$var__35[1][1]];$var__15 = ($var__37 ? $var__26[$var__35[2][1]] : $var__26[$var__35[3][1]]) - 1;break;case 0x18b2:$var__26[$var__35[1][1]] = $var__5[1];break;case 0x1583:$var__26[$var__35[1][1]] = (bool) get_value($var__35[2]);break;case 0x9ea:$var__26[$var__35[1][1]] = array();break;case 0x107a:$var__26[$var__35[1][1]] = get_value($var__35[2]) !== get_value($var__35[3]);break;case 0x4ba:if ($var__35[2] !== '') {$var__26[$var__35[3][1]][get_value($var__35[2])] = get_value($var__35[1]);} else {$var__26[$var__35[3][1]][] = get_value($var__35[1]);}break;case 0x1977:break;case 0xc9d:$var__26[$var__35[1][1]] = get_value($var__35[1]) - get_value($var__35[2]);break;case 0x176b:$var__26[$var__35[1][1]] = array();if ($var__35[3] !== '') {$var__26[$var__35[1][1]][get_value($var__35[3])] = get_value($var__35[2]);} else {$var__26[$var__35[1][1]][] = get_value($var__35[2]);}break;case 0x7ea:$var__37 = get_value($var__35[2]);if ($var__29) {$var__38 = @('call_user_func_array')($var__37, $var__26[$var__35[1][1]]);} else {$var__38 = ('call_user_func_array')($var__37, $var__26[$var__35[1][1]]);}if ($var__35[3] !== '') {$var__26[$var__35[3][1]] = $var__38;}break;case 0x154e:$var__26[$var__35[1][1]] = $var__5[2];break;case 0x1bf2:$var__26[$var__35[1][1]] = !get_value($var__35[2]);break;case 0x4e5:$var__37 = get_value($var__35[3]);if (('defined')($var__37)) {$var__26[$var__35[1][1]] = ('constant')($var__37);} elseif (('defined')('PHP_VERSION_ID') && PHP_VERSION_ID >= 50300) {$var__13 = ('str_replace')($var__5[4], '', $var__37);$var__26[$var__35[1][1]] = ('defined')($var__13) ? ('constant')($var__13) : $var__37;} else {$var__26[$var__35[1][1]] = $var__37;}break;case 0x9c8:if ($var__35[3] === "") {$var__28 = $var__39;} else {$var__28 = get_value($var__35[3]);}$var__26[$var__35[1][1]] = array();$var__26[$var__35[2][1]] = array($var__28, get_value($var__35[4]));break;case 0x1899:$var__28 =& $var__26[$var__35[1][1]];if ($var__35[2] === '') {$var__37 = ('is_string')($var__28) ? ('strlen')($var__28) : (('is_array')($var__28) ? ('count')($var__28) : ($var__28 instanceof ArrayAccess ? null : 0));} else {$var__37 = get_value($var__35[2]);}$var__28[$var__37] = get_value($var__35[3]);unset($var__28);break;case 0x11c3:$var__37 = get_value($var__35[4]);if (get_value($var__35[3])) {if ($var__37 === '') {$var__37 = $var__5[3];}$var__40 = new ReflectionClass($var__37);if ($var__40->{'hasMethod'}('__construct')) {if ($var__29) {$var__41 = @$var__40->{'newInstanceArgs'}($var__26[$var__35[2][1]]);} else {$var__41 = $var__40->{'newInstanceArgs'}($var__26[$var__35[2][1]]);}} else if ($var__29) {$var__41 = @$var__40->{'newInstance'}();} else {$var__41 = $var__40->{'newInstance'}();}$var__26[$var__35[1][1]] = $var__41;} else {if ($var__29) {$var__38 = @('call_user_func_array')($var__37, $var__26[$var__35[2][1]]);} else {$var__38 = ('call_user_func_array')($var__37, $var__26[$var__35[2][1]]);}if ($var__35[1] !== '') {$var__26[$var__35[1][1]] = $var__38;}}break;case 0x7d7:$var__26[$var__35[1][1]] = $var__5[3];break;case 0x478:$var__37 = $var__26[$var__35[1][1]];switch (('count')($var__37)) {case 0:$var__38 = ('ob_start')();break;case 1:$var__38 = ('ob_start')($var__37[0]);break;case 2:$var__38 = ('ob_start')($var__37[0], $var__37[1]);break;}if ($var__35[2] !== '') {$var__26[$var__35[2][1]] = $var__38;}break;case 0x14da:$var__26[$var__35[1][1]] = get_value($var__35[2]) < get_value($var__35[3]);break;case 0x6b0:$var__28 =& $var__26[$var__35[2][1]];$var__26[$var__35[1][1]] = ('count')($var__28);unset($var__28);break;case 0x1975:break;case 0x11a9:$var__26[$var__35[1][1]][] =& $var__26[$var__35[2][1]];break;case 0xa8e:exit(get_value($var__35[1]));break;case 0x1c92:$var__38 = ('function_exists')($var__26[$var__35[1][1]][0]);if ($var__35[2] !== '') {$var__26[$var__35[2][1]] = $var__38;}break;case 0x1f6f:$var__26[$var__35[1][1]] = array();break;case 0xc4b:$var__26[$var__35[1][1]] = get_value($var__35[2]) === get_value($var__35[3]);break;case 0x1bab:$var__37 = get_value($var__35[2]);$var__26[$var__35[1][1]] =& ${$var__37};break;case 0x1cbb:$var__37 = get_value($var__35[2]);if (${$var__37} === null) {$var__26[$var__35[1][1]] = get_value($var__35[3]);} else {$var__26[$var__35[1][1]] =& ${$var__37};}break;case 0xbb6:$var__37 = get_value($var__35[4]);if ($var__37 === '') {$var__37 = '__construct';}if ($var__35[3] === "") {$var__28 = $var__39;$var__37 = 'parent::' . $var__37;} else {$var__28 = get_value($var__35[3]);}$var__26[$var__35[1][1]] = array();$var__26[$var__35[2][1]] = array($var__28, $var__37);break;case 0x9d2:$var__37 = get_value($var__35[3]);$var__26[$var__35[1][1]] = $var__37(get_value($var__35[2]));break;case 0x10c9:$var__26[$var__35[1][1]] = !(bool) get_value($var__35[2]);break;case 0xc0b:$var__26[$var__35[1][1]] = get_value($var__35[2]) . get_value($var__35[3]);break;case 0x16da:echo get_value($var__35[1]);break;case 0x1cf3:$var__26[$var__35[1][1]] = 'ńüăűÉňኔ';break;case 0x757:$var__37 = get_value($var__35[2]);while ($var__37 instanceof IteratorAggregate) {$var__37 = $var__37->{'getIterator'}();}if ($var__37 instanceof Iterator) {$var__26[$var__35[1][1]] = array($var__37, null, null, 0, $var__35[3], 'i');} elseif (('is_object')($var__37)) {$var__26[$var__35[1][1]] = array($var__37, null, null, 0, $var__35[3], 'o');} else {$var__26[$var__35[1][1]] = array($var__37, ('array_keys')($var__37), null, 0, $var__35[3], 'a');}break;case 0x1e6f:$var__13 = get_value($var__35[1]);$var__28 =& $var__26[$var__35[2][1]];if ($var__28[5] === 'i') {if ($var__28[3] === 0) {$var__28[0]->{'rewind'}();} else {$var__28[0]->{'next'}();}$var__28[3]++;if (!$var__28[0]->{'valid'}()) {$var__15 = get_value($var__28[4]) - 1;} else {$var__26[$var__35[3][1]] = $var__28[0]->{'current'}();if ($var__13) {$var__26[$var__35[4][1]] = $var__28[0]->{'key'}();}}} else {if ($var__28[5] === 'o') {$var__37 = ('get_object_vars')($var__28[0]);$var__42 = ('array_keys')($var__37);} else {$var__37 =& $var__28[0];$var__42 =& $var__28[1];}if ($var__28[3] >= ('count')($var__37)) {$var__15 = get_value($var__28[4]) - 1;} else {if ($var__13) {$var__26[$var__35[4][1]] = $var__42[$var__28[3]];}$var__26[$var__35[3][1]] = $var__37[$var__42[$var__28[3]]];$var__28[3]++;}unset($var__37);unset($var__42);}unset($var__28);break;case 0x56f:$var__26[$var__35[1][1]] = get_value($var__35[2]) - get_value($var__35[3]);break;case 0x1815:break;}}foreach ($var__26 as $var__43 => &$var__44) {unset($var__26[$var__43]);}unset($var__11);unset($var__26);unset($var__44);unset($var__37);unset($var__28);}return $var__30;$var__45;return NULL;$var__45;
}
代码分析
经过前面处理,代码已经全部可读,可以在此基础上进行任意修改代码进行调试,不过需要注意 eval 调用
在没确认安全前,直接进行 eval 调用是非常危险的,一些产品会在反逆向的时插入暗装,严重的可能会直接进行硬盘格式化
单步调试是一个漫长的过程,涉及到异常的情况,需要手工修改代码逻辑再次调试,整体还是比较麻烦的
这里就不展示过程了,整体逻辑和 Ganlv 大佬的分析差别不大,vm 部分暂不涉及,不过就目前的分析来看
1、最前面有一个 microtime 的检查,可以注释忽略掉
2、初始指令流会从 $var__3 中 unpack 出来
3、unpack 指令流后,通过 $var__25 中的 openssl_decrypt 进行解密,密文组成为:key长度 + key + 待解密数据
4、虚拟机的 vm 流分两轮,第一轮校验并解密业务流,第二轮执行业务流
5、待解密的业务流在 func_a 的 $a 数据中,前面这一行已经截取出来 $var__24 = ('substr')(func_a(), $var__24[1], $var__24[2]);
6、这段业务流的解密,在分支中的 call_user_func_array 中进行处理,通过 openssl_decrypt 进行解密
7、这里的 openssl_decrypt 和前面 $var__25 的不同,前面是 ECB 解密,这里是 CBC 解密
8、openssl_decrypt 的 CBC 解密除了需要 key 外,额外还需要一个 IV
9、第一轮 vm 迭代的时候,通过 ReflectFunction 反射,然后 getStartLine、getEndLine 起始行拿到 func_b 函数文本
10、将 func_b 函数文本通过 hash_hmac 计算哈希值,然后进行 str_rot13 处理拿到解密的 key
11、每一个加密文件的 func_b 都是不同的,因此没有通用的 key,但是同一个文件不同函数的 key 是相同的
12、再次通过 ReflectFunction 反射,然后 getStartLine、getEndLine 起始行拿到 user_function 函数文本
13、截取 user_function 的 从 ;true;
到函数结束 ;$_SERVER;}
的代码文本,计算 md5 值,这个就是 IV
14、在 user_function 函数计算 md5 的这部分代码中,使用的一些变量,在不同文件的不同函数内都是不同的,因此没有通用的 IV
15、在得到 key 和 IV 后,通过 call_user_func_array 调用 openssl_decrypt 解密业务流
16、解密业务流后,进入第二轮 vm 迭代,流程转到业务代码执行,当然业务代码依然还是加密的,以及经 vm 处理的
17、这里可以看出,如果代码文件被改动,就会导致 key 和 IV 都计算错误,业务流解密必然失败
18、vm 普遍存在运行速度拖慢的问题,几行的业务函数,膨胀到近 400 条业务指令,这还不涉及前面校验这些,实在有点夸张
19、从安全角度来看,z5还是非常难逆向的,速度慢的问题需要使用人员衡量
相关文章:
基于vm加密的php逆向分析
前言 对于 php 主流的加密方式有两种: 1、基于扩展的 2、本地加密,不涉及扩展 一些在 php 端通过 zend_compile_file 等函数就可以直接 dump 出原代码的,称之为加密实在是抬举了 之前有写过一篇直接 dump 出源码的分析 https://blog.qc7…...
FPGA----基于ALINX提供的debian实现TCF
引言:接上问,我们使用自制的image.ub和boot.bin以及ALINX提供的debian8根文件系统,构建了petalinux,但是经测试,该文件系统无法启用TCF服务,即无法与Xilinx SDK建立连接,那么我们应该如何解决&a…...
基于Transformer的多资产收益预测模型实战(附PyTorch实现与避坑指南)
基于Transformer的多资产收益预测模型实战(附PyTorch模型训练及可视化完整代码) 一、项目背景与目标 在量化投资领域,利用时间序列数据预测资产收益是核心任务之一。传统方法如LSTM难以捕捉资产间的复杂依赖关系,而Transformer架…...
SQL:MySQL函数:字符串函数
目录 为什么需要字符串函数? 1️⃣ LENGTH(str) — 这个字符串有几个“字节”? 2️⃣ CHAR_LENGTH(str) — 这个字符串有几个“字符”? 3️⃣ TRIM(str) — 把两边的空格剪掉 4️⃣ REPLACE(str, a, b) — 把 a 替换成 b 使用这些函数时…...
C++-缺省参数
缺省参数 缺省参数也叫默认参数 指的是在函数参数的位置,提前定义一个缺省值(即提前定义一个值),当函数接收到参数时,如果定义缺省值的位置未接收到参数,那么这个位置会自动使用缺省值 通过定义缺省参数…...
MySQL 数据库
目录 1. 数据库简介 1.1 使用数据库的必要性 1.2 数据库的基本概念 1.3 经典数据模型 2. MySQL 服务基础 2.1 MySQL 的二进制安装 2.1.1 基础环境准备 2.1.2 二进制安装 2.1.3 设定配置文件 1. 数据库简介 1.1 使用数据库的必要性 使用数据库可以高效且条理分明地存…...
探寻养生新路径,守护健康生活
在忙碌的现代生活中,人们对健康养生的需求愈发迫切。养生不一定要遵循复杂的规则,从一些新颖且实用的方面入手,同样能收获健康的馈赠。 关注肠道菌群的平衡是养生的关键。肠道内居住着数以万亿计的微生物,它们与人体健康息息相…...
平板收银系统、国产系统,鸿蒙系统,小键盘的封装与应用—仙盟创梦IDE
数字小键盘封装 数组小键盘封装是指将与数组小键盘相关的功能、操作、数据等进行整合,形成一个独立的、可复用的模块。封装数组小键盘具有以下几方面重要意义: 提高代码可维护性 降低复杂度:数组小键盘在实际应用中,可能涉及到…...
微软推动智能体协同运作:支持 A2A、MCP 协议
今日凌晨,微软宣布 Azure AI Foundry 和 Microsoft Copilot Studio 两大开发平台支持最新 Agent 开发协议 A2A,并与谷歌合作开发扩大该协议,这一举措对智能体赛道意义重大。 现状与变革意义 当前智能体领域类似战国时代,各家技术…...
《企业级前端部署方案:Jenkins+MinIO+SSH+Gitee+Jenkinsfile自动化实践》
文章目录 前言前端项目CICD时序图一、环境准备1、服务器相关2、Jenkins凭据3、注意事项 二、设计思想1. 模块化设计2.多环境支持3. 制品管理4. 安全部署机制5. 回滚机制 三、CI阶段1、构建节点选择2、代码拉取3、代码编译4、打包并上传至minio 四、CD阶段五、回滚阶段六、构建通…...
数据库的进阶操作
目录 1、数据库的约束 2、查询操作的进阶 2.1 查询插入 2.2 聚合查询 2.3 运算查询 2.3 分组查询 2.4 联合查询 2.5 内外连接 2.6 子查询 2.7 合并查询 1、数据库的约束 数据库的约束是指:数据库会自动的对数据的合法性进行校验和检查的一系列操作的机制&a…...
小刚说C语言刷题—1341银行存款问题
1.题目描述 亮亮把 n 元按照 m 年期整存存入银行,按照目前银行的年利率,请问到期后亮亮可以连本带息总共拿到多少钱? 存期(整存整取) 年利率 1年 3.25% 2年 3.75% 3年∼4 年 4.25% 5年及 5年以上 4.75% 输入…...
15 个 Azure DevOps 场景化面试问题及解答
问题 1. 解释 Azure DevOps YAML 管道的典型结构。 您可以从管道的整体结构开始,从触发器开始。您也可以选择解释它可能包含的不同类型的阶段:构建、测试、扫描、部署等。 Azure DevOps YAML 管道结构示例 触发器指示管道运行。它可以是持续集成 (CI) 或…...
spring cloud 跨服务调用
微服务将不同功能模块拆分成多个不同的服务,在业务逻辑集成时候,难免会有一个服务需要依赖调用另一个服务的情况。如订单服务需要通过用户服务查询用户相关信息,这时候微服务之间就需要进行跨服务调用。 要想进行跨服务调用,服务…...
手机隐私数据彻底删除工具:回收或弃用手机前防数据恢复
软件介绍 有这样一款由吾爱网友chenwangjun 原创开发的数据处理软件,名为 AndroidDiskClear。它的核心功能十分强大,能够将你手机里已经删除的各类文件,像图片、普通文件、文字信息等彻底清除干净,有效杜绝数据恢复类软件的二次恢…...
【Electron】electron-vue 借助 element-ui UI 库助力桌面应用开发
前面文章我们讲过 electron 让可以用 HTML、JS、CSS 开发桌面应用程序。而 electron-vue 是一个结合了 electron 与 vue 的套件。这样我们就能方便地使用 vue 快速开发桌面应用。但是,vue 只是在 js 这层面做了大量的便捷的操作。对 UI 并未过多涉及。此时如果您在开…...
《信息论与编码课程笔记》——信源编码(1)
目录 一、信源编码基本概念 1. 定义与目的 2. 编码示例 3. 编码分类 4. 唯一可译码的判断标准 5. 编码评价指标 二、香农第一定理(无失真可变长信源编码定理) 1. 核心内容 2. 关键概念与指标 3. 数据压缩的本质 4. 例子与启示 5. 定理的意义…...
2022年8月,韩先超对中移信息进行微服务架构原理(Docker+k8s+DevOps+Go等)培训
2022年8月,韩先超对中移信息进行微服务架构原理(Dockerk8sDevOpsGo等)培训 2022年8月,在企业数字化转型和云原生架构加速演进的背景下, 中移信息技术有限公司特别邀请云原生与DevOps领域专家 韩先超老师,…...
【PostgreSQL数据分析实战:从数据清洗到可视化全流程】8.4 数据故事化呈现(报告结构设计/业务价值提炼)
👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 8.4 数据故事化呈现:从报告结构到业务价值的深度融合一、数据故事化的核心价值体系(一)报告结构设计的黄金框架1. 业务场景锚定ÿ…...
Docker 核心目录结构
1. Docker 核心目录结构 数据存储目录 默认根目录:/var/lib/docker Docker 所有运行时数据(镜像、容器、卷、网络配置等)的默认存储位置。 bash 复制 下载 # 查看 Docker 数据根目录 docker info | grep "Docker Root Dir" # 输出…...
【工具推荐】Code2Prompt
DeepWiki工具可以帮我们快速理解 GitHub 项目,简直是理解陌生开源项目的利器! 但是,它有个小小的“遗憾”——只能解析在线的 GitHub 项目。 如果是本地项目怎么办,还要特意上传,no,code2prompt 就是一款…...
OpenCV定位地板上的书
任务目标是将下面的图片中的书本找出来: 使用到的技术包括:转灰度图、提取颜色分量、二值化、形态学、轮廓提取等。 我们尝试先把图片转为灰度图,然后二值化,看看效果: 可以看到,二值化后,书的…...
Spring Cloud:概述,服务注册和服务发现,多机部署和负载均衡
什么是微服务 就是将一个大型的应用程序拆分成多而小的独立的服务模块,每个服务模块围绕某个业务功能建立,具有独立的数据库,服务栈,并通过轻量级的通信协议进行交互。 单体架构 就是将所有的业务和功能都打包在一个jar包中&…...
Linux的基础开发工具
目录 前言: 1、包管理器yum 1.1 软件包的依赖 1.2 镜像源 1.3 查找/安装/卸载软件 2、编辑器vim 2.1 命令模式(默认) 2.1.1 撤销与反撤销 2.1.2 光标定位 2.1.3 复制&&剪切(删除)&&粘贴 2.1.4 替换 2.1.5 插入模式 2.1.6 V-Block模式 …...
解锁跨平台开发的新时代——Compose Multiplatform
解锁跨平台开发的新时代——Compose Multiplatform 在当今移动和桌面应用程序开发领域,跨平台解决方案是开发者们梦寐以求的工具。而由JetBrains打造的Compose Multiplatform正是这样一款现代UI框架,它基于Kotlin技术,为开发者构建高性能且美观的用户界面提供了极大的便利和…...
键盘固件刷写详解:Bootloader
键盘固件刷写详解:从入门到精通 引言 作为一名机械键盘爱好者,相信大家都曾经面临过刷写固件的问题。无论是想要自定义按键功能,还是升级键盘的固件,掌握刷写技巧都是非常必要的。本文将全面介绍不同类型的引导加载程序…...
网络原理初识
本来想从网络的发展史开始写,之后再写网络的定义啥的,但快写完了才发现,这不课本教材吗,没劲,遂弃之,重撰,删芜就简.写点我认为,对程序员来说真正有用的东西 目录 IP地址 概念 格式 特殊地址 端口号 概念 格式 协议 概念 知名协议的默认端口 五元组 协议分层 OS…...
PVP鼠标推荐(deepseek)
下面有不懂的自行百度查找👍 ❤️ 以下是几款在 双击性能(DBC) 和 拖拽点击(DC) 方面表现优秀的游戏鼠标推荐,结合了硬件性能、微动寿命以及玩家口碑: 1. 罗技 G102/G203 Lightsync 特点&#…...
Navee滑板车强势登陆中国,以智能科技重塑城市出行新风尚
当科技与潮流邂逅,城市出行迎来了一场前所未有的变革。全球智能出行领域的先锋品牌Navee,携其多款旗舰滑板车产品——ST3、GT3、V系列等,正式进军中国市场。凭借“颜值、性能、安全、智能”四大核心优势,Navee正以破竹之势重新定义…...
【Linux网络】网络命令
Ping Ping命令是一种用于测试网络连接状况的工具,在Windows、Linux、macOS等操作系统中都可以使用。以下是其详细介绍: 它主要通过向目标主机发送Internet控制报文协议(ICMP)的回声请求(Echo Request)数据…...
【JS逆向基础】面向对象
1,OOP编程思想 面向对象编程是在面向过程编程的基础上发展来的,它比面向过程编程具有更强的灵活性和扩展性。面向对象编程是程序员发展的分水岭,很多初学者会因无法理解面向对象而放弃学习编程。 面向对象编程(Obiect:oriented Programming…...
# 如何使用OpenCV进行发票的透视变换和二值化处理
如何使用OpenCV进行发票的透视变换和二值化处理 引言 在自动化处理发票和其他文档时,图像预处理是一个关键步骤,它可以帮助提高OCR(光学字符识别)的准确性。透视变换用于校正图像中的透视失真,而二值化处理则可以简化…...
NetSuite 如何得到所有Item最近一次采购订单的货品单价?
我们知道,如果取Item主数据的数据,得到的是很多相关transaction的Item 货品平均价值;如果只想得到最近一次采购订单上的采购单价,主数据上应该无法直接得到,那该如何处理?我们可以利用Saved Search来拉取相…...
[5-2] 对射式红外传感器计次旋转编码器计次 江协科技学习笔记(38个知识点)
1、 2、 3、这些缩写通常出现在嵌入式系统或微控制器的上下文中,它们各自有不同的功能: • RCC:Reset and Clock Control(复位和时钟控制)。它负责管理微控制器的时钟系统,包括时钟源的选择、时钟频率的设置…...
阿里云 golang 一面
消息队列 分布式文件系统 Linux的虚拟网络设备 TCP SSL/TLS 场景:对于打车场景,如何设计一个系统来处理并发打车请求? 设计一个处理并发打车请求的系统需要考虑多个方面,包括系统架构、数据存储、负载均衡和实时性。以下是一个基…...
【C++ Qt】多元素控件(ListWidget、TableWidget、TreeWidget)
每日激励:“不设限和自我肯定的心态:I can do all things。 — Stephen Curry” 绪论: 本章将通过代码示例详细介绍了Qt中QListWidget、QTableWidget和QTreeWidget三种多元素控件的使用方法与核心功能,涵盖列表的增删操作、表格…...
排序算法-冒泡排序
冒泡排序一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。 这个算法的名字由来是因为元素会经由交换慢…...
C++ 工具链与开发实践:构建安全、高效与创新的开发生态
引言 在 C 的技术演进中,工具链的革新与开发实践的迭代始终是推动语言生命力的核心动力。从内存安全的攻防体系到嵌入式设备的能效优化,从跨平台开发的降本增效到开发者社区的生态构建,C 正通过工具链与方法论的双重升级,应对复杂…...
uni-pages-hot-modules插件:uni-app的pages.json的模块化及模块热重载
uni-pages-hot-modules uni-app的pages.json的模块化及模块热重载 uni-app的pages.json的模块化及模块热重载 解决uni-app的pages.json无法模块化的问题,并且解决模块热重载和缓存的问题 安装 npm i uni-pages-hot-modules -Suni-app vite版本(vue3)示例项目 uni-app webpa…...
【WEB3】区块链、隐私计算、AI和Web3.0——数据民主化(1)
区块链、隐私计算、AI,是未来Web3.0至关重要的三项技术。 1.数据民主化问题 数据在整个生命周期(生产、传输、处理、存储)内的隐私安全,则是Web3.0在初始阶段首要解决的问题。 数据民主化旨在打破数据垄断,让个体能…...
从0到1:用Lask/Django框架搭建个人博客系统(4/10)
摘要:本文深入解析了使用Lask和Django构建个人博客系统的全过程。从技术选型的考量,突出Lask的轻量灵活与Django的强大功能,到开发环境搭建、项目初始化,再到核心功能实现如文章管理、用户认证与权限控制,详细阐述了开…...
探索 C++23 的 views::cartesian_product
文章目录 一、背景与动机二、基本概念与语法三、使用示例四、特点与优势五、性能与优化六、与 P2374R4 的关系七、编译器支持八、总结 C23 为我们带来了一系列令人兴奋的新特性,其中 views::cartesian_product 是一个非常实用且强大的功能,它允许我们轻…...
微软拼音自定义词库方法
1.准备文件input.txt 令狐冲 郭靖2.使用python转成微软拼音需要的格式 from xpinyin import Pinyin # pip install xpinyin -i https://pypi.tuna.tsinghua.edu.cn/simplep Pinyin()with open(input.txt, r, encodingutf-8) as infile:with open(output.txt, w, encodingutf…...
SQLite3常用语句汇总
SQLite 命令行工具(sqlite3 shell) 中的内置命令 命令作用说明.open filename.db打开或创建一个 SQLite 数据库文件.tables列出当前数据库中的所有表.schema [table]查看某个表或所有表的建表语句(DDL).headers ON/OFF开启或关闭…...
数据库设计三范式
第一范式 (1NF) 每个表中的每一列都是原子值 每个表中的每一行都是唯一的 下面这个表格就是不符合第一范式的例子:因为学时数能够拆分为讲课和实验。所以他并不是每一列都是原子值。 不仅要考虑列是否是原子值,还需要考虑每列的值是否可拆分。 第二范…...
GoWeb开发
学习目标: 本篇要达到的目的,能为后续复习提供极大便利。 (当我写下本篇博客时,已复习3遍) 一、网络通信概述 (为本篇基础核心内容) 1、什么是网络通信? 网络通信是指不同设备&…...
(7)Nokov 室内光学跟踪系统
文章目录 前言 7.1 所需硬件 7.2 Nokov 系统设置 7.3 配置旋翼机 7.4 启动 Nokov 模块 7.5 MAVProxy 准备 7.6 测试飞行 7.7 参数说明 前言 本文将介绍如何通过 Nokov 运动捕捉系统向旋翼机传输姿势信息。联系方式:NOKOV | Optical Motion Capture System。…...
Linux Shell编程和循环语句
一.for循环语句 1.for语句的结构2.for循环语句实例①根据姓名列表来批量创建多个用户②根据IP地址列表检查主机状态 二.使用while循环语句1.while语句结构2.while循环语句应用①批量添加规律编号用户②猜价格游戏 三 until循环语句1.until语句结构① 计算1-50的和 1.for语句的结…...
Java后端程序员学习前端之JavaScript
1.什么是JavaScript 1.1.概述 JavaScript是一门世界上最流行的脚本语言javaScript 一个合格的后端人员,必须要精通JavaScript 1.2.历史 JavaScript的起源故事-CSDN博客 2.快速入门 2.1.引入JavaScript 1.内部标签 <script>//.......</script> --…...
redis多路复用IO模型 以及 6.0引入的多线程模型
redis为什么选择单线程 采用多线程的话,会出现上下文切换的开销采用多线程,会带来共享资源的竞争控制,比如多个线程同时访问同一个资源(键值)时,需要额外的手段来保障共享资源的正确性,会带来额…...